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 * External assign API
19 *
20 * @package    mod_assign
21 * @since      Moodle 2.4
22 * @copyright  2012 Paul Charsley
23 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26defined('MOODLE_INTERNAL') || die;
27
28require_once("$CFG->libdir/externallib.php");
29require_once("$CFG->dirroot/user/externallib.php");
30require_once("$CFG->dirroot/mod/assign/locallib.php");
31
32/**
33 * Assign functions
34 * @copyright 2012 Paul Charsley
35 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36 */
37class mod_assign_external extends external_api {
38
39    /**
40     * Generate a warning in a standard structure for a known failure.
41     *
42     * @param int $assignmentid - The assignment
43     * @param string $warningcode - The key for the warning message
44     * @param string $detail - A description of the error
45     * @return array - Warning structure containing item, itemid, warningcode, message
46     */
47    private static function generate_warning($assignmentid, $warningcode, $detail) {
48        $warningmessages = array(
49            'couldnotlock'=>'Could not lock the submission for this user.',
50            'couldnotunlock'=>'Could not unlock the submission for this user.',
51            'couldnotsubmitforgrading'=>'Could not submit assignment for grading.',
52            'couldnotrevealidentities'=>'Could not reveal identities.',
53            'couldnotgrantextensions'=>'Could not grant submission date extensions.',
54            'couldnotrevert'=>'Could not revert submission to draft.',
55            'invalidparameters'=>'Invalid parameters.',
56            'couldnotsavesubmission'=>'Could not save submission.',
57            'couldnotsavegrade'=>'Could not save grade.'
58        );
59
60        $message = $warningmessages[$warningcode];
61        if (empty($message)) {
62            $message = 'Unknown warning type.';
63        }
64
65        return array('item' => s($detail),
66                     'itemid'=>$assignmentid,
67                     'warningcode'=>$warningcode,
68                     'message'=>$message);
69    }
70
71    /**
72     * Describes the parameters for get_grades
73     * @return external_function_parameters
74     * @since  Moodle 2.4
75     */
76    public static function get_grades_parameters() {
77        return new external_function_parameters(
78            array(
79                'assignmentids' => new external_multiple_structure(
80                    new external_value(PARAM_INT, 'assignment id'),
81                    '1 or more assignment ids',
82                    VALUE_REQUIRED),
83                'since' => new external_value(PARAM_INT,
84                          'timestamp, only return records where timemodified >= since',
85                          VALUE_DEFAULT, 0)
86            )
87        );
88    }
89
90    /**
91     * Returns grade information from assign_grades for the requested assignment ids
92     * @param int[] $assignmentids
93     * @param int $since only return records with timemodified >= since
94     * @return array of grade records for each requested assignment
95     * @since  Moodle 2.4
96     */
97    public static function get_grades($assignmentids, $since = 0) {
98        global $DB;
99        $params = self::validate_parameters(self::get_grades_parameters(),
100                        array('assignmentids' => $assignmentids,
101                              'since' => $since));
102
103        $assignments = array();
104        $warnings = array();
105        $requestedassignmentids = $params['assignmentids'];
106
107        // Check the user is allowed to get the grades for the assignments requested.
108        $placeholders = array();
109        list($sqlassignmentids, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
110        $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
111               "WHERE md.name = :modname AND cm.instance ".$sqlassignmentids;
112        $placeholders['modname'] = 'assign';
113        $cms = $DB->get_records_sql($sql, $placeholders);
114        foreach ($cms as $cm) {
115            try {
116                $context = context_module::instance($cm->id);
117                self::validate_context($context);
118                $assign = new assign($context, null, null);
119                $assign->require_view_grades();
120            } catch (Exception $e) {
121                $requestedassignmentids = array_diff($requestedassignmentids, array($cm->instance));
122                $warning = array();
123                $warning['item'] = 'assignment';
124                $warning['itemid'] = $cm->instance;
125                $warning['warningcode'] = '1';
126                $warning['message'] = 'No access rights in module context';
127                $warnings[] = $warning;
128            }
129        }
130
131        // Create the query and populate an array of grade records from the recordset results.
132        if (count ($requestedassignmentids) > 0) {
133            $placeholders = array();
134            list($inorequalsql, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
135
136            $sql = "SELECT ag.id,
137                           ag.assignment,
138                           ag.userid,
139                           ag.timecreated,
140                           ag.timemodified,
141                           ag.grader,
142                           ag.grade,
143                           ag.attemptnumber
144                      FROM {assign_grades} ag, {assign_submission} s
145                     WHERE s.assignment $inorequalsql
146                       AND s.userid = ag.userid
147                       AND s.latest = 1
148                       AND s.attemptnumber = ag.attemptnumber
149                       AND ag.timemodified  >= :since
150                       AND ag.assignment = s.assignment
151                  ORDER BY ag.assignment, ag.id";
152
153            $placeholders['since'] = $params['since'];
154            $rs = $DB->get_recordset_sql($sql, $placeholders);
155            $currentassignmentid = null;
156            $assignment = null;
157            foreach ($rs as $rd) {
158                $grade = array();
159                $grade['id'] = $rd->id;
160                $grade['userid'] = $rd->userid;
161                $grade['timecreated'] = $rd->timecreated;
162                $grade['timemodified'] = $rd->timemodified;
163                $grade['grader'] = $rd->grader;
164                $grade['attemptnumber'] = $rd->attemptnumber;
165                $grade['grade'] = (string)$rd->grade;
166
167                if (is_null($currentassignmentid) || ($rd->assignment != $currentassignmentid )) {
168                    if (!is_null($assignment)) {
169                        $assignments[] = $assignment;
170                    }
171                    $assignment = array();
172                    $assignment['assignmentid'] = $rd->assignment;
173                    $assignment['grades'] = array();
174                    $requestedassignmentids = array_diff($requestedassignmentids, array($rd->assignment));
175                }
176                $assignment['grades'][] = $grade;
177
178                $currentassignmentid = $rd->assignment;
179            }
180            if (!is_null($assignment)) {
181                $assignments[] = $assignment;
182            }
183            $rs->close();
184        }
185        foreach ($requestedassignmentids as $assignmentid) {
186            $warning = array();
187            $warning['item'] = 'assignment';
188            $warning['itemid'] = $assignmentid;
189            $warning['warningcode'] = '3';
190            $warning['message'] = 'No grades found';
191            $warnings[] = $warning;
192        }
193
194        $result = array();
195        $result['assignments'] = $assignments;
196        $result['warnings'] = $warnings;
197        return $result;
198    }
199
200    /**
201     * Creates a grade single structure.
202     *
203     * @return external_single_structure a grade single structure.
204     * @since  Moodle 3.1
205     */
206    private static function get_grade_structure($required = VALUE_REQUIRED) {
207        return new external_single_structure(
208            array(
209                'id'                => new external_value(PARAM_INT, 'grade id'),
210                'assignment'        => new external_value(PARAM_INT, 'assignment id', VALUE_OPTIONAL),
211                'userid'            => new external_value(PARAM_INT, 'student id'),
212                'attemptnumber'     => new external_value(PARAM_INT, 'attempt number'),
213                'timecreated'       => new external_value(PARAM_INT, 'grade creation time'),
214                'timemodified'      => new external_value(PARAM_INT, 'grade last modified time'),
215                'grader'            => new external_value(PARAM_INT, 'grader, -1 if grader is hidden'),
216                'grade'             => new external_value(PARAM_TEXT, 'grade'),
217                'gradefordisplay'   => new external_value(PARAM_RAW, 'grade rendered into a format suitable for display',
218                                                            VALUE_OPTIONAL),
219            ), 'grade information', $required
220        );
221    }
222
223    /**
224     * Creates an assign_grades external_single_structure
225     * @return external_single_structure
226     * @since  Moodle 2.4
227     */
228    private static function assign_grades() {
229        return new external_single_structure(
230            array (
231                'assignmentid'  => new external_value(PARAM_INT, 'assignment id'),
232                'grades'        => new external_multiple_structure(self::get_grade_structure())
233            )
234        );
235    }
236
237    /**
238     * Describes the get_grades return value
239     * @return external_single_structure
240     * @since  Moodle 2.4
241     */
242    public static function get_grades_returns() {
243        return new external_single_structure(
244            array(
245                'assignments' => new external_multiple_structure(self::assign_grades(), 'list of assignment grade information'),
246                'warnings'      => new external_warnings('item is always \'assignment\'',
247                    'when errorcode is 3 then itemid is an assignment id. When errorcode is 1, itemid is a course module id',
248                    'errorcode can be 3 (no grades found) or 1 (no permission to get grades)')
249            )
250        );
251    }
252
253    /**
254     * Returns description of method parameters
255     *
256     * @return external_function_parameters
257     * @since  Moodle 2.4
258     */
259    public static function get_assignments_parameters() {
260        return new external_function_parameters(
261            array(
262                'courseids' => new external_multiple_structure(
263                    new external_value(PARAM_INT, 'course id, empty for retrieving all the courses where the user is enroled in'),
264                    '0 or more course ids',
265                    VALUE_DEFAULT, array()
266                ),
267                'capabilities'  => new external_multiple_structure(
268                    new external_value(PARAM_CAPABILITY, 'capability'),
269                    'list of capabilities used to filter courses',
270                    VALUE_DEFAULT, array()
271                ),
272                'includenotenrolledcourses' => new external_value(PARAM_BOOL, 'whether to return courses that the user can see
273                                                                    even if is not enroled in. This requires the parameter courseids
274                                                                    to not be empty.', VALUE_DEFAULT, false)
275            )
276        );
277    }
278
279    /**
280     * Returns an array of courses the user is enrolled, and for each course all of the assignments that the user can
281     * view within that course.
282     *
283     * @param array $courseids An optional array of course ids. If provided only assignments within the given course
284     * will be returned. If the user is not enrolled in or can't view a given course a warning will be generated and returned.
285     * @param array $capabilities An array of additional capability checks you wish to be made on the course context.
286     * @param bool $includenotenrolledcourses Wheter to return courses that the user can see even if is not enroled in.
287     * This requires the parameter $courseids to not be empty.
288     * @return An array of courses and warnings.
289     * @since  Moodle 2.4
290     */
291    public static function get_assignments($courseids = array(), $capabilities = array(), $includenotenrolledcourses = false) {
292        global $USER, $DB, $CFG;
293
294        $params = self::validate_parameters(
295            self::get_assignments_parameters(),
296            array(
297                'courseids' => $courseids,
298                'capabilities' => $capabilities,
299                'includenotenrolledcourses' => $includenotenrolledcourses
300            )
301        );
302
303        $warnings = array();
304        $courses = array();
305        $fields = 'sortorder,shortname,fullname,timemodified';
306
307        // If the courseids list is empty, we return only the courses where the user is enrolled in.
308        if (empty($params['courseids'])) {
309            $courses = enrol_get_users_courses($USER->id, true, $fields);
310            $courseids = array_keys($courses);
311        } else if ($includenotenrolledcourses) {
312            // In this case, we don't have to check here for enrolmnents. Maybe the user can see the course even if is not enrolled.
313            $courseids = $params['courseids'];
314        } else {
315            // We need to check for enrolments.
316            $mycourses = enrol_get_users_courses($USER->id, true, $fields);
317            $mycourseids = array_keys($mycourses);
318
319            foreach ($params['courseids'] as $courseid) {
320                if (!in_array($courseid, $mycourseids)) {
321                    unset($courses[$courseid]);
322                    $warnings[] = array(
323                        'item' => 'course',
324                        'itemid' => $courseid,
325                        'warningcode' => '2',
326                        'message' => 'User is not enrolled or does not have requested capability'
327                    );
328                } else {
329                    $courses[$courseid] = $mycourses[$courseid];
330                }
331            }
332            $courseids = array_keys($courses);
333        }
334
335        foreach ($courseids as $cid) {
336
337            try {
338                $context = context_course::instance($cid);
339                self::validate_context($context);
340
341                // Check if this course was already loaded (by enrol_get_users_courses).
342                if (!isset($courses[$cid])) {
343                    $courses[$cid] = get_course($cid);
344                }
345                $courses[$cid]->contextid = $context->id;
346            } catch (Exception $e) {
347                unset($courses[$cid]);
348                $warnings[] = array(
349                    'item' => 'course',
350                    'itemid' => $cid,
351                    'warningcode' => '1',
352                    'message' => 'No access rights in course context '.$e->getMessage()
353                );
354                continue;
355            }
356            if (count($params['capabilities']) > 0 && !has_all_capabilities($params['capabilities'], $context)) {
357                unset($courses[$cid]);
358            }
359        }
360        $extrafields='m.id as assignmentid, ' .
361                     'm.course, ' .
362                     'm.nosubmissions, ' .
363                     'm.submissiondrafts, ' .
364                     'm.sendnotifications, '.
365                     'm.sendlatenotifications, ' .
366                     'm.sendstudentnotifications, ' .
367                     'm.duedate, ' .
368                     'm.allowsubmissionsfromdate, '.
369                     'm.grade, ' .
370                     'm.timemodified, '.
371                     'm.completionsubmit, ' .
372                     'm.cutoffdate, ' .
373                     'm.gradingduedate, ' .
374                     'm.teamsubmission, ' .
375                     'm.requireallteammemberssubmit, '.
376                     'm.teamsubmissiongroupingid, ' .
377                     'm.blindmarking, ' .
378                     'm.hidegrader, ' .
379                     'm.revealidentities, ' .
380                     'm.attemptreopenmethod, '.
381                     'm.maxattempts, ' .
382                     'm.markingworkflow, ' .
383                     'm.markingallocation, ' .
384                     'm.requiresubmissionstatement, '.
385                     'm.preventsubmissionnotingroup, '.
386                     'm.intro, '.
387                     'm.introformat';
388        $coursearray = array();
389        foreach ($courses as $id => $course) {
390            $assignmentarray = array();
391            // Get a list of assignments for the course.
392            if ($modules = get_coursemodules_in_course('assign', $courses[$id]->id, $extrafields)) {
393                foreach ($modules as $module) {
394                    $context = context_module::instance($module->id);
395                    try {
396                        self::validate_context($context);
397                        require_capability('mod/assign:view', $context);
398                    } catch (Exception $e) {
399                        $warnings[] = array(
400                            'item' => 'module',
401                            'itemid' => $module->id,
402                            'warningcode' => '1',
403                            'message' => 'No access rights in module context'
404                        );
405                        continue;
406                    }
407
408                    $assign = new assign($context, null, null);
409                    // Update assign with override information.
410                    $assign->update_effective_access($USER->id);
411
412                    // Get configurations for only enabled plugins.
413                    $plugins = $assign->get_submission_plugins();
414                    $plugins = array_merge($plugins, $assign->get_feedback_plugins());
415
416                    $configarray = array();
417                    foreach ($plugins as $plugin) {
418                        if ($plugin->is_enabled() && $plugin->is_visible()) {
419                            $configrecords = $plugin->get_config_for_external();
420                            foreach ($configrecords as $name => $value) {
421                                $configarray[] = array(
422                                    'plugin' => $plugin->get_type(),
423                                    'subtype' => $plugin->get_subtype(),
424                                    'name' => $name,
425                                    'value' => $value
426                                );
427                            }
428                        }
429                    }
430
431                    $assignment = array(
432                        'id' => $module->assignmentid,
433                        'cmid' => $module->id,
434                        'course' => $module->course,
435                        'name' => external_format_string($module->name, $context),
436                        'nosubmissions' => $module->nosubmissions,
437                        'submissiondrafts' => $module->submissiondrafts,
438                        'sendnotifications' => $module->sendnotifications,
439                        'sendlatenotifications' => $module->sendlatenotifications,
440                        'sendstudentnotifications' => $module->sendstudentnotifications,
441                        'duedate' => $assign->get_instance()->duedate,
442                        'allowsubmissionsfromdate' => $assign->get_instance()->allowsubmissionsfromdate,
443                        'grade' => $module->grade,
444                        'timemodified' => $module->timemodified,
445                        'completionsubmit' => $module->completionsubmit,
446                        'cutoffdate' => $assign->get_instance()->cutoffdate,
447                        'gradingduedate' => $assign->get_instance()->gradingduedate,
448                        'teamsubmission' => $module->teamsubmission,
449                        'requireallteammemberssubmit' => $module->requireallteammemberssubmit,
450                        'teamsubmissiongroupingid' => $module->teamsubmissiongroupingid,
451                        'blindmarking' => $module->blindmarking,
452                        'hidegrader' => $module->hidegrader,
453                        'revealidentities' => $module->revealidentities,
454                        'attemptreopenmethod' => $module->attemptreopenmethod,
455                        'maxattempts' => $module->maxattempts,
456                        'markingworkflow' => $module->markingworkflow,
457                        'markingallocation' => $module->markingallocation,
458                        'requiresubmissionstatement' => $module->requiresubmissionstatement,
459                        'preventsubmissionnotingroup' => $module->preventsubmissionnotingroup,
460                        'configs' => $configarray
461                    );
462
463                    // Return or not intro and file attachments depending on the plugin settings.
464                    if ($assign->show_intro()) {
465                        $options = array('noclean' => true);
466                        list($assignment['intro'], $assignment['introformat']) =
467                            external_format_text($module->intro, $module->introformat, $context->id, 'mod_assign', 'intro', null,
468                                $options);
469                        $assignment['introfiles'] = external_util::get_area_files($context->id, 'mod_assign', 'intro', false,
470                                                                                    false);
471
472                        $assignment['introattachments'] = external_util::get_area_files($context->id, 'mod_assign',
473                                                            ASSIGN_INTROATTACHMENT_FILEAREA, 0);
474                    }
475
476                    if ($module->requiresubmissionstatement) {
477                        // Submission statement is required, return the submission statement value.
478                        $adminconfig = get_config('assign');
479                        // Single submission.
480                        if (!$module->teamsubmission) {
481                            list($assignment['submissionstatement'], $assignment['submissionstatementformat']) =
482                                external_format_text($adminconfig->submissionstatement, FORMAT_MOODLE, $context->id,
483                                    'mod_assign', '', 0);
484                        } else { // Team submission.
485                            // One user can submit for the whole team.
486                            if (!empty($adminconfig->submissionstatementteamsubmission) && !$module->requireallteammemberssubmit) {
487                                list($assignment['submissionstatement'], $assignment['submissionstatementformat']) =
488                                    external_format_text($adminconfig->submissionstatementteamsubmission,
489                                        FORMAT_MOODLE, $context->id, 'mod_assign', '', 0);
490                            } else if (!empty($adminconfig->submissionstatementteamsubmissionallsubmit) &&
491                                $module->requireallteammemberssubmit) {
492                                // All team members must submit.
493                                list($assignment['submissionstatement'], $assignment['submissionstatementformat']) =
494                                    external_format_text($adminconfig->submissionstatementteamsubmissionallsubmit,
495                                        FORMAT_MOODLE, $context->id, 'mod_assign', '', 0);
496                            }
497                        }
498                    }
499
500                    $assignmentarray[] = $assignment;
501                }
502            }
503            $coursearray[]= array(
504                'id' => $courses[$id]->id,
505                'fullname' => external_format_string($courses[$id]->fullname, $course->contextid),
506                'shortname' => external_format_string($courses[$id]->shortname, $course->contextid),
507                'timemodified' => $courses[$id]->timemodified,
508                'assignments' => $assignmentarray
509            );
510        }
511
512        $result = array(
513            'courses' => $coursearray,
514            'warnings' => $warnings
515        );
516        return $result;
517    }
518
519    /**
520     * Creates an assignment external_single_structure
521     *
522     * @return external_single_structure
523     * @since Moodle 2.4
524     */
525    private static function get_assignments_assignment_structure() {
526        return new external_single_structure(
527            array(
528                'id' => new external_value(PARAM_INT, 'assignment id'),
529                'cmid' => new external_value(PARAM_INT, 'course module id'),
530                'course' => new external_value(PARAM_INT, 'course id'),
531                'name' => new external_value(PARAM_RAW, 'assignment name'),
532                'nosubmissions' => new external_value(PARAM_INT, 'no submissions'),
533                'submissiondrafts' => new external_value(PARAM_INT, 'submissions drafts'),
534                'sendnotifications' => new external_value(PARAM_INT, 'send notifications'),
535                'sendlatenotifications' => new external_value(PARAM_INT, 'send notifications'),
536                'sendstudentnotifications' => new external_value(PARAM_INT, 'send student notifications (default)'),
537                'duedate' => new external_value(PARAM_INT, 'assignment due date'),
538                'allowsubmissionsfromdate' => new external_value(PARAM_INT, 'allow submissions from date'),
539                'grade' => new external_value(PARAM_INT, 'grade type'),
540                'timemodified' => new external_value(PARAM_INT, 'last time assignment was modified'),
541                'completionsubmit' => new external_value(PARAM_INT, 'if enabled, set activity as complete following submission'),
542                'cutoffdate' => new external_value(PARAM_INT, 'date after which submission is not accepted without an extension'),
543                'gradingduedate' => new external_value(PARAM_INT, 'the expected date for marking the submissions'),
544                'teamsubmission' => new external_value(PARAM_INT, 'if enabled, students submit as a team'),
545                'requireallteammemberssubmit' => new external_value(PARAM_INT, 'if enabled, all team members must submit'),
546                'teamsubmissiongroupingid' => new external_value(PARAM_INT, 'the grouping id for the team submission groups'),
547                'blindmarking' => new external_value(PARAM_INT, 'if enabled, hide identities until reveal identities actioned'),
548                'hidegrader' => new external_value(PARAM_INT, 'If enabled, hide grader to student'),
549                'revealidentities' => new external_value(PARAM_INT, 'show identities for a blind marking assignment'),
550                'attemptreopenmethod' => new external_value(PARAM_TEXT, 'method used to control opening new attempts'),
551                'maxattempts' => new external_value(PARAM_INT, 'maximum number of attempts allowed'),
552                'markingworkflow' => new external_value(PARAM_INT, 'enable marking workflow'),
553                'markingallocation' => new external_value(PARAM_INT, 'enable marking allocation'),
554                'requiresubmissionstatement' => new external_value(PARAM_INT, 'student must accept submission statement'),
555                'preventsubmissionnotingroup' => new external_value(PARAM_INT, 'Prevent submission not in group', VALUE_OPTIONAL),
556                'submissionstatement' => new external_value(PARAM_RAW, 'Submission statement formatted.', VALUE_OPTIONAL),
557                'submissionstatementformat' => new external_format_value('submissionstatement', VALUE_OPTIONAL),
558                'configs' => new external_multiple_structure(self::get_assignments_config_structure(), 'configuration settings'),
559                'intro' => new external_value(PARAM_RAW,
560                    'assignment intro, not allways returned because it deppends on the activity configuration', VALUE_OPTIONAL),
561                'introformat' => new external_format_value('intro', VALUE_OPTIONAL),
562                'introfiles' => new external_files('Files in the introduction text', VALUE_OPTIONAL),
563                'introattachments' => new external_files('intro attachments files', VALUE_OPTIONAL),
564            ), 'assignment information object');
565    }
566
567    /**
568     * Creates an assign_plugin_config external_single_structure
569     *
570     * @return external_single_structure
571     * @since Moodle 2.4
572     */
573    private static function get_assignments_config_structure() {
574        return new external_single_structure(
575            array(
576                'id' => new external_value(PARAM_INT, 'assign_plugin_config id', VALUE_OPTIONAL),
577                'assignment' => new external_value(PARAM_INT, 'assignment id', VALUE_OPTIONAL),
578                'plugin' => new external_value(PARAM_TEXT, 'plugin'),
579                'subtype' => new external_value(PARAM_TEXT, 'subtype'),
580                'name' => new external_value(PARAM_TEXT, 'name'),
581                'value' => new external_value(PARAM_TEXT, 'value')
582            ), 'assignment configuration object'
583        );
584    }
585
586    /**
587     * Creates a course external_single_structure
588     *
589     * @return external_single_structure
590     * @since Moodle 2.4
591     */
592    private static function get_assignments_course_structure() {
593        return new external_single_structure(
594            array(
595                'id' => new external_value(PARAM_INT, 'course id'),
596                'fullname' => new external_value(PARAM_RAW, 'course full name'),
597                'shortname' => new external_value(PARAM_RAW, 'course short name'),
598                'timemodified' => new external_value(PARAM_INT, 'last time modified'),
599                'assignments' => new external_multiple_structure(self::get_assignments_assignment_structure(), 'assignment info')
600              ), 'course information object'
601        );
602    }
603
604    /**
605     * Describes the return value for get_assignments
606     *
607     * @return external_single_structure
608     * @since Moodle 2.4
609     */
610    public static function get_assignments_returns() {
611        return new external_single_structure(
612            array(
613                'courses' => new external_multiple_structure(self::get_assignments_course_structure(), 'list of courses'),
614                'warnings'  => new external_warnings('item can be \'course\' (errorcode 1 or 2) or \'module\' (errorcode 1)',
615                    'When item is a course then itemid is a course id. When the item is a module then itemid is a module id',
616                    'errorcode can be 1 (no access rights) or 2 (not enrolled or no permissions)')
617            )
618        );
619    }
620
621    /**
622     * Return information (files and text fields) for the given plugins in the assignment.
623     *
624     * @param  assign $assign the assignment object
625     * @param  array $assignplugins array of assignment plugins (submission or feedback)
626     * @param  stdClass $item the item object (submission or grade)
627     * @return array an array containing the plugins returned information
628     */
629    private static function get_plugins_data($assign, $assignplugins, $item) {
630        global $CFG;
631
632        $plugins = array();
633        $fs = get_file_storage();
634
635        foreach ($assignplugins as $assignplugin) {
636
637            if (!$assignplugin->is_enabled() or !$assignplugin->is_visible()) {
638                continue;
639            }
640
641            $plugin = array(
642                'name' => $assignplugin->get_name(),
643                'type' => $assignplugin->get_type()
644            );
645            // Subtype is 'assignsubmission', type is currently 'file' or 'onlinetext'.
646            $component = $assignplugin->get_subtype().'_'.$assignplugin->get_type();
647
648            $fileareas = $assignplugin->get_file_areas();
649            foreach ($fileareas as $filearea => $name) {
650                $fileareainfo = array('area' => $filearea);
651
652                $fileareainfo['files'] = external_util::get_area_files(
653                    $assign->get_context()->id,
654                    $component,
655                    $filearea,
656                    $item->id
657                );
658
659                $plugin['fileareas'][] = $fileareainfo;
660            }
661
662            $editorfields = $assignplugin->get_editor_fields();
663            foreach ($editorfields as $name => $description) {
664                $editorfieldinfo = array(
665                    'name' => $name,
666                    'description' => $description,
667                    'text' => $assignplugin->get_editor_text($name, $item->id),
668                    'format' => $assignplugin->get_editor_format($name, $item->id)
669                );
670
671                // Now format the text.
672                foreach ($fileareas as $filearea => $name) {
673                    list($editorfieldinfo['text'], $editorfieldinfo['format']) = external_format_text(
674                        $editorfieldinfo['text'], $editorfieldinfo['format'], $assign->get_context()->id,
675                        $component, $filearea, $item->id);
676                }
677
678                $plugin['editorfields'][] = $editorfieldinfo;
679            }
680            $plugins[] = $plugin;
681        }
682        return $plugins;
683    }
684
685    /**
686     * Describes the parameters for get_submissions
687     *
688     * @return external_function_parameters
689     * @since Moodle 2.5
690     */
691    public static function get_submissions_parameters() {
692        return new external_function_parameters(
693            array(
694                'assignmentids' => new external_multiple_structure(
695                    new external_value(PARAM_INT, 'assignment id'),
696                    '1 or more assignment ids',
697                    VALUE_REQUIRED),
698                'status' => new external_value(PARAM_ALPHA, 'status', VALUE_DEFAULT, ''),
699                'since' => new external_value(PARAM_INT, 'submitted since', VALUE_DEFAULT, 0),
700                'before' => new external_value(PARAM_INT, 'submitted before', VALUE_DEFAULT, 0)
701            )
702        );
703    }
704
705    /**
706     * Returns submissions for the requested assignment ids
707     *
708     * @param int[] $assignmentids
709     * @param string $status only return submissions with this status
710     * @param int $since only return submissions with timemodified >= since
711     * @param int $before only return submissions with timemodified <= before
712     * @return array of submissions for each requested assignment
713     * @since Moodle 2.5
714     */
715    public static function get_submissions($assignmentids, $status = '', $since = 0, $before = 0) {
716        global $DB, $CFG;
717
718        $params = self::validate_parameters(self::get_submissions_parameters(),
719                        array('assignmentids' => $assignmentids,
720                              'status' => $status,
721                              'since' => $since,
722                              'before' => $before));
723
724        $warnings = array();
725        $assignments = array();
726
727        // Check the user is allowed to get the submissions for the assignments requested.
728        $placeholders = array();
729        list($inorequalsql, $placeholders) = $DB->get_in_or_equal($params['assignmentids'], SQL_PARAMS_NAMED);
730        $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
731               "WHERE md.name = :modname AND cm.instance ".$inorequalsql;
732        $placeholders['modname'] = 'assign';
733        $cms = $DB->get_records_sql($sql, $placeholders);
734        $assigns = array();
735        foreach ($cms as $cm) {
736            try {
737                $context = context_module::instance($cm->id);
738                self::validate_context($context);
739                $assign = new assign($context, null, null);
740                $assign->require_view_grades();
741                $assigns[] = $assign;
742            } catch (Exception $e) {
743                $warnings[] = array(
744                    'item' => 'assignment',
745                    'itemid' => $cm->instance,
746                    'warningcode' => '1',
747                    'message' => 'No access rights in module context'
748                );
749            }
750        }
751
752        foreach ($assigns as $assign) {
753            $submissions = array();
754            $placeholders = array('assignid1' => $assign->get_instance()->id,
755                                  'assignid2' => $assign->get_instance()->id);
756
757            $submissionmaxattempt = 'SELECT mxs.userid, mxs.groupid, MAX(mxs.attemptnumber) AS maxattempt
758                                     FROM {assign_submission} mxs
759                                     WHERE mxs.assignment = :assignid1 GROUP BY mxs.userid, mxs.groupid';
760
761            $sql = "SELECT mas.id, mas.assignment,mas.userid,".
762                   "mas.timecreated,mas.timemodified,mas.status,mas.groupid,mas.attemptnumber ".
763                   "FROM {assign_submission} mas ".
764                   "JOIN ( " . $submissionmaxattempt . " ) smx ON mas.userid = smx.userid ".
765                   "AND mas.groupid = smx.groupid ".
766                   "WHERE mas.assignment = :assignid2 AND mas.attemptnumber = smx.maxattempt";
767
768            if (!empty($params['status'])) {
769                $placeholders['status'] = $params['status'];
770                $sql = $sql." AND mas.status = :status";
771            }
772            if (!empty($params['before'])) {
773                $placeholders['since'] = $params['since'];
774                $placeholders['before'] = $params['before'];
775                $sql = $sql." AND mas.timemodified BETWEEN :since AND :before";
776            } else {
777                $placeholders['since'] = $params['since'];
778                $sql = $sql." AND mas.timemodified >= :since";
779            }
780
781            $submissionrecords = $DB->get_records_sql($sql, $placeholders);
782
783            if (!empty($submissionrecords)) {
784                $submissionplugins = $assign->get_submission_plugins();
785                foreach ($submissionrecords as $submissionrecord) {
786                    $submission = array(
787                        'id' => $submissionrecord->id,
788                        'userid' => $submissionrecord->userid,
789                        'timecreated' => $submissionrecord->timecreated,
790                        'timemodified' => $submissionrecord->timemodified,
791                        'status' => $submissionrecord->status,
792                        'attemptnumber' => $submissionrecord->attemptnumber,
793                        'groupid' => $submissionrecord->groupid,
794                        'plugins' => self::get_plugins_data($assign, $submissionplugins, $submissionrecord),
795                        'gradingstatus' => $assign->get_grading_status($submissionrecord->userid)
796                    );
797
798                    if (($assign->get_instance()->teamsubmission
799                        && $assign->can_view_group_submission($submissionrecord->groupid))
800                        || (!$assign->get_instance()->teamsubmission
801                        && $assign->can_view_submission($submissionrecord->userid))
802                    ) {
803                        $submissions[] = $submission;
804                    }
805                }
806            } else {
807                $warnings[] = array(
808                    'item' => 'module',
809                    'itemid' => $assign->get_instance()->id,
810                    'warningcode' => '3',
811                    'message' => 'No submissions found'
812                );
813            }
814
815            $assignments[] = array(
816                'assignmentid' => $assign->get_instance()->id,
817                'submissions' => $submissions
818            );
819
820        }
821
822        $result = array(
823            'assignments' => $assignments,
824            'warnings' => $warnings
825        );
826        return $result;
827    }
828
829    /**
830     * Creates an assignment plugin structure.
831     *
832     * @return external_single_structure the plugin structure
833     */
834    private static function get_plugin_structure() {
835        return new external_single_structure(
836            array(
837                'type' => new external_value(PARAM_TEXT, 'submission plugin type'),
838                'name' => new external_value(PARAM_TEXT, 'submission plugin name'),
839                'fileareas' => new external_multiple_structure(
840                    new external_single_structure(
841                        array (
842                            'area' => new external_value (PARAM_TEXT, 'file area'),
843                            'files' => new external_files('files', VALUE_OPTIONAL),
844                        )
845                    ), 'fileareas', VALUE_OPTIONAL
846                ),
847                'editorfields' => new external_multiple_structure(
848                    new external_single_structure(
849                        array(
850                            'name' => new external_value(PARAM_TEXT, 'field name'),
851                            'description' => new external_value(PARAM_RAW, 'field description'),
852                            'text' => new external_value (PARAM_RAW, 'field value'),
853                            'format' => new external_format_value ('text')
854                        )
855                    )
856                    , 'editorfields', VALUE_OPTIONAL
857                )
858            )
859        );
860    }
861
862    /**
863     * Creates a submission structure.
864     *
865     * @return external_single_structure the submission structure
866     */
867    private static function get_submission_structure($required = VALUE_REQUIRED) {
868        return new external_single_structure(
869            array(
870                'id' => new external_value(PARAM_INT, 'submission id'),
871                'userid' => new external_value(PARAM_INT, 'student id'),
872                'attemptnumber' => new external_value(PARAM_INT, 'attempt number'),
873                'timecreated' => new external_value(PARAM_INT, 'submission creation time'),
874                'timemodified' => new external_value(PARAM_INT, 'submission last modified time'),
875                'status' => new external_value(PARAM_TEXT, 'submission status'),
876                'groupid' => new external_value(PARAM_INT, 'group id'),
877                'assignment' => new external_value(PARAM_INT, 'assignment id', VALUE_OPTIONAL),
878                'latest' => new external_value(PARAM_INT, 'latest attempt', VALUE_OPTIONAL),
879                'plugins' => new external_multiple_structure(self::get_plugin_structure(), 'plugins', VALUE_OPTIONAL),
880                'gradingstatus' => new external_value(PARAM_ALPHANUMEXT, 'Grading status.', VALUE_OPTIONAL),
881            ), 'submission info', $required
882        );
883    }
884
885    /**
886     * Creates an assign_submissions external_single_structure
887     *
888     * @return external_single_structure
889     * @since Moodle 2.5
890     */
891    private static function get_submissions_structure() {
892        return new external_single_structure(
893            array (
894                'assignmentid' => new external_value(PARAM_INT, 'assignment id'),
895                'submissions' => new external_multiple_structure(self::get_submission_structure())
896            )
897        );
898    }
899
900    /**
901     * Describes the get_submissions return value
902     *
903     * @return external_single_structure
904     * @since Moodle 2.5
905     */
906    public static function get_submissions_returns() {
907        return new external_single_structure(
908            array(
909                'assignments' => new external_multiple_structure(self::get_submissions_structure(), 'assignment submissions'),
910                'warnings' => new external_warnings()
911            )
912        );
913    }
914
915    /**
916     * Describes the parameters for set_user_flags
917     * @return external_function_parameters
918     * @since  Moodle 2.6
919     */
920    public static function set_user_flags_parameters() {
921        return new external_function_parameters(
922            array(
923                'assignmentid'    => new external_value(PARAM_INT, 'assignment id'),
924                'userflags' => new external_multiple_structure(
925                    new external_single_structure(
926                        array(
927                            'userid'           => new external_value(PARAM_INT, 'student id'),
928                            'locked'           => new external_value(PARAM_INT, 'locked', VALUE_OPTIONAL),
929                            'mailed'           => new external_value(PARAM_INT, 'mailed', VALUE_OPTIONAL),
930                            'extensionduedate' => new external_value(PARAM_INT, 'extension due date', VALUE_OPTIONAL),
931                            'workflowstate'    => new external_value(PARAM_ALPHA, 'marking workflow state', VALUE_OPTIONAL),
932                            'allocatedmarker'  => new external_value(PARAM_INT, 'allocated marker', VALUE_OPTIONAL)
933                        )
934                    )
935                )
936            )
937        );
938    }
939
940    /**
941     * Create or update user_flags records
942     *
943     * @param int $assignmentid the assignment for which the userflags are created or updated
944     * @param array $userflags  An array of userflags to create or update
945     * @return array containing success or failure information for each record
946     * @since Moodle 2.6
947     */
948    public static function set_user_flags($assignmentid, $userflags = array()) {
949        global $CFG, $DB;
950
951        $params = self::validate_parameters(self::set_user_flags_parameters(),
952                                            array('assignmentid' => $assignmentid,
953                                                  'userflags' => $userflags));
954
955        // Load assignment if it exists and if the user has the capability.
956        list($assign, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
957        require_capability('mod/assign:grade', $context);
958
959        $results = array();
960        foreach ($params['userflags'] as $userflag) {
961            $success = true;
962            $result = array();
963
964            $record = $assign->get_user_flags($userflag['userid'], false);
965            if ($record) {
966                if (isset($userflag['locked'])) {
967                    $record->locked = $userflag['locked'];
968                }
969                if (isset($userflag['mailed'])) {
970                    $record->mailed = $userflag['mailed'];
971                }
972                if (isset($userflag['extensionduedate'])) {
973                    $record->extensionduedate = $userflag['extensionduedate'];
974                }
975                if (isset($userflag['workflowstate'])) {
976                    $record->workflowstate = $userflag['workflowstate'];
977                }
978                if (isset($userflag['allocatedmarker'])) {
979                    $record->allocatedmarker = $userflag['allocatedmarker'];
980                }
981                if ($assign->update_user_flags($record)) {
982                    $result['id'] = $record->id;
983                    $result['userid'] = $userflag['userid'];
984                } else {
985                    $result['id'] = $record->id;
986                    $result['userid'] = $userflag['userid'];
987                    $result['errormessage'] = 'Record created but values could not be set';
988                }
989            } else {
990                $record = $assign->get_user_flags($userflag['userid'], true);
991                $setfields = isset($userflag['locked'])
992                             || isset($userflag['mailed'])
993                             || isset($userflag['extensionduedate'])
994                             || isset($userflag['workflowstate'])
995                             || isset($userflag['allocatedmarker']);
996                if ($record) {
997                    if ($setfields) {
998                        if (isset($userflag['locked'])) {
999                            $record->locked = $userflag['locked'];
1000                        }
1001                        if (isset($userflag['mailed'])) {
1002                            $record->mailed = $userflag['mailed'];
1003                        }
1004                        if (isset($userflag['extensionduedate'])) {
1005                            $record->extensionduedate = $userflag['extensionduedate'];
1006                        }
1007                        if (isset($userflag['workflowstate'])) {
1008                            $record->workflowstate = $userflag['workflowstate'];
1009                        }
1010                        if (isset($userflag['allocatedmarker'])) {
1011                            $record->allocatedmarker = $userflag['allocatedmarker'];
1012                        }
1013                        if ($assign->update_user_flags($record)) {
1014                            $result['id'] = $record->id;
1015                            $result['userid'] = $userflag['userid'];
1016                        } else {
1017                            $result['id'] = $record->id;
1018                            $result['userid'] = $userflag['userid'];
1019                            $result['errormessage'] = 'Record created but values could not be set';
1020                        }
1021                    } else {
1022                        $result['id'] = $record->id;
1023                        $result['userid'] = $userflag['userid'];
1024                    }
1025                } else {
1026                    $result['id'] = -1;
1027                    $result['userid'] = $userflag['userid'];
1028                    $result['errormessage'] = 'Record could not be created';
1029                }
1030            }
1031
1032            $results[] = $result;
1033        }
1034        return $results;
1035    }
1036
1037    /**
1038     * Describes the set_user_flags return value
1039     * @return external_multiple_structure
1040     * @since  Moodle 2.6
1041     */
1042    public static function set_user_flags_returns() {
1043        return new external_multiple_structure(
1044            new external_single_structure(
1045                array(
1046                    'id' => new external_value(PARAM_INT, 'id of record if successful, -1 for failure'),
1047                    'userid' => new external_value(PARAM_INT, 'userid of record'),
1048                    'errormessage' => new external_value(PARAM_TEXT, 'Failure error message', VALUE_OPTIONAL)
1049                )
1050            )
1051        );
1052    }
1053
1054    /**
1055     * Describes the parameters for get_user_flags
1056     * @return external_function_parameters
1057     * @since  Moodle 2.6
1058     */
1059    public static function get_user_flags_parameters() {
1060        return new external_function_parameters(
1061            array(
1062                'assignmentids' => new external_multiple_structure(
1063                    new external_value(PARAM_INT, 'assignment id'),
1064                    '1 or more assignment ids',
1065                    VALUE_REQUIRED)
1066            )
1067        );
1068    }
1069
1070    /**
1071     * Returns user flag information from assign_user_flags for the requested assignment ids
1072     * @param int[] $assignmentids
1073     * @return array of user flag records for each requested assignment
1074     * @since  Moodle 2.6
1075     */
1076    public static function get_user_flags($assignmentids) {
1077        global $DB;
1078        $params = self::validate_parameters(self::get_user_flags_parameters(),
1079                        array('assignmentids' => $assignmentids));
1080
1081        $assignments = array();
1082        $warnings = array();
1083        $requestedassignmentids = $params['assignmentids'];
1084
1085        // Check the user is allowed to get the user flags for the assignments requested.
1086        $placeholders = array();
1087        list($sqlassignmentids, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1088        $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
1089               "WHERE md.name = :modname AND cm.instance ".$sqlassignmentids;
1090        $placeholders['modname'] = 'assign';
1091        $cms = $DB->get_records_sql($sql, $placeholders);
1092        foreach ($cms as $cm) {
1093            try {
1094                $context = context_module::instance($cm->id);
1095                self::validate_context($context);
1096                require_capability('mod/assign:grade', $context);
1097            } catch (Exception $e) {
1098                $requestedassignmentids = array_diff($requestedassignmentids, array($cm->instance));
1099                $warning = array();
1100                $warning['item'] = 'assignment';
1101                $warning['itemid'] = $cm->instance;
1102                $warning['warningcode'] = '1';
1103                $warning['message'] = 'No access rights in module context';
1104                $warnings[] = $warning;
1105            }
1106        }
1107
1108        // Create the query and populate an array of assign_user_flags records from the recordset results.
1109        if (count ($requestedassignmentids) > 0) {
1110            $placeholders = array();
1111            list($inorequalsql, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1112
1113            $sql = "SELECT auf.id,auf.assignment,auf.userid,auf.locked,auf.mailed,".
1114                   "auf.extensionduedate,auf.workflowstate,auf.allocatedmarker ".
1115                   "FROM {assign_user_flags} auf ".
1116                   "WHERE auf.assignment ".$inorequalsql.
1117                   " ORDER BY auf.assignment, auf.id";
1118
1119            $rs = $DB->get_recordset_sql($sql, $placeholders);
1120            $currentassignmentid = null;
1121            $assignment = null;
1122            foreach ($rs as $rd) {
1123                $userflag = array();
1124                $userflag['id'] = $rd->id;
1125                $userflag['userid'] = $rd->userid;
1126                $userflag['locked'] = $rd->locked;
1127                $userflag['mailed'] = $rd->mailed;
1128                $userflag['extensionduedate'] = $rd->extensionduedate;
1129                $userflag['workflowstate'] = $rd->workflowstate;
1130                $userflag['allocatedmarker'] = $rd->allocatedmarker;
1131
1132                if (is_null($currentassignmentid) || ($rd->assignment != $currentassignmentid )) {
1133                    if (!is_null($assignment)) {
1134                        $assignments[] = $assignment;
1135                    }
1136                    $assignment = array();
1137                    $assignment['assignmentid'] = $rd->assignment;
1138                    $assignment['userflags'] = array();
1139                    $requestedassignmentids = array_diff($requestedassignmentids, array($rd->assignment));
1140                }
1141                $assignment['userflags'][] = $userflag;
1142
1143                $currentassignmentid = $rd->assignment;
1144            }
1145            if (!is_null($assignment)) {
1146                $assignments[] = $assignment;
1147            }
1148            $rs->close();
1149
1150        }
1151
1152        foreach ($requestedassignmentids as $assignmentid) {
1153            $warning = array();
1154            $warning['item'] = 'assignment';
1155            $warning['itemid'] = $assignmentid;
1156            $warning['warningcode'] = '3';
1157            $warning['message'] = 'No user flags found';
1158            $warnings[] = $warning;
1159        }
1160
1161        $result = array();
1162        $result['assignments'] = $assignments;
1163        $result['warnings'] = $warnings;
1164        return $result;
1165    }
1166
1167    /**
1168     * Creates an assign_user_flags external_single_structure
1169     * @return external_single_structure
1170     * @since  Moodle 2.6
1171     */
1172    private static function assign_user_flags() {
1173        return new external_single_structure(
1174            array (
1175                'assignmentid'    => new external_value(PARAM_INT, 'assignment id'),
1176                'userflags'   => new external_multiple_structure(new external_single_structure(
1177                        array(
1178                            'id'               => new external_value(PARAM_INT, 'user flag id'),
1179                            'userid'           => new external_value(PARAM_INT, 'student id'),
1180                            'locked'           => new external_value(PARAM_INT, 'locked'),
1181                            'mailed'           => new external_value(PARAM_INT, 'mailed'),
1182                            'extensionduedate' => new external_value(PARAM_INT, 'extension due date'),
1183                            'workflowstate'    => new external_value(PARAM_ALPHA, 'marking workflow state', VALUE_OPTIONAL),
1184                            'allocatedmarker'  => new external_value(PARAM_INT, 'allocated marker')
1185                        )
1186                    )
1187                )
1188            )
1189        );
1190    }
1191
1192    /**
1193     * Describes the get_user_flags return value
1194     * @return external_single_structure
1195     * @since  Moodle 2.6
1196     */
1197    public static function get_user_flags_returns() {
1198        return new external_single_structure(
1199            array(
1200                'assignments' => new external_multiple_structure(self::assign_user_flags(), 'list of assign user flag information'),
1201                'warnings'      => new external_warnings('item is always \'assignment\'',
1202                    'when errorcode is 3 then itemid is an assignment id. When errorcode is 1, itemid is a course module id',
1203                    'errorcode can be 3 (no user flags found) or 1 (no permission to get user flags)')
1204            )
1205        );
1206    }
1207
1208    /**
1209     * Describes the parameters for get_user_mappings
1210     * @return external_function_parameters
1211     * @since  Moodle 2.6
1212     */
1213    public static function get_user_mappings_parameters() {
1214        return new external_function_parameters(
1215            array(
1216                'assignmentids' => new external_multiple_structure(
1217                    new external_value(PARAM_INT, 'assignment id'),
1218                    '1 or more assignment ids',
1219                    VALUE_REQUIRED)
1220            )
1221        );
1222    }
1223
1224    /**
1225     * Returns user mapping information from assign_user_mapping for the requested assignment ids
1226     * @param int[] $assignmentids
1227     * @return array of user mapping records for each requested assignment
1228     * @since  Moodle 2.6
1229     */
1230    public static function get_user_mappings($assignmentids) {
1231        global $DB;
1232        $params = self::validate_parameters(self::get_user_mappings_parameters(),
1233                        array('assignmentids' => $assignmentids));
1234
1235        $assignments = array();
1236        $warnings = array();
1237        $requestedassignmentids = $params['assignmentids'];
1238
1239        // Check the user is allowed to get the mappings for the assignments requested.
1240        $placeholders = array();
1241        list($sqlassignmentids, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1242        $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
1243               "WHERE md.name = :modname AND cm.instance ".$sqlassignmentids;
1244        $placeholders['modname'] = 'assign';
1245        $cms = $DB->get_records_sql($sql, $placeholders);
1246        foreach ($cms as $cm) {
1247            try {
1248                $context = context_module::instance($cm->id);
1249                self::validate_context($context);
1250                require_capability('mod/assign:revealidentities', $context);
1251            } catch (Exception $e) {
1252                $requestedassignmentids = array_diff($requestedassignmentids, array($cm->instance));
1253                $warning = array();
1254                $warning['item'] = 'assignment';
1255                $warning['itemid'] = $cm->instance;
1256                $warning['warningcode'] = '1';
1257                $warning['message'] = 'No access rights in module context';
1258                $warnings[] = $warning;
1259            }
1260        }
1261
1262        // Create the query and populate an array of assign_user_mapping records from the recordset results.
1263        if (count ($requestedassignmentids) > 0) {
1264            $placeholders = array();
1265            list($inorequalsql, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1266
1267            $sql = "SELECT aum.id,aum.assignment,aum.userid ".
1268                   "FROM {assign_user_mapping} aum ".
1269                   "WHERE aum.assignment ".$inorequalsql.
1270                   " ORDER BY aum.assignment, aum.id";
1271
1272            $rs = $DB->get_recordset_sql($sql, $placeholders);
1273            $currentassignmentid = null;
1274            $assignment = null;
1275            foreach ($rs as $rd) {
1276                $mapping = array();
1277                $mapping['id'] = $rd->id;
1278                $mapping['userid'] = $rd->userid;
1279
1280                if (is_null($currentassignmentid) || ($rd->assignment != $currentassignmentid )) {
1281                    if (!is_null($assignment)) {
1282                        $assignments[] = $assignment;
1283                    }
1284                    $assignment = array();
1285                    $assignment['assignmentid'] = $rd->assignment;
1286                    $assignment['mappings'] = array();
1287                    $requestedassignmentids = array_diff($requestedassignmentids, array($rd->assignment));
1288                }
1289                $assignment['mappings'][] = $mapping;
1290
1291                $currentassignmentid = $rd->assignment;
1292            }
1293            if (!is_null($assignment)) {
1294                $assignments[] = $assignment;
1295            }
1296            $rs->close();
1297
1298        }
1299
1300        foreach ($requestedassignmentids as $assignmentid) {
1301            $warning = array();
1302            $warning['item'] = 'assignment';
1303            $warning['itemid'] = $assignmentid;
1304            $warning['warningcode'] = '3';
1305            $warning['message'] = 'No mappings found';
1306            $warnings[] = $warning;
1307        }
1308
1309        $result = array();
1310        $result['assignments'] = $assignments;
1311        $result['warnings'] = $warnings;
1312        return $result;
1313    }
1314
1315    /**
1316     * Creates an assign_user_mappings external_single_structure
1317     * @return external_single_structure
1318     * @since  Moodle 2.6
1319     */
1320    private static function assign_user_mappings() {
1321        return new external_single_structure(
1322            array (
1323                'assignmentid'    => new external_value(PARAM_INT, 'assignment id'),
1324                'mappings'   => new external_multiple_structure(new external_single_structure(
1325                        array(
1326                            'id'     => new external_value(PARAM_INT, 'user mapping id'),
1327                            'userid' => new external_value(PARAM_INT, 'student id')
1328                        )
1329                    )
1330                )
1331            )
1332        );
1333    }
1334
1335    /**
1336     * Describes the get_user_mappings return value
1337     * @return external_single_structure
1338     * @since  Moodle 2.6
1339     */
1340    public static function get_user_mappings_returns() {
1341        return new external_single_structure(
1342            array(
1343                'assignments' => new external_multiple_structure(self::assign_user_mappings(), 'list of assign user mapping data'),
1344                'warnings'      => new external_warnings('item is always \'assignment\'',
1345                    'when errorcode is 3 then itemid is an assignment id. When errorcode is 1, itemid is a course module id',
1346                    'errorcode can be 3 (no user mappings found) or 1 (no permission to get user mappings)')
1347            )
1348        );
1349    }
1350
1351    /**
1352     * Describes the parameters for lock_submissions
1353     * @return external_function_parameters
1354     * @since  Moodle 2.6
1355     */
1356    public static function lock_submissions_parameters() {
1357        return new external_function_parameters(
1358            array(
1359                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1360                'userids' => new external_multiple_structure(
1361                    new external_value(PARAM_INT, 'user id'),
1362                    '1 or more user ids',
1363                    VALUE_REQUIRED),
1364            )
1365        );
1366    }
1367
1368    /**
1369     * Locks (prevent updates to) submissions in this assignment.
1370     *
1371     * @param int $assignmentid The id of the assignment
1372     * @param array $userids Array of user ids to lock
1373     * @return array of warnings for each submission that could not be locked.
1374     * @since Moodle 2.6
1375     */
1376    public static function lock_submissions($assignmentid, $userids) {
1377        global $CFG;
1378
1379        $params = self::validate_parameters(self::lock_submissions_parameters(),
1380                        array('assignmentid' => $assignmentid,
1381                              'userids' => $userids));
1382
1383        list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1384
1385        $warnings = array();
1386        foreach ($params['userids'] as $userid) {
1387            if (!$assignment->lock_submission($userid)) {
1388                $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'];
1389                $warnings[] = self::generate_warning($params['assignmentid'],
1390                                                     'couldnotlock',
1391                                                     $detail);
1392            }
1393        }
1394
1395        return $warnings;
1396    }
1397
1398    /**
1399     * Describes the return value for lock_submissions
1400     *
1401     * @return external_single_structure
1402     * @since Moodle 2.6
1403     */
1404    public static function lock_submissions_returns() {
1405        return new external_warnings();
1406    }
1407
1408    /**
1409     * Describes the parameters for revert_submissions_to_draft
1410     * @return external_function_parameters
1411     * @since  Moodle 2.6
1412     */
1413    public static function revert_submissions_to_draft_parameters() {
1414        return new external_function_parameters(
1415            array(
1416                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1417                'userids' => new external_multiple_structure(
1418                    new external_value(PARAM_INT, 'user id'),
1419                    '1 or more user ids',
1420                    VALUE_REQUIRED),
1421            )
1422        );
1423    }
1424
1425    /**
1426     * Reverts a list of user submissions to draft for a single assignment.
1427     *
1428     * @param int $assignmentid The id of the assignment
1429     * @param array $userids Array of user ids to revert
1430     * @return array of warnings for each submission that could not be reverted.
1431     * @since Moodle 2.6
1432     */
1433    public static function revert_submissions_to_draft($assignmentid, $userids) {
1434        global $CFG;
1435
1436        $params = self::validate_parameters(self::revert_submissions_to_draft_parameters(),
1437                        array('assignmentid' => $assignmentid,
1438                              'userids' => $userids));
1439
1440        list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1441
1442        $warnings = array();
1443        foreach ($params['userids'] as $userid) {
1444            if (!$assignment->revert_to_draft($userid)) {
1445                $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'];
1446                $warnings[] = self::generate_warning($params['assignmentid'],
1447                                                     'couldnotrevert',
1448                                                     $detail);
1449            }
1450        }
1451
1452        return $warnings;
1453    }
1454
1455    /**
1456     * Describes the return value for revert_submissions_to_draft
1457     *
1458     * @return external_single_structure
1459     * @since Moodle 2.6
1460     */
1461    public static function revert_submissions_to_draft_returns() {
1462        return new external_warnings();
1463    }
1464
1465    /**
1466     * Describes the parameters for unlock_submissions
1467     * @return external_function_parameters
1468     * @since  Moodle 2.6
1469     */
1470    public static function unlock_submissions_parameters() {
1471        return new external_function_parameters(
1472            array(
1473                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1474                'userids' => new external_multiple_structure(
1475                    new external_value(PARAM_INT, 'user id'),
1476                    '1 or more user ids',
1477                    VALUE_REQUIRED),
1478            )
1479        );
1480    }
1481
1482    /**
1483     * Locks (prevent updates to) submissions in this assignment.
1484     *
1485     * @param int $assignmentid The id of the assignment
1486     * @param array $userids Array of user ids to lock
1487     * @return array of warnings for each submission that could not be locked.
1488     * @since Moodle 2.6
1489     */
1490    public static function unlock_submissions($assignmentid, $userids) {
1491        global $CFG;
1492
1493        $params = self::validate_parameters(self::unlock_submissions_parameters(),
1494                        array('assignmentid' => $assignmentid,
1495                              'userids' => $userids));
1496
1497        list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1498
1499        $warnings = array();
1500        foreach ($params['userids'] as $userid) {
1501            if (!$assignment->unlock_submission($userid)) {
1502                $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'];
1503                $warnings[] = self::generate_warning($params['assignmentid'],
1504                                                     'couldnotunlock',
1505                                                     $detail);
1506            }
1507        }
1508
1509        return $warnings;
1510    }
1511
1512    /**
1513     * Describes the return value for unlock_submissions
1514     *
1515     * @return external_single_structure
1516     * @since Moodle 2.6
1517     */
1518    public static function unlock_submissions_returns() {
1519        return new external_warnings();
1520    }
1521
1522    /**
1523     * Describes the parameters for submit_grading_form webservice.
1524     * @return external_function_parameters
1525     * @since  Moodle 3.1
1526     */
1527    public static function submit_grading_form_parameters() {
1528        return new external_function_parameters(
1529            array(
1530                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1531                'userid' => new external_value(PARAM_INT, 'The user id the submission belongs to'),
1532                'jsonformdata' => new external_value(PARAM_RAW, 'The data from the grading form, encoded as a json array')
1533            )
1534        );
1535    }
1536
1537    /**
1538     * Submit the logged in users assignment for grading.
1539     *
1540     * @param int $assignmentid The id of the assignment
1541     * @param int $userid The id of the user the submission belongs to.
1542     * @param string $jsonformdata The data from the form, encoded as a json array.
1543     * @return array of warnings to indicate any errors.
1544     * @since Moodle 3.1
1545     */
1546    public static function submit_grading_form($assignmentid, $userid, $jsonformdata) {
1547        global $CFG, $USER;
1548
1549        require_once($CFG->dirroot . '/mod/assign/locallib.php');
1550        require_once($CFG->dirroot . '/mod/assign/gradeform.php');
1551
1552        $params = self::validate_parameters(self::submit_grading_form_parameters(),
1553                                            array(
1554                                                'assignmentid' => $assignmentid,
1555                                                'userid' => $userid,
1556                                                'jsonformdata' => $jsonformdata
1557                                            ));
1558
1559        list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1560
1561        $serialiseddata = json_decode($params['jsonformdata']);
1562
1563        $data = array();
1564        parse_str($serialiseddata, $data);
1565
1566        $warnings = array();
1567
1568        $options = array(
1569            'userid' => $params['userid'],
1570            'attemptnumber' => $data['attemptnumber'],
1571            'rownum' => 0,
1572            'gradingpanel' => true
1573        );
1574
1575        if (WS_SERVER) {
1576            // Assume form submission if coming from WS.
1577            $USER->ignoresesskey = true;
1578            $data['_qf__mod_assign_grade_form_'.$params['userid']] = 1;
1579        }
1580
1581        $customdata = (object) $data;
1582        $formparams = array($assignment, $customdata, $options);
1583
1584        // Data is injected into the form by the last param for the constructor.
1585        $mform = new mod_assign_grade_form(null, $formparams, 'post', '', null, true, $data);
1586        $validateddata = $mform->get_data();
1587
1588        if ($validateddata) {
1589            $assignment->save_grade($params['userid'], $validateddata);
1590        } else {
1591            $warnings[] = self::generate_warning($params['assignmentid'],
1592                                                 'couldnotsavegrade',
1593                                                 'Form validation failed.');
1594        }
1595
1596
1597        return $warnings;
1598    }
1599
1600    /**
1601     * Describes the return for submit_grading_form
1602     * @return external_function_parameters
1603     * @since  Moodle 3.1
1604     */
1605    public static function submit_grading_form_returns() {
1606        return new external_warnings();
1607    }
1608
1609    /**
1610     * Describes the parameters for submit_for_grading
1611     * @return external_function_parameters
1612     * @since  Moodle 2.6
1613     */
1614    public static function submit_for_grading_parameters() {
1615        return new external_function_parameters(
1616            array(
1617                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1618                'acceptsubmissionstatement' => new external_value(PARAM_BOOL, 'Accept the assignment submission statement')
1619            )
1620        );
1621    }
1622
1623    /**
1624     * Submit the logged in users assignment for grading.
1625     *
1626     * @param int $assignmentid The id of the assignment
1627     * @return array of warnings to indicate any errors.
1628     * @since Moodle 2.6
1629     */
1630    public static function submit_for_grading($assignmentid, $acceptsubmissionstatement) {
1631        global $CFG, $USER;
1632
1633        $params = self::validate_parameters(self::submit_for_grading_parameters(),
1634                                            array('assignmentid' => $assignmentid,
1635                                                  'acceptsubmissionstatement' => $acceptsubmissionstatement));
1636
1637        list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1638
1639        $warnings = array();
1640        $data = new stdClass();
1641        $data->submissionstatement = $params['acceptsubmissionstatement'];
1642        $notices = array();
1643
1644        if (!$assignment->submit_for_grading($data, $notices)) {
1645            $detail = 'User id: ' . $USER->id . ', Assignment id: ' . $params['assignmentid'] . ' Notices:' . implode(', ', $notices);
1646            $warnings[] = self::generate_warning($params['assignmentid'],
1647                                                 'couldnotsubmitforgrading',
1648                                                 $detail);
1649        }
1650
1651        return $warnings;
1652    }
1653
1654    /**
1655     * Describes the return value for submit_for_grading
1656     *
1657     * @return external_single_structure
1658     * @since Moodle 2.6
1659     */
1660    public static function submit_for_grading_returns() {
1661        return new external_warnings();
1662    }
1663
1664    /**
1665     * Describes the parameters for save_user_extensions
1666     * @return external_function_parameters
1667     * @since  Moodle 2.6
1668     */
1669    public static function save_user_extensions_parameters() {
1670        return new external_function_parameters(
1671            array(
1672                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1673                'userids' => new external_multiple_structure(
1674                    new external_value(PARAM_INT, 'user id'),
1675                    '1 or more user ids',
1676                    VALUE_REQUIRED),
1677                'dates' => new external_multiple_structure(
1678                    new external_value(PARAM_INT, 'dates'),
1679                    '1 or more extension dates (timestamp)',
1680                    VALUE_REQUIRED),
1681            )
1682        );
1683    }
1684
1685    /**
1686     * Grant extension dates to students for an assignment.
1687     *
1688     * @param int $assignmentid The id of the assignment
1689     * @param array $userids Array of user ids to grant extensions to
1690     * @param array $dates Array of extension dates
1691     * @return array of warnings for each extension date that could not be granted
1692     * @since Moodle 2.6
1693     */
1694    public static function save_user_extensions($assignmentid, $userids, $dates) {
1695        global $CFG;
1696
1697        $params = self::validate_parameters(self::save_user_extensions_parameters(),
1698                        array('assignmentid' => $assignmentid,
1699                              'userids' => $userids,
1700                              'dates' => $dates));
1701
1702        if (count($params['userids']) != count($params['dates'])) {
1703            $detail = 'Length of userids and dates parameters differ.';
1704            $warnings[] = self::generate_warning($params['assignmentid'],
1705                                                 'invalidparameters',
1706                                                 $detail);
1707
1708            return $warnings;
1709        }
1710
1711        list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1712
1713        $warnings = array();
1714        foreach ($params['userids'] as $idx => $userid) {
1715            $duedate = $params['dates'][$idx];
1716            if (!$assignment->save_user_extension($userid, $duedate)) {
1717                $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'] . ', Extension date: ' . $duedate;
1718                $warnings[] = self::generate_warning($params['assignmentid'],
1719                                                     'couldnotgrantextensions',
1720                                                     $detail);
1721            }
1722        }
1723
1724        return $warnings;
1725    }
1726
1727    /**
1728     * Describes the return value for save_user_extensions
1729     *
1730     * @return external_single_structure
1731     * @since Moodle 2.6
1732     */
1733    public static function save_user_extensions_returns() {
1734        return new external_warnings();
1735    }
1736
1737    /**
1738     * Describes the parameters for reveal_identities
1739     * @return external_function_parameters
1740     * @since  Moodle 2.6
1741     */
1742    public static function reveal_identities_parameters() {
1743        return new external_function_parameters(
1744            array(
1745                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on')
1746            )
1747        );
1748    }
1749
1750    /**
1751     * Reveal the identities of anonymous students to markers for a single assignment.
1752     *
1753     * @param int $assignmentid The id of the assignment
1754     * @return array of warnings to indicate any errors.
1755     * @since Moodle 2.6
1756     */
1757    public static function reveal_identities($assignmentid) {
1758        global $CFG, $USER;
1759
1760        $params = self::validate_parameters(self::reveal_identities_parameters(),
1761                                            array('assignmentid' => $assignmentid));
1762
1763        list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1764
1765        $warnings = array();
1766        if (!$assignment->reveal_identities()) {
1767            $detail = 'User id: ' . $USER->id . ', Assignment id: ' . $params['assignmentid'];
1768            $warnings[] = self::generate_warning($params['assignmentid'],
1769                                                 'couldnotrevealidentities',
1770                                                 $detail);
1771        }
1772
1773        return $warnings;
1774    }
1775
1776    /**
1777     * Describes the return value for reveal_identities
1778     *
1779     * @return external_single_structure
1780     * @since Moodle 2.6
1781     */
1782    public static function reveal_identities_returns() {
1783        return new external_warnings();
1784    }
1785
1786    /**
1787     * Describes the parameters for save_submission
1788     * @return external_function_parameters
1789     * @since  Moodle 2.6
1790     */
1791    public static function save_submission_parameters() {
1792        global $CFG;
1793        $instance = new assign(null, null, null);
1794        $pluginsubmissionparams = array();
1795
1796        foreach ($instance->get_submission_plugins() as $plugin) {
1797            if ($plugin->is_visible()) {
1798                $pluginparams = $plugin->get_external_parameters();
1799                if (!empty($pluginparams)) {
1800                    $pluginsubmissionparams = array_merge($pluginsubmissionparams, $pluginparams);
1801                }
1802            }
1803        }
1804
1805        return new external_function_parameters(
1806            array(
1807                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1808                'plugindata' => new external_single_structure(
1809                    $pluginsubmissionparams
1810                )
1811            )
1812        );
1813    }
1814
1815    /**
1816     * Save a student submission for a single assignment
1817     *
1818     * @param int $assignmentid The id of the assignment
1819     * @param array $plugindata - The submitted data for plugins
1820     * @return array of warnings to indicate any errors
1821     * @since Moodle 2.6
1822     */
1823    public static function save_submission($assignmentid, $plugindata) {
1824        global $CFG, $USER;
1825
1826        $params = self::validate_parameters(self::save_submission_parameters(),
1827                                            array('assignmentid' => $assignmentid,
1828                                                  'plugindata' => $plugindata));
1829
1830        list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1831
1832        $notices = array();
1833
1834        $assignment->update_effective_access($USER->id);
1835        if (!$assignment->submissions_open($USER->id)) {
1836            $notices[] = get_string('duedatereached', 'assign');
1837        } else {
1838            $submissiondata = (object)$params['plugindata'];
1839            $assignment->save_submission($submissiondata, $notices);
1840        }
1841
1842        $warnings = array();
1843        foreach ($notices as $notice) {
1844            $warnings[] = self::generate_warning($params['assignmentid'],
1845                                                 'couldnotsavesubmission',
1846                                                 $notice);
1847        }
1848
1849        return $warnings;
1850    }
1851
1852    /**
1853     * Describes the return value for save_submission
1854     *
1855     * @return external_single_structure
1856     * @since Moodle 2.6
1857     */
1858    public static function save_submission_returns() {
1859        return new external_warnings();
1860    }
1861
1862    /**
1863     * Describes the parameters for save_grade
1864     * @return external_function_parameters
1865     * @since  Moodle 2.6
1866     */
1867    public static function save_grade_parameters() {
1868        global $CFG;
1869        require_once("$CFG->dirroot/grade/grading/lib.php");
1870        $instance = new assign(null, null, null);
1871        $pluginfeedbackparams = array();
1872
1873        foreach ($instance->get_feedback_plugins() as $plugin) {
1874            if ($plugin->is_visible()) {
1875                $pluginparams = $plugin->get_external_parameters();
1876                if (!empty($pluginparams)) {
1877                    $pluginfeedbackparams = array_merge($pluginfeedbackparams, $pluginparams);
1878                }
1879            }
1880        }
1881
1882        $advancedgradingdata = array();
1883        $methods = array_keys(grading_manager::available_methods(false));
1884        foreach ($methods as $method) {
1885            require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
1886            $details  = call_user_func('gradingform_'.$method.'_controller::get_external_instance_filling_details');
1887            if (!empty($details)) {
1888                $items = array();
1889                foreach ($details as $key => $value) {
1890                    $value->required = VALUE_OPTIONAL;
1891                    unset($value->content->keys['id']);
1892                    $items[$key] = new external_multiple_structure (new external_single_structure(
1893                        array(
1894                            'criterionid' => new external_value(PARAM_INT, 'criterion id'),
1895                            'fillings' => $value
1896                        )
1897                    ));
1898                }
1899                $advancedgradingdata[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL);
1900            }
1901        }
1902
1903        return new external_function_parameters(
1904            array(
1905                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1906                'userid' => new external_value(PARAM_INT, 'The student id to operate on'),
1907                'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user. Ignored if advanced grading used'),
1908                'attemptnumber' => new external_value(PARAM_INT, 'The attempt number (-1 means latest attempt)'),
1909                'addattempt' => new external_value(PARAM_BOOL, 'Allow another attempt if the attempt reopen method is manual'),
1910                'workflowstate' => new external_value(PARAM_ALPHA, 'The next marking workflow state'),
1911                'applytoall' => new external_value(PARAM_BOOL, 'If true, this grade will be applied ' .
1912                                                               'to all members ' .
1913                                                               'of the group (for group assignments).'),
1914                'plugindata' => new external_single_structure($pluginfeedbackparams, 'plugin data', VALUE_DEFAULT, array()),
1915                'advancedgradingdata' => new external_single_structure($advancedgradingdata, 'advanced grading data',
1916                                                                       VALUE_DEFAULT, array())
1917            )
1918        );
1919    }
1920
1921    /**
1922     * Save a student grade for a single assignment.
1923     *
1924     * @param int $assignmentid The id of the assignment
1925     * @param int $userid The id of the user
1926     * @param float $grade The grade (ignored if the assignment uses advanced grading)
1927     * @param int $attemptnumber The attempt number
1928     * @param bool $addattempt Allow another attempt
1929     * @param string $workflowstate New workflow state
1930     * @param bool $applytoall Apply the grade to all members of the group
1931     * @param array $plugindata Custom data used by plugins
1932     * @param array $advancedgradingdata Advanced grading data
1933     * @return null
1934     * @since Moodle 2.6
1935     */
1936    public static function save_grade($assignmentid,
1937                                      $userid,
1938                                      $grade,
1939                                      $attemptnumber,
1940                                      $addattempt,
1941                                      $workflowstate,
1942                                      $applytoall,
1943                                      $plugindata = array(),
1944                                      $advancedgradingdata = array()) {
1945        global $CFG, $USER;
1946
1947        $params = self::validate_parameters(self::save_grade_parameters(),
1948                                            array('assignmentid' => $assignmentid,
1949                                                  'userid' => $userid,
1950                                                  'grade' => $grade,
1951                                                  'attemptnumber' => $attemptnumber,
1952                                                  'workflowstate' => $workflowstate,
1953                                                  'addattempt' => $addattempt,
1954                                                  'applytoall' => $applytoall,
1955                                                  'plugindata' => $plugindata,
1956                                                  'advancedgradingdata' => $advancedgradingdata));
1957
1958        list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1959
1960        $gradedata = (object)$params['plugindata'];
1961
1962        $gradedata->addattempt = $params['addattempt'];
1963        $gradedata->attemptnumber = $params['attemptnumber'];
1964        $gradedata->workflowstate = $params['workflowstate'];
1965        $gradedata->applytoall = $params['applytoall'];
1966        $gradedata->grade = $params['grade'];
1967
1968        if (!empty($params['advancedgradingdata'])) {
1969            $advancedgrading = array();
1970            $criteria = reset($params['advancedgradingdata']);
1971            foreach ($criteria as $key => $criterion) {
1972                $details = array();
1973                foreach ($criterion as $value) {
1974                    foreach ($value['fillings'] as $filling) {
1975                        $details[$value['criterionid']] = $filling;
1976                    }
1977                }
1978                $advancedgrading[$key] = $details;
1979            }
1980            $gradedata->advancedgrading = $advancedgrading;
1981        }
1982
1983        $assignment->save_grade($params['userid'], $gradedata);
1984
1985        return null;
1986    }
1987
1988    /**
1989     * Describes the return value for save_grade
1990     *
1991     * @return external_single_structure
1992     * @since Moodle 2.6
1993     */
1994    public static function save_grade_returns() {
1995        return null;
1996    }
1997
1998    /**
1999     * Describes the parameters for save_grades
2000     * @return external_function_parameters
2001     * @since  Moodle 2.7
2002     */
2003    public static function save_grades_parameters() {
2004        global $CFG;
2005        require_once("$CFG->dirroot/grade/grading/lib.php");
2006        $instance = new assign(null, null, null);
2007        $pluginfeedbackparams = array();
2008
2009        foreach ($instance->get_feedback_plugins() as $plugin) {
2010            if ($plugin->is_visible()) {
2011                $pluginparams = $plugin->get_external_parameters();
2012                if (!empty($pluginparams)) {
2013                    $pluginfeedbackparams = array_merge($pluginfeedbackparams, $pluginparams);
2014                }
2015            }
2016        }
2017
2018        $advancedgradingdata = array();
2019        $methods = array_keys(grading_manager::available_methods(false));
2020        foreach ($methods as $method) {
2021            require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
2022            $details  = call_user_func('gradingform_'.$method.'_controller::get_external_instance_filling_details');
2023            if (!empty($details)) {
2024                $items = array();
2025                foreach ($details as $key => $value) {
2026                    $value->required = VALUE_OPTIONAL;
2027                    unset($value->content->keys['id']);
2028                    $items[$key] = new external_multiple_structure (new external_single_structure(
2029                        array(
2030                            'criterionid' => new external_value(PARAM_INT, 'criterion id'),
2031                            'fillings' => $value
2032                        )
2033                    ));
2034                }
2035                $advancedgradingdata[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL);
2036            }
2037        }
2038
2039        return new external_function_parameters(
2040            array(
2041                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
2042                'applytoall' => new external_value(PARAM_BOOL, 'If true, this grade will be applied ' .
2043                                                               'to all members ' .
2044                                                               'of the group (for group assignments).'),
2045                'grades' => new external_multiple_structure(
2046                    new external_single_structure(
2047                        array (
2048                            'userid' => new external_value(PARAM_INT, 'The student id to operate on'),
2049                            'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user. '.
2050                                                                       'Ignored if advanced grading used'),
2051                            'attemptnumber' => new external_value(PARAM_INT, 'The attempt number (-1 means latest attempt)'),
2052                            'addattempt' => new external_value(PARAM_BOOL, 'Allow another attempt if manual attempt reopen method'),
2053                            'workflowstate' => new external_value(PARAM_ALPHA, 'The next marking workflow state'),
2054                            'plugindata' => new external_single_structure($pluginfeedbackparams, 'plugin data',
2055                                                                          VALUE_DEFAULT, array()),
2056                            'advancedgradingdata' => new external_single_structure($advancedgradingdata, 'advanced grading data',
2057                                                                                   VALUE_DEFAULT, array())
2058                        )
2059                    )
2060                )
2061            )
2062        );
2063    }
2064
2065    /**
2066     * Save multiple student grades for a single assignment.
2067     *
2068     * @param int $assignmentid The id of the assignment
2069     * @param boolean $applytoall If set to true and this is a team assignment,
2070     * apply the grade to all members of the group
2071     * @param array $grades grade data for one or more students that includes
2072     *                  userid - The id of the student being graded
2073     *                  grade - The grade (ignored if the assignment uses advanced grading)
2074     *                  attemptnumber - The attempt number
2075     *                  addattempt - Allow another attempt
2076     *                  workflowstate - New workflow state
2077     *                  plugindata - Custom data used by plugins
2078     *                  advancedgradingdata - Optional Advanced grading data
2079     * @throws invalid_parameter_exception if multiple grades are supplied for
2080     * a team assignment that has $applytoall set to true
2081     * @return null
2082     * @since Moodle 2.7
2083     */
2084    public static function save_grades($assignmentid, $applytoall = false, $grades) {
2085        global $CFG, $USER;
2086
2087        $params = self::validate_parameters(self::save_grades_parameters(),
2088                                            array('assignmentid' => $assignmentid,
2089                                                  'applytoall' => $applytoall,
2090                                                  'grades' => $grades));
2091
2092        list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
2093
2094        if ($assignment->get_instance()->teamsubmission && $params['applytoall']) {
2095            // Check that only 1 user per submission group is provided.
2096            $groupids = array();
2097            foreach ($params['grades'] as $gradeinfo) {
2098                $group = $assignment->get_submission_group($gradeinfo['userid']);
2099                if (in_array($group->id, $groupids)) {
2100                    throw new invalid_parameter_exception('Multiple grades for the same team have been supplied '
2101                                                          .' this is not permitted when the applytoall flag is set');
2102                } else {
2103                    $groupids[] = $group->id;
2104                }
2105            }
2106        }
2107
2108        foreach ($params['grades'] as $gradeinfo) {
2109            $gradedata = (object)$gradeinfo['plugindata'];
2110            $gradedata->addattempt = $gradeinfo['addattempt'];
2111            $gradedata->attemptnumber = $gradeinfo['attemptnumber'];
2112            $gradedata->workflowstate = $gradeinfo['workflowstate'];
2113            $gradedata->applytoall = $params['applytoall'];
2114            $gradedata->grade = $gradeinfo['grade'];
2115
2116            if (!empty($gradeinfo['advancedgradingdata'])) {
2117                $advancedgrading = array();
2118                $criteria = reset($gradeinfo['advancedgradingdata']);
2119                foreach ($criteria as $key => $criterion) {
2120                    $details = array();
2121                    foreach ($criterion as $value) {
2122                        foreach ($value['fillings'] as $filling) {
2123                            $details[$value['criterionid']] = $filling;
2124                        }
2125                    }
2126                    $advancedgrading[$key] = $details;
2127                }
2128                $gradedata->advancedgrading = $advancedgrading;
2129            }
2130            $assignment->save_grade($gradeinfo['userid'], $gradedata);
2131        }
2132
2133        return null;
2134    }
2135
2136    /**
2137     * Describes the return value for save_grades
2138     *
2139     * @return external_single_structure
2140     * @since Moodle 2.7
2141     */
2142    public static function save_grades_returns() {
2143        return null;
2144    }
2145
2146    /**
2147     * Describes the parameters for copy_previous_attempt
2148     * @return external_function_parameters
2149     * @since  Moodle 2.6
2150     */
2151    public static function copy_previous_attempt_parameters() {
2152        return new external_function_parameters(
2153            array(
2154                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
2155            )
2156        );
2157    }
2158
2159    /**
2160     * Copy a students previous attempt to a new attempt.
2161     *
2162     * @param int $assignmentid
2163     * @return array of warnings to indicate any errors.
2164     * @since Moodle 2.6
2165     */
2166    public static function copy_previous_attempt($assignmentid) {
2167
2168        $params = self::validate_parameters(self::copy_previous_attempt_parameters(),
2169                                            array('assignmentid' => $assignmentid));
2170
2171        list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
2172
2173        $notices = array();
2174
2175        $assignment->copy_previous_attempt($notices);
2176
2177        $warnings = array();
2178        foreach ($notices as $notice) {
2179            $warnings[] = self::generate_warning($assignmentid,
2180                                                 'couldnotcopyprevioussubmission',
2181                                                 $notice);
2182        }
2183
2184        return $warnings;
2185    }
2186
2187    /**
2188     * Describes the return value for save_submission
2189     *
2190     * @return external_single_structure
2191     * @since Moodle 2.6
2192     */
2193    public static function copy_previous_attempt_returns() {
2194        return new external_warnings();
2195    }
2196
2197    /**
2198     * Returns description of method parameters
2199     *
2200     * @return external_function_parameters
2201     * @since Moodle 3.0
2202     */
2203    public static function view_grading_table_parameters() {
2204        return new external_function_parameters(
2205            array(
2206                'assignid' => new external_value(PARAM_INT, 'assign instance id')
2207            )
2208        );
2209    }
2210
2211    /**
2212     * Trigger the grading_table_viewed event.
2213     *
2214     * @param int $assignid the assign instance id
2215     * @return array of warnings and status result
2216     * @since Moodle 3.0
2217     * @throws moodle_exception
2218     */
2219    public static function view_grading_table($assignid) {
2220
2221        $params = self::validate_parameters(self::view_grading_table_parameters(),
2222                                            array(
2223                                                'assignid' => $assignid
2224                                            ));
2225        $warnings = array();
2226
2227        list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2228
2229        $assign->require_view_grades();
2230        \mod_assign\event\grading_table_viewed::create_from_assign($assign)->trigger();
2231
2232        $result = array();
2233        $result['status'] = true;
2234        $result['warnings'] = $warnings;
2235        return $result;
2236    }
2237
2238    /**
2239     * Returns description of method result value
2240     *
2241     * @return external_description
2242     * @since Moodle 3.0
2243     */
2244    public static function view_grading_table_returns() {
2245        return new external_single_structure(
2246            array(
2247                'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2248                'warnings' => new external_warnings()
2249            )
2250        );
2251    }
2252
2253    /**
2254     * Describes the parameters for view_submission_status.
2255     *
2256     * @return external_function_parameters
2257     * @since Moodle 3.1
2258     */
2259    public static function view_submission_status_parameters() {
2260        return new external_function_parameters (
2261            array(
2262                'assignid' => new external_value(PARAM_INT, 'assign instance id'),
2263            )
2264        );
2265    }
2266
2267    /**
2268     * Trigger the submission status viewed event.
2269     *
2270     * @param int $assignid assign instance id
2271     * @return array of warnings and status result
2272     * @since Moodle 3.1
2273     */
2274    public static function view_submission_status($assignid) {
2275
2276        $warnings = array();
2277        $params = array(
2278            'assignid' => $assignid,
2279        );
2280        $params = self::validate_parameters(self::view_submission_status_parameters(), $params);
2281
2282        list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2283
2284        \mod_assign\event\submission_status_viewed::create_from_assign($assign)->trigger();
2285
2286        $result = array();
2287        $result['status'] = true;
2288        $result['warnings'] = $warnings;
2289        return $result;
2290    }
2291
2292    /**
2293     * Describes the view_submission_status return value.
2294     *
2295     * @return external_single_structure
2296     * @since Moodle 3.1
2297     */
2298    public static function view_submission_status_returns() {
2299        return new external_single_structure(
2300            array(
2301                'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2302                'warnings' => new external_warnings(),
2303            )
2304        );
2305    }
2306
2307    /**
2308     * Describes the parameters for get_submission_status.
2309     *
2310     * @return external_function_parameters
2311     * @since Moodle 3.1
2312     */
2313    public static function get_submission_status_parameters() {
2314        return new external_function_parameters (
2315            array(
2316                'assignid' => new external_value(PARAM_INT, 'assignment instance id'),
2317                'userid' => new external_value(PARAM_INT, 'user id (empty for current user)', VALUE_DEFAULT, 0),
2318                'groupid' => new external_value(PARAM_INT, 'filter by users in group (used for generating the grading summary).
2319                    0 for all groups information, any other empty value will calculate currrent group.', VALUE_DEFAULT, 0),
2320            )
2321        );
2322    }
2323
2324    /**
2325     * Returns information about an assignment submission status for a given user.
2326     *
2327     * @param int $assignid assignment instance id
2328     * @param int $userid user id (empty for current user)
2329     * @param int $groupid filter by users in group id (used for generating the grading summary). Use 0 for all groups information.
2330     * @return array of warnings and grading, status, feedback and previous attempts information
2331     * @since Moodle 3.1
2332     * @throws required_capability_exception
2333     */
2334    public static function get_submission_status($assignid, $userid = 0, $groupid = 0) {
2335        global $USER;
2336
2337        $warnings = array();
2338
2339        $params = array(
2340            'assignid' => $assignid,
2341            'userid' => $userid,
2342            'groupid' => $groupid,
2343        );
2344        $params = self::validate_parameters(self::get_submission_status_parameters(), $params);
2345
2346        list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2347
2348        // Default value for userid.
2349        if (empty($params['userid'])) {
2350            $params['userid'] = $USER->id;
2351        }
2352        $user = core_user::get_user($params['userid'], '*', MUST_EXIST);
2353        core_user::require_active_user($user);
2354
2355        if (!$assign->can_view_submission($user->id)) {
2356            throw new required_capability_exception($context, 'mod/assign:viewgrades', 'nopermission', '');
2357        }
2358
2359        $assign->update_effective_access($user->id);
2360
2361        $gradingsummary = $lastattempt = $feedback = $previousattempts = null;
2362
2363        // Get the renderable since it contais all the info we need.
2364        if (!empty($params['groupid'])) {
2365            $groupid = $params['groupid'];
2366            // Determine is the group is visible to user.
2367            if (!groups_group_visible($groupid, $course, $cm)) {
2368                throw new moodle_exception('notingroup');
2369            }
2370        } else {
2371            // A null group means that following functions will calculate the current group.
2372            // A groupid set to 0 means all groups.
2373            $groupid = ($params['groupid'] == 0) ? 0 : null;
2374        }
2375        if ($assign->can_view_grades($groupid)) {
2376            $gradingsummary = $assign->get_assign_grading_summary_renderable($groupid);
2377        }
2378
2379        // Retrieve the rest of the renderable objects.
2380        if (has_capability('mod/assign:viewownsubmissionsummary', $context, $user, false)) {
2381            // The user can view the submission summary.
2382            $lastattempt = $assign->get_assign_submission_status_renderable($user, true);
2383        }
2384
2385        $feedback = $assign->get_assign_feedback_status_renderable($user);
2386
2387        $previousattempts = $assign->get_assign_attempt_history_renderable($user);
2388
2389        // Now, build the result.
2390        $result = array();
2391
2392        // First of all, grading summary, this is suitable for teachers/managers.
2393        if ($gradingsummary) {
2394            $result['gradingsummary'] = $gradingsummary;
2395        }
2396        // Show the grader's identity if 'Hide Grader' is disabled or has the 'Show Hidden Grader' capability.
2397        $showgradername = (has_capability('mod/assign:showhiddengrader', $context) or
2398            !$assign->is_hidden_grader());
2399
2400        // Did we submit anything?
2401        if ($lastattempt) {
2402            $submissionplugins = $assign->get_submission_plugins();
2403
2404            if (empty($lastattempt->submission)) {
2405                unset($lastattempt->submission);
2406            } else {
2407                $lastattempt->submission->plugins = self::get_plugins_data($assign, $submissionplugins, $lastattempt->submission);
2408            }
2409
2410            if (empty($lastattempt->teamsubmission)) {
2411                unset($lastattempt->teamsubmission);
2412            } else {
2413                $lastattempt->teamsubmission->plugins = self::get_plugins_data($assign, $submissionplugins,
2414                                                                                $lastattempt->teamsubmission);
2415            }
2416
2417            // We need to change the type of some of the structures retrieved from the renderable.
2418            if (!empty($lastattempt->submissiongroup)) {
2419                $lastattempt->submissiongroup = $lastattempt->submissiongroup->id;
2420            } else {
2421                unset($lastattempt->submissiongroup);
2422            }
2423
2424            if (!empty($lastattempt->usergroups)) {
2425                $lastattempt->usergroups = array_keys($lastattempt->usergroups);
2426            }
2427            // We cannot use array_keys here.
2428            if (!empty($lastattempt->submissiongroupmemberswhoneedtosubmit)) {
2429                $lastattempt->submissiongroupmemberswhoneedtosubmit = array_map(
2430                                                                            function($e){
2431                                                                                return $e->id;
2432                                                                            },
2433                                                                            $lastattempt->submissiongroupmemberswhoneedtosubmit);
2434            }
2435
2436            // Can edit its own submission?
2437            $lastattempt->caneditowner = has_capability('mod/assign:submit', $context, $user, false)
2438                && $assign->submissions_open($user->id) && $assign->is_any_submission_plugin_enabled();
2439
2440            $result['lastattempt'] = $lastattempt;
2441        }
2442
2443        // The feedback for our latest submission.
2444        if ($feedback) {
2445            if ($feedback->grade) {
2446                if (!$showgradername) {
2447                    $feedback->grade->grader = -1;
2448                }
2449                $feedbackplugins = $assign->get_feedback_plugins();
2450                $feedback->plugins = self::get_plugins_data($assign, $feedbackplugins, $feedback->grade);
2451            } else {
2452                unset($feedback->plugins);
2453                unset($feedback->grade);
2454            }
2455
2456            $result['feedback'] = $feedback;
2457        }
2458
2459        // Retrieve only previous attempts.
2460        if ($previousattempts and count($previousattempts->submissions) > 1) {
2461            // Don't show the last one because it is the current submission.
2462            array_pop($previousattempts->submissions);
2463
2464            // Show newest to oldest.
2465            $previousattempts->submissions = array_reverse($previousattempts->submissions);
2466
2467            foreach ($previousattempts->submissions as $i => $submission) {
2468                $attempt = array();
2469
2470                $grade = null;
2471                foreach ($previousattempts->grades as $onegrade) {
2472                    if ($onegrade->attemptnumber == $submission->attemptnumber) {
2473                        $grade = $onegrade;
2474                        break;
2475                    }
2476                }
2477
2478                $attempt['attemptnumber'] = $submission->attemptnumber;
2479
2480                if ($submission) {
2481                    $submission->plugins = self::get_plugins_data($assign, $previousattempts->submissionplugins, $submission);
2482                    $attempt['submission'] = $submission;
2483                }
2484
2485                if ($grade) {
2486                    // From object to id.
2487                    if (!$showgradername) {
2488                        $grade->grader = -1;
2489                    } else {
2490                        $grade->grader = $grade->grader->id;
2491                    }
2492
2493                    $feedbackplugins = self::get_plugins_data($assign, $previousattempts->feedbackplugins, $grade);
2494
2495                    $attempt['grade'] = $grade;
2496                    $attempt['feedbackplugins'] = $feedbackplugins;
2497                }
2498                $result['previousattempts'][] = $attempt;
2499            }
2500        }
2501
2502        $result['warnings'] = $warnings;
2503        return $result;
2504    }
2505
2506    /**
2507     * Describes the get_submission_status return value.
2508     *
2509     * @return external_single_structure
2510     * @since Moodle 3.1
2511     */
2512    public static function get_submission_status_returns() {
2513        return new external_single_structure(
2514            array(
2515                'gradingsummary' => new external_single_structure(
2516                    array(
2517                        'participantcount' => new external_value(PARAM_INT, 'Number of users who can submit.'),
2518                        'submissiondraftscount' => new external_value(PARAM_INT, 'Number of submissions in draft status.'),
2519                        'submissiondraftscount' => new external_value(PARAM_INT, 'Number of submissions in draft status.'),
2520                        'submissionsenabled' => new external_value(PARAM_BOOL, 'Whether submissions are enabled or not.'),
2521                        'submissionssubmittedcount' => new external_value(PARAM_INT, 'Number of submissions in submitted status.'),
2522                        'submissionsneedgradingcount' => new external_value(PARAM_INT, 'Number of submissions that need grading.'),
2523                        'warnofungroupedusers' => new external_value(PARAM_ALPHA, 'Whether we need to warn people that there
2524                                                                        are users without groups (\'warningrequired\'), warn
2525                                                                        people there are users who will submit in the default
2526                                                                        group (\'warningoptional\') or no warning (\'\').'),
2527                    ), 'Grading information.', VALUE_OPTIONAL
2528                ),
2529                'lastattempt' => new external_single_structure(
2530                    array(
2531                        'submission' => self::get_submission_structure(VALUE_OPTIONAL),
2532                        'teamsubmission' => self::get_submission_structure(VALUE_OPTIONAL),
2533                        'submissiongroup' => new external_value(PARAM_INT, 'The submission group id (for group submissions only).',
2534                                                                VALUE_OPTIONAL),
2535                        'submissiongroupmemberswhoneedtosubmit' => new external_multiple_structure(
2536                            new external_value(PARAM_INT, 'USER id.'),
2537                            'List of users who still need to submit (for group submissions only).',
2538                            VALUE_OPTIONAL
2539                        ),
2540                        'submissionsenabled' => new external_value(PARAM_BOOL, 'Whether submissions are enabled or not.'),
2541                        'locked' => new external_value(PARAM_BOOL, 'Whether new submissions are locked.'),
2542                        'graded' => new external_value(PARAM_BOOL, 'Whether the submission is graded.'),
2543                        'canedit' => new external_value(PARAM_BOOL, 'Whether the user can edit the current submission.'),
2544                        'caneditowner' => new external_value(PARAM_BOOL, 'Whether the owner of the submission can edit it.'),
2545                        'cansubmit' => new external_value(PARAM_BOOL, 'Whether the user can submit.'),
2546                        'extensionduedate' => new external_value(PARAM_INT, 'Extension due date.'),
2547                        'blindmarking' => new external_value(PARAM_BOOL, 'Whether blind marking is enabled.'),
2548                        'gradingstatus' => new external_value(PARAM_ALPHANUMEXT, 'Grading status.'),
2549                        'usergroups' => new external_multiple_structure(
2550                            new external_value(PARAM_INT, 'Group id.'), 'User groups in the course.'
2551                        ),
2552                    ), 'Last attempt information.', VALUE_OPTIONAL
2553                ),
2554                'feedback' => new external_single_structure(
2555                    array(
2556                        'grade' => self::get_grade_structure(VALUE_OPTIONAL),
2557                        'gradefordisplay' => new external_value(PARAM_RAW, 'Grade rendered into a format suitable for display.'),
2558                        'gradeddate' => new external_value(PARAM_INT, 'The date the user was graded.'),
2559                        'plugins' => new external_multiple_structure(self::get_plugin_structure(), 'Plugins info.', VALUE_OPTIONAL),
2560                    ), 'Feedback for the last attempt.', VALUE_OPTIONAL
2561                ),
2562                'previousattempts' => new external_multiple_structure(
2563                    new external_single_structure(
2564                        array(
2565                            'attemptnumber' => new external_value(PARAM_INT, 'Attempt number.'),
2566                            'submission' => self::get_submission_structure(VALUE_OPTIONAL),
2567                            'grade' => self::get_grade_structure(VALUE_OPTIONAL),
2568                            'feedbackplugins' => new external_multiple_structure(self::get_plugin_structure(), 'Feedback info.',
2569                                                                                    VALUE_OPTIONAL),
2570                        )
2571                    ), 'List all the previous attempts did by the user.', VALUE_OPTIONAL
2572                ),
2573                'warnings' => new external_warnings(),
2574            )
2575        );
2576    }
2577
2578    /**
2579     * Returns description of method parameters
2580     *
2581     * @return external_function_parameters
2582     * @since Moodle 3.1
2583     */
2584    public static function list_participants_parameters() {
2585        return new external_function_parameters(
2586            array(
2587                'assignid' => new external_value(PARAM_INT, 'assign instance id'),
2588                'groupid' => new external_value(PARAM_INT, 'group id'),
2589                'filter' => new external_value(PARAM_RAW, 'search string to filter the results'),
2590                'skip' => new external_value(PARAM_INT, 'number of records to skip', VALUE_DEFAULT, 0),
2591                'limit' => new external_value(PARAM_INT, 'maximum number of records to return', VALUE_DEFAULT, 0),
2592                'onlyids' => new external_value(PARAM_BOOL, 'Do not return all user fields', VALUE_DEFAULT, false),
2593                'includeenrolments' => new external_value(PARAM_BOOL, 'Do return courses where the user is enrolled',
2594                                                          VALUE_DEFAULT, true),
2595                'tablesort' => new external_value(PARAM_BOOL, 'Apply current user table sorting preferences.',
2596                                                          VALUE_DEFAULT, false)
2597            )
2598        );
2599    }
2600
2601    /**
2602     * Retrieves the list of students to be graded for the assignment.
2603     *
2604     * @param int $assignid the assign instance id
2605     * @param int $groupid the current group id
2606     * @param string $filter search string to filter the results.
2607     * @param int $skip Number of records to skip
2608     * @param int $limit Maximum number of records to return
2609     * @param bool $onlyids Only return user ids.
2610     * @param bool $includeenrolments Return courses where the user is enrolled.
2611     * @param bool $tablesort Apply current user table sorting params from the grading table.
2612     * @return array of warnings and status result
2613     * @since Moodle 3.1
2614     * @throws moodle_exception
2615     */
2616    public static function list_participants($assignid, $groupid, $filter, $skip,
2617            $limit, $onlyids, $includeenrolments, $tablesort) {
2618        global $DB, $CFG;
2619        require_once($CFG->dirroot . "/mod/assign/locallib.php");
2620        require_once($CFG->dirroot . "/user/lib.php");
2621
2622        $params = self::validate_parameters(self::list_participants_parameters(),
2623                                            array(
2624                                                'assignid' => $assignid,
2625                                                'groupid' => $groupid,
2626                                                'filter' => $filter,
2627                                                'skip' => $skip,
2628                                                'limit' => $limit,
2629                                                'onlyids' => $onlyids,
2630                                                'includeenrolments' => $includeenrolments,
2631                                                'tablesort' => $tablesort
2632                                            ));
2633        $warnings = array();
2634
2635        list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2636
2637        require_capability('mod/assign:view', $context);
2638
2639        $assign->require_view_grades();
2640
2641        $participants = array();
2642        if (groups_group_visible($params['groupid'], $course, $cm)) {
2643            $participants = $assign->list_participants_with_filter_status_and_group($params['groupid'], $params['tablesort']);
2644        }
2645
2646        $userfields = user_get_default_fields();
2647        if (!$params['includeenrolments']) {
2648            // Remove enrolled courses from users fields to be returned.
2649            $key = array_search('enrolledcourses', $userfields);
2650            if ($key !== false) {
2651                unset($userfields[$key]);
2652            } else {
2653                throw new moodle_exception('invaliduserfield', 'error', '', 'enrolledcourses');
2654            }
2655        }
2656
2657        $result = array();
2658        $index = 0;
2659        foreach ($participants as $record) {
2660            // Preserve the fullname set by the assignment.
2661            $fullname = $record->fullname;
2662            $searchable = $fullname;
2663            $match = false;
2664            if (empty($filter)) {
2665                $match = true;
2666            } else {
2667                $filter = core_text::strtolower($filter);
2668                $value = core_text::strtolower($searchable);
2669                if (is_string($value) && (core_text::strpos($value, $filter) !== false)) {
2670                    $match = true;
2671                }
2672            }
2673            if ($match) {
2674                $index++;
2675                if ($index <= $params['skip']) {
2676                    continue;
2677                }
2678                if (($params['limit'] > 0) && (($index - $params['skip']) > $params['limit'])) {
2679                    break;
2680                }
2681                // Now we do the expensive lookup of user details because we completed the filtering.
2682                if (!$assign->is_blind_marking() && !$params['onlyids']) {
2683                    $userdetails = user_get_user_details($record, $course, $userfields);
2684                } else {
2685                    $userdetails = array('id' => $record->id);
2686                }
2687                $userdetails['fullname'] = $fullname;
2688                $userdetails['submitted'] = $record->submitted;
2689                $userdetails['requiregrading'] = $record->requiregrading;
2690                $userdetails['grantedextension'] = $record->grantedextension;
2691                if (!empty($record->groupid)) {
2692                    $userdetails['groupid'] = $record->groupid;
2693                }
2694                if (!empty($record->groupname)) {
2695                    $userdetails['groupname'] = $record->groupname;
2696                }
2697                // Unique id is required for blind marking.
2698                $userdetails['recordid'] = -1;
2699                if (!empty($record->recordid)) {
2700                    $userdetails['recordid'] = $record->recordid;
2701                }
2702
2703                $result[] = $userdetails;
2704            }
2705        }
2706        return $result;
2707    }
2708
2709    /**
2710     * Returns the description of the results of the mod_assign_external::list_participants() method.
2711     *
2712     * @return external_description
2713     * @since Moodle 3.1
2714     */
2715    public static function list_participants_returns() {
2716        // Get user description.
2717        $userdesc = core_user_external::user_description();
2718        // List unneeded properties.
2719        $unneededproperties = [
2720            'auth', 'confirmed', 'lang', 'calendartype', 'theme', 'timezone', 'mailformat'
2721        ];
2722        // Remove unneeded properties for consistency with the previous version.
2723        foreach ($unneededproperties as $prop) {
2724            unset($userdesc->keys[$prop]);
2725        }
2726
2727        // Override property attributes for consistency with the previous version.
2728        $userdesc->keys['fullname']->type = PARAM_NOTAGS;
2729        $userdesc->keys['profileimageurlsmall']->required = VALUE_OPTIONAL;
2730        $userdesc->keys['profileimageurl']->required = VALUE_OPTIONAL;
2731        $userdesc->keys['email']->desc = 'Email address';
2732        $userdesc->keys['idnumber']->desc = 'The idnumber of the user';
2733        $userdesc->keys['recordid'] = new external_value(PARAM_INT, 'record id');
2734
2735        // Define other keys.
2736        $otherkeys = [
2737            'groups' => new external_multiple_structure(
2738                new external_single_structure(
2739                    [
2740                        'id' => new external_value(PARAM_INT, 'group id'),
2741                        'name' => new external_value(PARAM_RAW, 'group name'),
2742                        'description' => new external_value(PARAM_RAW, 'group description'),
2743                    ]
2744                ), 'user groups', VALUE_OPTIONAL
2745            ),
2746            'roles' => new external_multiple_structure(
2747                new external_single_structure(
2748                    [
2749                        'roleid' => new external_value(PARAM_INT, 'role id'),
2750                        'name' => new external_value(PARAM_RAW, 'role name'),
2751                        'shortname' => new external_value(PARAM_ALPHANUMEXT, 'role shortname'),
2752                        'sortorder' => new external_value(PARAM_INT, 'role sortorder')
2753                    ]
2754                ), 'user roles', VALUE_OPTIONAL
2755            ),
2756            'enrolledcourses' => new external_multiple_structure(
2757                new external_single_structure(
2758                    [
2759                        'id' => new external_value(PARAM_INT, 'Id of the course'),
2760                        'fullname' => new external_value(PARAM_RAW, 'Fullname of the course'),
2761                        'shortname' => new external_value(PARAM_RAW, 'Shortname of the course')
2762                    ]
2763                ), 'Courses where the user is enrolled - limited by which courses the user is able to see', VALUE_OPTIONAL
2764            ),
2765            'submitted' => new external_value(PARAM_BOOL, 'have they submitted their assignment'),
2766            'requiregrading' => new external_value(PARAM_BOOL, 'is their submission waiting for grading'),
2767            'grantedextension' => new external_value(PARAM_BOOL, 'have they been granted an extension'),
2768            'groupid' => new external_value(PARAM_INT, 'for group assignments this is the group id', VALUE_OPTIONAL),
2769            'groupname' => new external_value(PARAM_NOTAGS, 'for group assignments this is the group name', VALUE_OPTIONAL),
2770        ];
2771
2772        // Merge keys.
2773        $userdesc->keys = array_merge($userdesc->keys, $otherkeys);
2774        return new external_multiple_structure($userdesc);
2775    }
2776
2777    /**
2778     * Returns description of method parameters
2779     *
2780     * @return external_function_parameters
2781     * @since Moodle 3.1
2782     */
2783    public static function get_participant_parameters() {
2784        return new external_function_parameters(
2785            array(
2786                'assignid' => new external_value(PARAM_INT, 'assign instance id'),
2787                'userid' => new external_value(PARAM_INT, 'user id'),
2788                'embeduser' => new external_value(PARAM_BOOL, 'user id', VALUE_DEFAULT, false),
2789            )
2790        );
2791    }
2792
2793    /**
2794     * Get the user participating in the given assignment. An error with code 'usernotincourse'
2795     * is thrown is the user isn't a participant of the given assignment.
2796     *
2797     * @param int $assignid the assign instance id
2798     * @param int $userid the user id
2799     * @param bool $embeduser return user details (only applicable if not blind marking)
2800     * @return array of warnings and status result
2801     * @since Moodle 3.1
2802     * @throws moodle_exception
2803     */
2804    public static function get_participant($assignid, $userid, $embeduser) {
2805        global $DB, $CFG;
2806        require_once($CFG->dirroot . "/mod/assign/locallib.php");
2807        require_once($CFG->dirroot . "/user/lib.php");
2808
2809        $params = self::validate_parameters(self::get_participant_parameters(), array(
2810            'assignid' => $assignid,
2811            'userid' => $userid,
2812            'embeduser' => $embeduser
2813        ));
2814
2815        list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2816        $assign->require_view_grades();
2817
2818        $participant = $assign->get_participant($params['userid']);
2819
2820        // Update assign with override information.
2821        $assign->update_effective_access($params['userid']);
2822
2823        if (!$participant) {
2824            // No participant found so we can return early.
2825            throw new moodle_exception('usernotincourse');
2826        }
2827
2828        $return = array(
2829            'id' => $participant->id,
2830            'fullname' => $participant->fullname,
2831            'submitted' => $participant->submitted,
2832            'requiregrading' => $participant->requiregrading,
2833            'grantedextension' => $participant->grantedextension,
2834            'blindmarking' => $assign->is_blind_marking(),
2835            'allowsubmissionsfromdate' => $assign->get_instance($userid)->allowsubmissionsfromdate,
2836            'duedate' => $assign->get_instance($userid)->duedate,
2837            'cutoffdate' => $assign->get_instance($userid)->cutoffdate,
2838            'duedatestr' => userdate($assign->get_instance($userid)->duedate, get_string('strftimedatetime', 'langconfig')),
2839        );
2840
2841        if (!empty($participant->groupid)) {
2842            $return['groupid'] = $participant->groupid;
2843        }
2844        if (!empty($participant->groupname)) {
2845            $return['groupname'] = $participant->groupname;
2846        }
2847
2848        // Skip the expensive lookup of user detail if we're blind marking or the caller
2849        // hasn't asked for user details to be embedded.
2850        if (!$assign->is_blind_marking() && $embeduser) {
2851            if ($userdetails = user_get_user_details($participant, $course)) {
2852                $return['user'] = $userdetails;
2853            }
2854        }
2855
2856        return $return;
2857    }
2858
2859    /**
2860     * Returns description of method result value
2861     *
2862     * @return external_description
2863     * @since Moodle 3.1
2864     */
2865    public static function get_participant_returns() {
2866        $userdescription = core_user_external::user_description();
2867        $userdescription->default = [];
2868        $userdescription->required = VALUE_OPTIONAL;
2869
2870        return new external_single_structure(array(
2871            'id' => new external_value(PARAM_INT, 'ID of the user'),
2872            'fullname' => new external_value(PARAM_NOTAGS, 'The fullname of the user'),
2873            'submitted' => new external_value(PARAM_BOOL, 'have they submitted their assignment'),
2874            'requiregrading' => new external_value(PARAM_BOOL, 'is their submission waiting for grading'),
2875            'grantedextension' => new external_value(PARAM_BOOL, 'have they been granted an extension'),
2876            'blindmarking' => new external_value(PARAM_BOOL, 'is blind marking enabled for this assignment'),
2877            'allowsubmissionsfromdate' => new external_value(PARAM_INT, 'allowsubmissionsfromdate for the user'),
2878            'duedate' => new external_value(PARAM_INT, 'duedate for the user'),
2879            'cutoffdate' => new external_value(PARAM_INT, 'cutoffdate for the user'),
2880            'duedatestr' => new external_value(PARAM_TEXT, 'duedate for the user'),
2881            'groupid' => new external_value(PARAM_INT, 'for group assignments this is the group id', VALUE_OPTIONAL),
2882            'groupname' => new external_value(PARAM_NOTAGS, 'for group assignments this is the group name', VALUE_OPTIONAL),
2883            'user' => $userdescription,
2884        ));
2885    }
2886
2887    /**
2888     * Utility function for validating an assign.
2889     *
2890     * @param int $assignid assign instance id
2891     * @return array array containing the assign, course, context and course module objects
2892     * @since  Moodle 3.2
2893     */
2894    protected static function validate_assign($assignid) {
2895        global $DB;
2896
2897        // Request and permission validation.
2898        $assign = $DB->get_record('assign', array('id' => $assignid), 'id', MUST_EXIST);
2899        list($course, $cm) = get_course_and_cm_from_instance($assign, 'assign');
2900
2901        $context = context_module::instance($cm->id);
2902        // Please, note that is not required to check mod/assign:view because is done by validate_context->require_login.
2903        self::validate_context($context);
2904        $assign = new assign($context, $cm, $course);
2905
2906        return array($assign, $course, $cm, $context);
2907    }
2908
2909    /**
2910     * Describes the parameters for view_assign.
2911     *
2912     * @return external_function_parameters
2913     * @since Moodle 3.2
2914     */
2915    public static function view_assign_parameters() {
2916        return new external_function_parameters (
2917            array(
2918                'assignid' => new external_value(PARAM_INT, 'assign instance id'),
2919            )
2920        );
2921    }
2922
2923    /**
2924     * Update the module completion status.
2925     *
2926     * @param int $assignid assign instance id
2927     * @return array of warnings and status result
2928     * @since Moodle 3.2
2929     */
2930    public static function view_assign($assignid) {
2931        $warnings = array();
2932        $params = array(
2933            'assignid' => $assignid,
2934        );
2935        $params = self::validate_parameters(self::view_assign_parameters(), $params);
2936
2937        list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2938
2939        $assign->set_module_viewed();
2940
2941        $result = array();
2942        $result['status'] = true;
2943        $result['warnings'] = $warnings;
2944        return $result;
2945    }
2946
2947    /**
2948     * Describes the view_assign return value.
2949     *
2950     * @return external_single_structure
2951     * @since Moodle 3.2
2952     */
2953    public static function view_assign_returns() {
2954        return new external_single_structure(
2955            array(
2956                'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2957                'warnings' => new external_warnings(),
2958            )
2959        );
2960    }
2961}
2962