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