2f769e144f86b77ef77d49b877652d34f93ba09c
[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/mod/assign/locallib.php");
31 /**
32  * Assign functions
33  * @copyright 2012 Paul Charsley
34  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  */
36 class mod_assign_external extends external_api {
38     /**
39      * Generate a warning in a standard structure for a known failure.
40      *
41      * @param int $assignmentid - The assignment
42      * @param string $warningcode - The key for the warning message
43      * @param string $detail - A description of the error
44      * @return array - Warning structure containing item, itemid, warningcode, message
45      */
46     private static function generate_warning($assignmentid, $warningcode, $detail) {
47         $warningmessages = array(
48             'couldnotlock'=>'Could not lock the submission for this user.',
49             'couldnotunlock'=>'Could not unlock the submission for this user.',
50             'couldnotsubmitforgrading'=>'Could not submit assignment for grading.',
51             'couldnotrevealidentities'=>'Could not reveal identities.',
52             'couldnotgrantextensions'=>'Could not grant submission date extensions.',
53             'couldnotrevert'=>'Could not revert submission to draft.',
54             'invalidparameters'=>'Invalid parameters.',
55             'couldnotsavesubmission'=>'Could not save submission.',
56             'couldnotsavegrade'=>'Could not save grade.'
57         );
59         $message = $warningmessages[$warningcode];
60         if (empty($message)) {
61             $message = 'Unknown warning type.';
62         }
64         return array('item'=>$detail,
65                      'itemid'=>$assignmentid,
66                      'warningcode'=>$warningcode,
67                      'message'=>$message);
68     }
70     /**
71      * Describes the parameters for get_grades
72      * @return external_external_function_parameters
73      * @since  Moodle 2.4
74      */
75     public static function get_grades_parameters() {
76         return new external_function_parameters(
77             array(
78                 'assignmentids' => new external_multiple_structure(
79                     new external_value(PARAM_INT, 'assignment id'),
80                     '1 or more assignment ids',
81                     VALUE_REQUIRED),
82                 'since' => new external_value(PARAM_INT,
83                           'timestamp, only return records where timemodified >= since',
84                           VALUE_DEFAULT, 0)
85             )
86         );
87     }
89     /**
90      * Returns grade information from assign_grades for the requested assignment ids
91      * @param int[] $assignmentids
92      * @param int $since only return records with timemodified >= since
93      * @return array of grade records for each requested assignment
94      * @since  Moodle 2.4
95      */
96     public static function get_grades($assignmentids, $since = 0) {
97         global $DB;
98         $params = self::validate_parameters(self::get_grades_parameters(),
99                         array('assignmentids' => $assignmentids,
100                               'since' => $since));
102         $assignments = array();
103         $warnings = array();
104         $requestedassignmentids = $params['assignmentids'];
106         // Check the user is allowed to get the grades for the assignments requested.
107         $placeholders = array();
108         list($sqlassignmentids, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
109         $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
110                "WHERE md.name = :modname AND cm.instance ".$sqlassignmentids;
111         $placeholders['modname'] = 'assign';
112         $cms = $DB->get_records_sql($sql, $placeholders);
113         foreach ($cms as $cm) {
114             try {
115                 $context = context_module::instance($cm->id);
116                 self::validate_context($context);
117                 require_capability('mod/assign:grade', $context);
118             } catch (Exception $e) {
119                 $requestedassignmentids = array_diff($requestedassignmentids, array($cm->instance));
120                 $warning = array();
121                 $warning['item'] = 'assignment';
122                 $warning['itemid'] = $cm->instance;
123                 $warning['warningcode'] = '1';
124                 $warning['message'] = 'No access rights in module context';
125                 $warnings[] = $warning;
126             }
127         }
129         // Create the query and populate an array of grade records from the recordset results.
130         if (count ($requestedassignmentids) > 0) {
131             $placeholders = array();
132             list($inorequalsql, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
134             $sql = "SELECT ag.id,
135                            ag.assignment,
136                            ag.userid,
137                            ag.timecreated,
138                            ag.timemodified,
139                            ag.grader,
140                            ag.grade,
141                            ag.attemptnumber
142                       FROM {assign_grades} ag, {assign_submission} s
143                      WHERE s.assignment $inorequalsql
144                        AND s.userid = ag.userid
145                        AND s.latest = 1
146                        AND s.attemptnumber = ag.attemptnumber
147                        AND ag.timemodified  >= :since
148                        AND ag.assignment = s.assignment
149                   ORDER BY ag.assignment, ag.id";
151             $placeholders['since'] = $params['since'];
152             $rs = $DB->get_recordset_sql($sql, $placeholders);
153             $currentassignmentid = null;
154             $assignment = null;
155             foreach ($rs as $rd) {
156                 $grade = array();
157                 $grade['id'] = $rd->id;
158                 $grade['userid'] = $rd->userid;
159                 $grade['timecreated'] = $rd->timecreated;
160                 $grade['timemodified'] = $rd->timemodified;
161                 $grade['grader'] = $rd->grader;
162                 $grade['attemptnumber'] = $rd->attemptnumber;
163                 $grade['grade'] = (string)$rd->grade;
165                 if (is_null($currentassignmentid) || ($rd->assignment != $currentassignmentid )) {
166                     if (!is_null($assignment)) {
167                         $assignments[] = $assignment;
168                     }
169                     $assignment = array();
170                     $assignment['assignmentid'] = $rd->assignment;
171                     $assignment['grades'] = array();
172                     $requestedassignmentids = array_diff($requestedassignmentids, array($rd->assignment));
173                 }
174                 $assignment['grades'][] = $grade;
176                 $currentassignmentid = $rd->assignment;
177             }
178             if (!is_null($assignment)) {
179                 $assignments[] = $assignment;
180             }
181             $rs->close();
182         }
183         foreach ($requestedassignmentids as $assignmentid) {
184             $warning = array();
185             $warning['item'] = 'assignment';
186             $warning['itemid'] = $assignmentid;
187             $warning['warningcode'] = '3';
188             $warning['message'] = 'No grades found';
189             $warnings[] = $warning;
190         }
192         $result = array();
193         $result['assignments'] = $assignments;
194         $result['warnings'] = $warnings;
195         return $result;
196     }
198     /**
199      * Creates a grade single structure.
200      *
201      * @return external_single_structure a grade single structure.
202      * @since  Moodle 3.1
203      */
204     private static function get_grade_structure($required = VALUE_REQUIRED) {
205         return new external_single_structure(
206             array(
207                 'id'                => new external_value(PARAM_INT, 'grade id'),
208                 'assignment'        => new external_value(PARAM_INT, 'assignment id', VALUE_OPTIONAL),
209                 'userid'            => new external_value(PARAM_INT, 'student id'),
210                 'attemptnumber'     => new external_value(PARAM_INT, 'attempt number'),
211                 'timecreated'       => new external_value(PARAM_INT, 'grade creation time'),
212                 'timemodified'      => new external_value(PARAM_INT, 'grade last modified time'),
213                 'grader'            => new external_value(PARAM_INT, 'grader'),
214                 'grade'             => new external_value(PARAM_TEXT, 'grade'),
215                 'gradefordisplay'   => new external_value(PARAM_RAW, 'grade rendered into a format suitable for display',
216                                                             VALUE_OPTIONAL),
217             ), 'grade information', $required
218         );
219     }
221     /**
222      * Creates an assign_grades external_single_structure
223      * @return external_single_structure
224      * @since  Moodle 2.4
225      */
226     private static function assign_grades() {
227         return new external_single_structure(
228             array (
229                 'assignmentid'  => new external_value(PARAM_INT, 'assignment id'),
230                 'grades'        => new external_multiple_structure(self::get_grade_structure())
231             )
232         );
233     }
235     /**
236      * Describes the get_grades return value
237      * @return external_single_structure
238      * @since  Moodle 2.4
239      */
240     public static function get_grades_returns() {
241         return new external_single_structure(
242             array(
243                 'assignments' => new external_multiple_structure(self::assign_grades(), 'list of assignment grade information'),
244                 'warnings'      => new external_warnings('item is always \'assignment\'',
245                     'when errorcode is 3 then itemid is an assignment id. When errorcode is 1, itemid is a course module id',
246                     'errorcode can be 3 (no grades found) or 1 (no permission to get grades)')
247             )
248         );
249     }
251     /**
252      * Returns description of method parameters
253      *
254      * @return external_function_parameters
255      * @since  Moodle 2.4
256      */
257     public static function get_assignments_parameters() {
258         return new external_function_parameters(
259             array(
260                 'courseids' => new external_multiple_structure(
261                     new external_value(PARAM_INT, 'course id, empty for retrieving all the courses where the user is enroled in'),
262                     '0 or more course ids',
263                     VALUE_DEFAULT, array()
264                 ),
265                 'capabilities'  => new external_multiple_structure(
266                     new external_value(PARAM_CAPABILITY, 'capability'),
267                     'list of capabilities used to filter courses',
268                     VALUE_DEFAULT, array()
269                 ),
270                 'includenotenrolledcourses' => new external_value(PARAM_BOOL, 'whether to return courses that the user can see
271                                                                     even if is not enroled in. This requires the parameter courseids
272                                                                     to not be empty.', VALUE_DEFAULT, false)
273             )
274         );
275     }
277     /**
278      * Returns an array of courses the user is enrolled, and for each course all of the assignments that the user can
279      * view within that course.
280      *
281      * @param array $courseids An optional array of course ids. If provided only assignments within the given course
282      * will be returned. If the user is not enrolled in or can't view a given course a warning will be generated and returned.
283      * @param array $capabilities An array of additional capability checks you wish to be made on the course context.
284      * @param bool $includenotenrolledcourses Wheter to return courses that the user can see even if is not enroled in.
285      * This requires the parameter $courseids to not be empty.
286      * @return An array of courses and warnings.
287      * @since  Moodle 2.4
288      */
289     public static function get_assignments($courseids = array(), $capabilities = array(), $includenotenrolledcourses = false) {
290         global $USER, $DB, $CFG;
292         $params = self::validate_parameters(
293             self::get_assignments_parameters(),
294             array(
295                 'courseids' => $courseids,
296                 'capabilities' => $capabilities,
297                 'includenotenrolledcourses' => $includenotenrolledcourses
298             )
299         );
301         $warnings = array();
302         $courses = array();
303         $fields = 'sortorder,shortname,fullname,timemodified';
305         // If the courseids list is empty, we return only the courses where the user is enrolled in.
306         if (empty($params['courseids'])) {
307             $courses = enrol_get_users_courses($USER->id, true, $fields);
308             $courseids = array_keys($courses);
309         } else if ($includenotenrolledcourses) {
310             // In this case, we don't have to check here for enrolmnents. Maybe the user can see the course even if is not enrolled.
311             $courseids = $params['courseids'];
312         } else {
313             // We need to check for enrolments.
314             $mycourses = enrol_get_users_courses($USER->id, true, $fields);
315             $mycourseids = array_keys($mycourses);
317             foreach ($params['courseids'] as $courseid) {
318                 if (!in_array($courseid, $mycourseids)) {
319                     unset($courses[$courseid]);
320                     $warnings[] = array(
321                         'item' => 'course',
322                         'itemid' => $courseid,
323                         'warningcode' => '2',
324                         'message' => 'User is not enrolled or does not have requested capability'
325                     );
326                 } else {
327                     $courses[$courseid] = $mycourses[$courseid];
328                 }
329             }
330             $courseids = array_keys($courses);
331         }
333         foreach ($courseids as $cid) {
335             try {
336                 $context = context_course::instance($cid);
337                 self::validate_context($context);
339                 // Check if this course was already loaded (by enrol_get_users_courses).
340                 if (!isset($courses[$cid])) {
341                     $courses[$cid] = get_course($cid);
342                 }
343             } catch (Exception $e) {
344                 unset($courses[$cid]);
345                 $warnings[] = array(
346                     'item' => 'course',
347                     'itemid' => $cid,
348                     'warningcode' => '1',
349                     'message' => 'No access rights in course context '.$e->getMessage()
350                 );
351                 continue;
352             }
353             if (count($params['capabilities']) > 0 && !has_all_capabilities($params['capabilities'], $context)) {
354                 unset($courses[$cid]);
355             }
356         }
357         $extrafields='m.id as assignmentid, ' .
358                      'm.course, ' .
359                      'm.nosubmissions, ' .
360                      'm.submissiondrafts, ' .
361                      'm.sendnotifications, '.
362                      'm.sendlatenotifications, ' .
363                      'm.sendstudentnotifications, ' .
364                      'm.duedate, ' .
365                      'm.allowsubmissionsfromdate, '.
366                      'm.grade, ' .
367                      'm.timemodified, '.
368                      'm.completionsubmit, ' .
369                      'm.cutoffdate, ' .
370                      'm.teamsubmission, ' .
371                      'm.requireallteammemberssubmit, '.
372                      'm.teamsubmissiongroupingid, ' .
373                      'm.blindmarking, ' .
374                      'm.revealidentities, ' .
375                      'm.attemptreopenmethod, '.
376                      'm.maxattempts, ' .
377                      'm.markingworkflow, ' .
378                      'm.markingallocation, ' .
379                      'm.requiresubmissionstatement, '.
380                      'm.intro, '.
381                      'm.introformat';
382         $coursearray = array();
383         foreach ($courses as $id => $course) {
384             $assignmentarray = array();
385             // Get a list of assignments for the course.
386             if ($modules = get_coursemodules_in_course('assign', $courses[$id]->id, $extrafields)) {
387                 foreach ($modules as $module) {
388                     $context = context_module::instance($module->id);
389                     try {
390                         self::validate_context($context);
391                         require_capability('mod/assign:view', $context);
392                     } catch (Exception $e) {
393                         $warnings[] = array(
394                             'item' => 'module',
395                             'itemid' => $module->id,
396                             'warningcode' => '1',
397                             'message' => 'No access rights in module context'
398                         );
399                         continue;
400                     }
401                     $configrecords = $DB->get_recordset('assign_plugin_config', array('assignment' => $module->assignmentid));
402                     $configarray = array();
403                     foreach ($configrecords as $configrecord) {
404                         $configarray[] = array(
405                             'id' => $configrecord->id,
406                             'assignment' => $configrecord->assignment,
407                             'plugin' => $configrecord->plugin,
408                             'subtype' => $configrecord->subtype,
409                             'name' => $configrecord->name,
410                             'value' => $configrecord->value
411                         );
412                     }
413                     $configrecords->close();
415                     $assignment = array(
416                         'id' => $module->assignmentid,
417                         'cmid' => $module->id,
418                         'course' => $module->course,
419                         'name' => $module->name,
420                         'nosubmissions' => $module->nosubmissions,
421                         'submissiondrafts' => $module->submissiondrafts,
422                         'sendnotifications' => $module->sendnotifications,
423                         'sendlatenotifications' => $module->sendlatenotifications,
424                         'sendstudentnotifications' => $module->sendstudentnotifications,
425                         'duedate' => $module->duedate,
426                         'allowsubmissionsfromdate' => $module->allowsubmissionsfromdate,
427                         'grade' => $module->grade,
428                         'timemodified' => $module->timemodified,
429                         'completionsubmit' => $module->completionsubmit,
430                         'cutoffdate' => $module->cutoffdate,
431                         'teamsubmission' => $module->teamsubmission,
432                         'requireallteammemberssubmit' => $module->requireallteammemberssubmit,
433                         'teamsubmissiongroupingid' => $module->teamsubmissiongroupingid,
434                         'blindmarking' => $module->blindmarking,
435                         'revealidentities' => $module->revealidentities,
436                         'attemptreopenmethod' => $module->attemptreopenmethod,
437                         'maxattempts' => $module->maxattempts,
438                         'markingworkflow' => $module->markingworkflow,
439                         'markingallocation' => $module->markingallocation,
440                         'requiresubmissionstatement' => $module->requiresubmissionstatement,
441                         'configs' => $configarray
442                     );
444                     // Return or not intro and file attachments depending on the plugin settings.
445                     $assign = new assign($context, null, null);
447                     if ($assign->show_intro()) {
449                         list($assignment['intro'], $assignment['introformat']) = external_format_text($module->intro,
450                             $module->introformat, $context->id, 'mod_assign', ASSIGN_INTROATTACHMENT_FILEAREA, 0);
452                         $fs = get_file_storage();
453                         if ($files = $fs->get_area_files($context->id, 'mod_assign', ASSIGN_INTROATTACHMENT_FILEAREA,
454                                                             0, 'timemodified', false)) {
456                             $assignment['introattachments'] = array();
457                             foreach ($files as $file) {
458                                 $filename = $file->get_filename();
460                                 $assignment['introattachments'][] = array(
461                                     'filename' => $filename,
462                                     'mimetype' => $file->get_mimetype(),
463                                     'fileurl'  => moodle_url::make_webservice_pluginfile_url(
464                                         $context->id, 'mod_assign', ASSIGN_INTROATTACHMENT_FILEAREA, 0, '/', $filename)->out(false)
465                                 );
466                             }
467                         }
468                     }
470                     $assignmentarray[] = $assignment;
471                 }
472             }
473             $coursearray[]= array(
474                 'id' => $courses[$id]->id,
475                 'fullname' => $courses[$id]->fullname,
476                 'shortname' => $courses[$id]->shortname,
477                 'timemodified' => $courses[$id]->timemodified,
478                 'assignments' => $assignmentarray
479             );
480         }
482         $result = array(
483             'courses' => $coursearray,
484             'warnings' => $warnings
485         );
486         return $result;
487     }
489     /**
490      * Creates an assignment external_single_structure
491      *
492      * @return external_single_structure
493      * @since Moodle 2.4
494      */
495     private static function get_assignments_assignment_structure() {
496         return new external_single_structure(
497             array(
498                 'id' => new external_value(PARAM_INT, 'assignment id'),
499                 'cmid' => new external_value(PARAM_INT, 'course module id'),
500                 'course' => new external_value(PARAM_INT, 'course id'),
501                 'name' => new external_value(PARAM_TEXT, 'assignment name'),
502                 'nosubmissions' => new external_value(PARAM_INT, 'no submissions'),
503                 'submissiondrafts' => new external_value(PARAM_INT, 'submissions drafts'),
504                 'sendnotifications' => new external_value(PARAM_INT, 'send notifications'),
505                 'sendlatenotifications' => new external_value(PARAM_INT, 'send notifications'),
506                 'sendstudentnotifications' => new external_value(PARAM_INT, 'send student notifications (default)'),
507                 'duedate' => new external_value(PARAM_INT, 'assignment due date'),
508                 'allowsubmissionsfromdate' => new external_value(PARAM_INT, 'allow submissions from date'),
509                 'grade' => new external_value(PARAM_INT, 'grade type'),
510                 'timemodified' => new external_value(PARAM_INT, 'last time assignment was modified'),
511                 'completionsubmit' => new external_value(PARAM_INT, 'if enabled, set activity as complete following submission'),
512                 'cutoffdate' => new external_value(PARAM_INT, 'date after which submission is not accepted without an extension'),
513                 'teamsubmission' => new external_value(PARAM_INT, 'if enabled, students submit as a team'),
514                 'requireallteammemberssubmit' => new external_value(PARAM_INT, 'if enabled, all team members must submit'),
515                 'teamsubmissiongroupingid' => new external_value(PARAM_INT, 'the grouping id for the team submission groups'),
516                 'blindmarking' => new external_value(PARAM_INT, 'if enabled, hide identities until reveal identities actioned'),
517                 'revealidentities' => new external_value(PARAM_INT, 'show identities for a blind marking assignment'),
518                 'attemptreopenmethod' => new external_value(PARAM_TEXT, 'method used to control opening new attempts'),
519                 'maxattempts' => new external_value(PARAM_INT, 'maximum number of attempts allowed'),
520                 'markingworkflow' => new external_value(PARAM_INT, 'enable marking workflow'),
521                 'markingallocation' => new external_value(PARAM_INT, 'enable marking allocation'),
522                 'requiresubmissionstatement' => new external_value(PARAM_INT, 'student must accept submission statement'),
523                 'configs' => new external_multiple_structure(self::get_assignments_config_structure(), 'configuration settings'),
524                 'intro' => new external_value(PARAM_RAW,
525                     'assignment intro, not allways returned because it deppends on the activity configuration', VALUE_OPTIONAL),
526                 'introformat' => new external_format_value('intro', VALUE_OPTIONAL),
527                 'introattachments' => new external_multiple_structure(
528                     new external_single_structure(
529                         array (
530                             'filename' => new external_value(PARAM_FILE, 'file name'),
531                             'mimetype' => new external_value(PARAM_RAW, 'mime type'),
532                             'fileurl'  => new external_value(PARAM_URL, 'file download url')
533                         )
534                     ), 'intro attachments files', VALUE_OPTIONAL
535                 )
536             ), 'assignment information object');
537     }
539     /**
540      * Creates an assign_plugin_config external_single_structure
541      *
542      * @return external_single_structure
543      * @since Moodle 2.4
544      */
545     private static function get_assignments_config_structure() {
546         return new external_single_structure(
547             array(
548                 'id' => new external_value(PARAM_INT, 'assign_plugin_config id'),
549                 'assignment' => new external_value(PARAM_INT, 'assignment id'),
550                 'plugin' => new external_value(PARAM_TEXT, 'plugin'),
551                 'subtype' => new external_value(PARAM_TEXT, 'subtype'),
552                 'name' => new external_value(PARAM_TEXT, 'name'),
553                 'value' => new external_value(PARAM_TEXT, 'value')
554             ), 'assignment configuration object'
555         );
556     }
558     /**
559      * Creates a course external_single_structure
560      *
561      * @return external_single_structure
562      * @since Moodle 2.4
563      */
564     private static function get_assignments_course_structure() {
565         return new external_single_structure(
566             array(
567                 'id' => new external_value(PARAM_INT, 'course id'),
568                 'fullname' => new external_value(PARAM_TEXT, 'course full name'),
569                 'shortname' => new external_value(PARAM_TEXT, 'course short name'),
570                 'timemodified' => new external_value(PARAM_INT, 'last time modified'),
571                 'assignments' => new external_multiple_structure(self::get_assignments_assignment_structure(), 'assignment info')
572               ), 'course information object'
573         );
574     }
576     /**
577      * Describes the return value for get_assignments
578      *
579      * @return external_single_structure
580      * @since Moodle 2.4
581      */
582     public static function get_assignments_returns() {
583         return new external_single_structure(
584             array(
585                 'courses' => new external_multiple_structure(self::get_assignments_course_structure(), 'list of courses'),
586                 'warnings'  => new external_warnings('item can be \'course\' (errorcode 1 or 2) or \'module\' (errorcode 1)',
587                     'When item is a course then itemid is a course id. When the item is a module then itemid is a module id',
588                     'errorcode can be 1 (no access rights) or 2 (not enrolled or no permissions)')
589             )
590         );
591     }
593     /**
594      * Return information (files and text fields) for the given plugins in the assignment.
595      *
596      * @param  assign $assign the assignment object
597      * @param  array $assignplugins array of assignment plugins (submission or feedback)
598      * @param  stdClass $item the item object (submission or grade)
599      * @return array an array containing the plugins returned information
600      */
601     private static function get_plugins_data($assign, $assignplugins, $item) {
602         global $CFG;
604         $plugins = array();
605         $fs = get_file_storage();
607         foreach ($assignplugins as $assignplugin) {
609             if (!$assignplugin->is_enabled() or !$assignplugin->is_visible()) {
610                 continue;
611             }
613             $plugin = array(
614                 'name' => $assignplugin->get_name(),
615                 'type' => $assignplugin->get_type()
616             );
617             // Subtype is 'assignsubmission', type is currently 'file' or 'onlinetext'.
618             $component = $assignplugin->get_subtype().'_'.$assignplugin->get_type();
620             $fileareas = $assignplugin->get_file_areas();
621             foreach ($fileareas as $filearea => $name) {
622                 $fileareainfo = array('area' => $filearea);
623                 $files = $fs->get_area_files(
624                     $assign->get_context()->id,
625                     $component,
626                     $filearea,
627                     $item->id,
628                     "timemodified",
629                     false
630                 );
631                 foreach ($files as $file) {
632                     $filepath = $file->get_filepath().$file->get_filename();
633                     $fileurl = file_encode_url($CFG->wwwroot . '/webservice/pluginfile.php', '/' . $assign->get_context()->id .
634                         '/' . $component. '/'. $filearea . '/' . $item->id . $filepath);
635                     $fileinfo = array(
636                         'filepath' => $filepath,
637                         'fileurl' => $fileurl
638                         );
639                     $fileareainfo['files'][] = $fileinfo;
640                 }
641                 $plugin['fileareas'][] = $fileareainfo;
642             }
644             $editorfields = $assignplugin->get_editor_fields();
645             foreach ($editorfields as $name => $description) {
646                 $editorfieldinfo = array(
647                     'name' => $name,
648                     'description' => $description,
649                     'text' => $assignplugin->get_editor_text($name, $item->id),
650                     'format' => $assignplugin->get_editor_format($name, $item->id)
651                 );
652                 $plugin['editorfields'][] = $editorfieldinfo;
653             }
654             $plugins[] = $plugin;
655         }
656         return $plugins;
657     }
659     /**
660      * Describes the parameters for get_submissions
661      *
662      * @return external_external_function_parameters
663      * @since Moodle 2.5
664      */
665     public static function get_submissions_parameters() {
666         return new external_function_parameters(
667             array(
668                 'assignmentids' => new external_multiple_structure(
669                     new external_value(PARAM_INT, 'assignment id'),
670                     '1 or more assignment ids',
671                     VALUE_REQUIRED),
672                 'status' => new external_value(PARAM_ALPHA, 'status', VALUE_DEFAULT, ''),
673                 'since' => new external_value(PARAM_INT, 'submitted since', VALUE_DEFAULT, 0),
674                 'before' => new external_value(PARAM_INT, 'submitted before', VALUE_DEFAULT, 0)
675             )
676         );
677     }
679     /**
680      * Returns submissions for the requested assignment ids
681      *
682      * @param int[] $assignmentids
683      * @param string $status only return submissions with this status
684      * @param int $since only return submissions with timemodified >= since
685      * @param int $before only return submissions with timemodified <= before
686      * @return array of submissions for each requested assignment
687      * @since Moodle 2.5
688      */
689     public static function get_submissions($assignmentids, $status = '', $since = 0, $before = 0) {
690         global $DB, $CFG;
692         $params = self::validate_parameters(self::get_submissions_parameters(),
693                         array('assignmentids' => $assignmentids,
694                               'status' => $status,
695                               'since' => $since,
696                               'before' => $before));
698         $warnings = array();
699         $assignments = array();
701         // Check the user is allowed to get the submissions for the assignments requested.
702         $placeholders = array();
703         list($inorequalsql, $placeholders) = $DB->get_in_or_equal($params['assignmentids'], SQL_PARAMS_NAMED);
704         $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
705                "WHERE md.name = :modname AND cm.instance ".$inorequalsql;
706         $placeholders['modname'] = 'assign';
707         $cms = $DB->get_records_sql($sql, $placeholders);
708         $assigns = array();
709         foreach ($cms as $cm) {
710             try {
711                 $context = context_module::instance($cm->id);
712                 self::validate_context($context);
713                 require_capability('mod/assign:grade', $context);
714                 $assign = new assign($context, null, null);
715                 $assigns[] = $assign;
716             } catch (Exception $e) {
717                 $warnings[] = array(
718                     'item' => 'assignment',
719                     'itemid' => $cm->instance,
720                     'warningcode' => '1',
721                     'message' => 'No access rights in module context'
722                 );
723             }
724         }
726         foreach ($assigns as $assign) {
727             $submissions = array();
728             $placeholders = array('assignid1' => $assign->get_instance()->id,
729                                   'assignid2' => $assign->get_instance()->id);
731             $submissionmaxattempt = 'SELECT mxs.userid, MAX(mxs.attemptnumber) AS maxattempt
732                                      FROM {assign_submission} mxs
733                                      WHERE mxs.assignment = :assignid1 GROUP BY mxs.userid';
735             $sql = "SELECT mas.id, mas.assignment,mas.userid,".
736                    "mas.timecreated,mas.timemodified,mas.status,mas.groupid,mas.attemptnumber ".
737                    "FROM {assign_submission} mas ".
738                    "JOIN ( " . $submissionmaxattempt . " ) smx ON mas.userid = smx.userid ".
739                    "WHERE mas.assignment = :assignid2 AND mas.attemptnumber = smx.maxattempt";
741             if (!empty($params['status'])) {
742                 $placeholders['status'] = $params['status'];
743                 $sql = $sql." AND mas.status = :status";
744             }
745             if (!empty($params['before'])) {
746                 $placeholders['since'] = $params['since'];
747                 $placeholders['before'] = $params['before'];
748                 $sql = $sql." AND mas.timemodified BETWEEN :since AND :before";
749             } else {
750                 $placeholders['since'] = $params['since'];
751                 $sql = $sql." AND mas.timemodified >= :since";
752             }
754             $submissionrecords = $DB->get_records_sql($sql, $placeholders);
756             if (!empty($submissionrecords)) {
757                 $submissionplugins = $assign->get_submission_plugins();
758                 foreach ($submissionrecords as $submissionrecord) {
759                     $submission = array(
760                         'id' => $submissionrecord->id,
761                         'userid' => $submissionrecord->userid,
762                         'timecreated' => $submissionrecord->timecreated,
763                         'timemodified' => $submissionrecord->timemodified,
764                         'status' => $submissionrecord->status,
765                         'attemptnumber' => $submissionrecord->attemptnumber,
766                         'groupid' => $submissionrecord->groupid,
767                         'plugins' => self::get_plugins_data($assign, $submissionplugins, $submissionrecord)
768                     );
769                     $submissions[] = $submission;
770                 }
771             } else {
772                 $warnings[] = array(
773                     'item' => 'module',
774                     'itemid' => $assign->get_instance()->id,
775                     'warningcode' => '3',
776                     'message' => 'No submissions found'
777                 );
778             }
780             $assignments[] = array(
781                 'assignmentid' => $assign->get_instance()->id,
782                 'submissions' => $submissions
783             );
785         }
787         $result = array(
788             'assignments' => $assignments,
789             'warnings' => $warnings
790         );
791         return $result;
792     }
794     /**
795      * Creates an assignment plugin structure.
796      *
797      * @return external_single_structure the plugin structure
798      */
799     private static function get_plugin_structure() {
800         return new external_single_structure(
801             array(
802                 'type' => new external_value(PARAM_TEXT, 'submission plugin type'),
803                 'name' => new external_value(PARAM_TEXT, 'submission plugin name'),
804                 'fileareas' => new external_multiple_structure(
805                     new external_single_structure(
806                         array (
807                             'area' => new external_value (PARAM_TEXT, 'file area'),
808                             'files' => new external_multiple_structure(
809                                 new external_single_structure(
810                                     array (
811                                         'filepath' => new external_value (PARAM_TEXT, 'file path'),
812                                         'fileurl' => new external_value (PARAM_URL, 'file download url',
813                                             VALUE_OPTIONAL)
814                                     )
815                                 ), 'files', VALUE_OPTIONAL
816                             )
817                         )
818                     ), 'fileareas', VALUE_OPTIONAL
819                 ),
820                 'editorfields' => new external_multiple_structure(
821                     new external_single_structure(
822                         array(
823                             'name' => new external_value(PARAM_TEXT, 'field name'),
824                             'description' => new external_value(PARAM_TEXT, 'field description'),
825                             'text' => new external_value (PARAM_RAW, 'field value'),
826                             'format' => new external_format_value ('text')
827                         )
828                     )
829                     , 'editorfields', VALUE_OPTIONAL
830                 )
831             )
832         );
833     }
835     /**
836      * Creates a submission structure.
837      *
838      * @return external_single_structure the submission structure
839      */
840     private static function get_submission_structure($required = VALUE_REQUIRED) {
841         return new external_single_structure(
842             array(
843                 'id' => new external_value(PARAM_INT, 'submission id'),
844                 'userid' => new external_value(PARAM_INT, 'student id'),
845                 'attemptnumber' => new external_value(PARAM_INT, 'attempt number'),
846                 'timecreated' => new external_value(PARAM_INT, 'submission creation time'),
847                 'timemodified' => new external_value(PARAM_INT, 'submission last modified time'),
848                 'status' => new external_value(PARAM_TEXT, 'submission status'),
849                 'groupid' => new external_value(PARAM_INT, 'group id'),
850                 'assignment' => new external_value(PARAM_INT, 'assignment id', VALUE_OPTIONAL),
851                 'latest' => new external_value(PARAM_INT, 'latest attempt', VALUE_OPTIONAL),
852                 'plugins' => new external_multiple_structure(self::get_plugin_structure(), 'plugins', VALUE_OPTIONAL)
853             ), 'submission info', $required
854         );
855     }
857     /**
858      * Creates an assign_submissions external_single_structure
859      *
860      * @return external_single_structure
861      * @since Moodle 2.5
862      */
863     private static function get_submissions_structure() {
864         return new external_single_structure(
865             array (
866                 'assignmentid' => new external_value(PARAM_INT, 'assignment id'),
867                 'submissions' => new external_multiple_structure(self::get_submission_structure())
868             )
869         );
870     }
872     /**
873      * Describes the get_submissions return value
874      *
875      * @return external_single_structure
876      * @since Moodle 2.5
877      */
878     public static function get_submissions_returns() {
879         return new external_single_structure(
880             array(
881                 'assignments' => new external_multiple_structure(self::get_submissions_structure(), 'assignment submissions'),
882                 'warnings' => new external_warnings()
883             )
884         );
885     }
887     /**
888      * Describes the parameters for set_user_flags
889      * @return external_function_parameters
890      * @since  Moodle 2.6
891      */
892     public static function set_user_flags_parameters() {
893         return new external_function_parameters(
894             array(
895                 'assignmentid'    => new external_value(PARAM_INT, 'assignment id'),
896                 'userflags' => new external_multiple_structure(
897                     new external_single_structure(
898                         array(
899                             'userid'           => new external_value(PARAM_INT, 'student id'),
900                             'locked'           => new external_value(PARAM_INT, 'locked', VALUE_OPTIONAL),
901                             'mailed'           => new external_value(PARAM_INT, 'mailed', VALUE_OPTIONAL),
902                             'extensionduedate' => new external_value(PARAM_INT, 'extension due date', VALUE_OPTIONAL),
903                             'workflowstate'    => new external_value(PARAM_TEXT, 'marking workflow state', VALUE_OPTIONAL),
904                             'allocatedmarker'  => new external_value(PARAM_INT, 'allocated marker', VALUE_OPTIONAL)
905                         )
906                     )
907                 )
908             )
909         );
910     }
912     /**
913      * Create or update user_flags records
914      *
915      * @param int $assignmentid the assignment for which the userflags are created or updated
916      * @param array $userflags  An array of userflags to create or update
917      * @return array containing success or failure information for each record
918      * @since Moodle 2.6
919      */
920     public static function set_user_flags($assignmentid, $userflags = array()) {
921         global $CFG, $DB;
923         $params = self::validate_parameters(self::set_user_flags_parameters(),
924                                             array('assignmentid' => $assignmentid,
925                                                   'userflags' => $userflags));
927         // Load assignment if it exists and if the user has the capability.
928         $cm = get_coursemodule_from_instance('assign', $params['assignmentid'], 0, false, MUST_EXIST);
929         $context = context_module::instance($cm->id);
930         self::validate_context($context);
931         require_capability('mod/assign:grade', $context);
932         $assign = new assign($context, null, null);
934         $results = array();
935         foreach ($params['userflags'] as $userflag) {
936             $success = true;
937             $result = array();
939             $record = $assign->get_user_flags($userflag['userid'], false);
940             if ($record) {
941                 if (isset($userflag['locked'])) {
942                     $record->locked = $userflag['locked'];
943                 }
944                 if (isset($userflag['mailed'])) {
945                     $record->mailed = $userflag['mailed'];
946                 }
947                 if (isset($userflag['extensionduedate'])) {
948                     $record->extensionduedate = $userflag['extensionduedate'];
949                 }
950                 if (isset($userflag['workflowstate'])) {
951                     $record->workflowstate = $userflag['workflowstate'];
952                 }
953                 if (isset($userflag['allocatedmarker'])) {
954                     $record->allocatedmarker = $userflag['allocatedmarker'];
955                 }
956                 if ($assign->update_user_flags($record)) {
957                     $result['id'] = $record->id;
958                     $result['userid'] = $userflag['userid'];
959                 } else {
960                     $result['id'] = $record->id;
961                     $result['userid'] = $userflag['userid'];
962                     $result['errormessage'] = 'Record created but values could not be set';
963                 }
964             } else {
965                 $record = $assign->get_user_flags($userflag['userid'], true);
966                 $setfields = isset($userflag['locked'])
967                              || isset($userflag['mailed'])
968                              || isset($userflag['extensionduedate'])
969                              || isset($userflag['workflowstate'])
970                              || isset($userflag['allocatedmarker']);
971                 if ($record) {
972                     if ($setfields) {
973                         if (isset($userflag['locked'])) {
974                             $record->locked = $userflag['locked'];
975                         }
976                         if (isset($userflag['mailed'])) {
977                             $record->mailed = $userflag['mailed'];
978                         }
979                         if (isset($userflag['extensionduedate'])) {
980                             $record->extensionduedate = $userflag['extensionduedate'];
981                         }
982                         if (isset($userflag['workflowstate'])) {
983                             $record->workflowstate = $userflag['workflowstate'];
984                         }
985                         if (isset($userflag['allocatedmarker'])) {
986                             $record->allocatedmarker = $userflag['allocatedmarker'];
987                         }
988                         if ($assign->update_user_flags($record)) {
989                             $result['id'] = $record->id;
990                             $result['userid'] = $userflag['userid'];
991                         } else {
992                             $result['id'] = $record->id;
993                             $result['userid'] = $userflag['userid'];
994                             $result['errormessage'] = 'Record created but values could not be set';
995                         }
996                     } else {
997                         $result['id'] = $record->id;
998                         $result['userid'] = $userflag['userid'];
999                     }
1000                 } else {
1001                     $result['id'] = -1;
1002                     $result['userid'] = $userflag['userid'];
1003                     $result['errormessage'] = 'Record could not be created';
1004                 }
1005             }
1007             $results[] = $result;
1008         }
1009         return $results;
1010     }
1012     /**
1013      * Describes the set_user_flags return value
1014      * @return external_multiple_structure
1015      * @since  Moodle 2.6
1016      */
1017     public static function set_user_flags_returns() {
1018         return new external_multiple_structure(
1019             new external_single_structure(
1020                 array(
1021                     'id' => new external_value(PARAM_INT, 'id of record if successful, -1 for failure'),
1022                     'userid' => new external_value(PARAM_INT, 'userid of record'),
1023                     'errormessage' => new external_value(PARAM_TEXT, 'Failure error message', VALUE_OPTIONAL)
1024                 )
1025             )
1026         );
1027     }
1029     /**
1030      * Describes the parameters for get_user_flags
1031      * @return external_function_parameters
1032      * @since  Moodle 2.6
1033      */
1034     public static function get_user_flags_parameters() {
1035         return new external_function_parameters(
1036             array(
1037                 'assignmentids' => new external_multiple_structure(
1038                     new external_value(PARAM_INT, 'assignment id'),
1039                     '1 or more assignment ids',
1040                     VALUE_REQUIRED)
1041             )
1042         );
1043     }
1045     /**
1046      * Returns user flag information from assign_user_flags for the requested assignment ids
1047      * @param int[] $assignmentids
1048      * @return array of user flag records for each requested assignment
1049      * @since  Moodle 2.6
1050      */
1051     public static function get_user_flags($assignmentids) {
1052         global $DB;
1053         $params = self::validate_parameters(self::get_user_flags_parameters(),
1054                         array('assignmentids' => $assignmentids));
1056         $assignments = array();
1057         $warnings = array();
1058         $requestedassignmentids = $params['assignmentids'];
1060         // Check the user is allowed to get the user flags for the assignments requested.
1061         $placeholders = array();
1062         list($sqlassignmentids, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1063         $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
1064                "WHERE md.name = :modname AND cm.instance ".$sqlassignmentids;
1065         $placeholders['modname'] = 'assign';
1066         $cms = $DB->get_records_sql($sql, $placeholders);
1067         foreach ($cms as $cm) {
1068             try {
1069                 $context = context_module::instance($cm->id);
1070                 self::validate_context($context);
1071                 require_capability('mod/assign:grade', $context);
1072             } catch (Exception $e) {
1073                 $requestedassignmentids = array_diff($requestedassignmentids, array($cm->instance));
1074                 $warning = array();
1075                 $warning['item'] = 'assignment';
1076                 $warning['itemid'] = $cm->instance;
1077                 $warning['warningcode'] = '1';
1078                 $warning['message'] = 'No access rights in module context';
1079                 $warnings[] = $warning;
1080             }
1081         }
1083         // Create the query and populate an array of assign_user_flags records from the recordset results.
1084         if (count ($requestedassignmentids) > 0) {
1085             $placeholders = array();
1086             list($inorequalsql, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1088             $sql = "SELECT auf.id,auf.assignment,auf.userid,auf.locked,auf.mailed,".
1089                    "auf.extensionduedate,auf.workflowstate,auf.allocatedmarker ".
1090                    "FROM {assign_user_flags} auf ".
1091                    "WHERE auf.assignment ".$inorequalsql.
1092                    " ORDER BY auf.assignment, auf.id";
1094             $rs = $DB->get_recordset_sql($sql, $placeholders);
1095             $currentassignmentid = null;
1096             $assignment = null;
1097             foreach ($rs as $rd) {
1098                 $userflag = array();
1099                 $userflag['id'] = $rd->id;
1100                 $userflag['userid'] = $rd->userid;
1101                 $userflag['locked'] = $rd->locked;
1102                 $userflag['mailed'] = $rd->mailed;
1103                 $userflag['extensionduedate'] = $rd->extensionduedate;
1104                 $userflag['workflowstate'] = $rd->workflowstate;
1105                 $userflag['allocatedmarker'] = $rd->allocatedmarker;
1107                 if (is_null($currentassignmentid) || ($rd->assignment != $currentassignmentid )) {
1108                     if (!is_null($assignment)) {
1109                         $assignments[] = $assignment;
1110                     }
1111                     $assignment = array();
1112                     $assignment['assignmentid'] = $rd->assignment;
1113                     $assignment['userflags'] = array();
1114                     $requestedassignmentids = array_diff($requestedassignmentids, array($rd->assignment));
1115                 }
1116                 $assignment['userflags'][] = $userflag;
1118                 $currentassignmentid = $rd->assignment;
1119             }
1120             if (!is_null($assignment)) {
1121                 $assignments[] = $assignment;
1122             }
1123             $rs->close();
1125         }
1127         foreach ($requestedassignmentids as $assignmentid) {
1128             $warning = array();
1129             $warning['item'] = 'assignment';
1130             $warning['itemid'] = $assignmentid;
1131             $warning['warningcode'] = '3';
1132             $warning['message'] = 'No user flags found';
1133             $warnings[] = $warning;
1134         }
1136         $result = array();
1137         $result['assignments'] = $assignments;
1138         $result['warnings'] = $warnings;
1139         return $result;
1140     }
1142     /**
1143      * Creates an assign_user_flags external_single_structure
1144      * @return external_single_structure
1145      * @since  Moodle 2.6
1146      */
1147     private static function assign_user_flags() {
1148         return new external_single_structure(
1149             array (
1150                 'assignmentid'    => new external_value(PARAM_INT, 'assignment id'),
1151                 'userflags'   => new external_multiple_structure(new external_single_structure(
1152                         array(
1153                             'id'               => new external_value(PARAM_INT, 'user flag id'),
1154                             'userid'           => new external_value(PARAM_INT, 'student id'),
1155                             'locked'           => new external_value(PARAM_INT, 'locked'),
1156                             'mailed'           => new external_value(PARAM_INT, 'mailed'),
1157                             'extensionduedate' => new external_value(PARAM_INT, 'extension due date'),
1158                             'workflowstate'    => new external_value(PARAM_TEXT, 'marking workflow state', VALUE_OPTIONAL),
1159                             'allocatedmarker'  => new external_value(PARAM_INT, 'allocated marker')
1160                         )
1161                     )
1162                 )
1163             )
1164         );
1165     }
1167     /**
1168      * Describes the get_user_flags return value
1169      * @return external_single_structure
1170      * @since  Moodle 2.6
1171      */
1172     public static function get_user_flags_returns() {
1173         return new external_single_structure(
1174             array(
1175                 'assignments' => new external_multiple_structure(self::assign_user_flags(), 'list of assign user flag information'),
1176                 'warnings'      => new external_warnings('item is always \'assignment\'',
1177                     'when errorcode is 3 then itemid is an assignment id. When errorcode is 1, itemid is a course module id',
1178                     'errorcode can be 3 (no user flags found) or 1 (no permission to get user flags)')
1179             )
1180         );
1181     }
1183     /**
1184      * Describes the parameters for get_user_mappings
1185      * @return external_function_parameters
1186      * @since  Moodle 2.6
1187      */
1188     public static function get_user_mappings_parameters() {
1189         return new external_function_parameters(
1190             array(
1191                 'assignmentids' => new external_multiple_structure(
1192                     new external_value(PARAM_INT, 'assignment id'),
1193                     '1 or more assignment ids',
1194                     VALUE_REQUIRED)
1195             )
1196         );
1197     }
1199     /**
1200      * Returns user mapping information from assign_user_mapping for the requested assignment ids
1201      * @param int[] $assignmentids
1202      * @return array of user mapping records for each requested assignment
1203      * @since  Moodle 2.6
1204      */
1205     public static function get_user_mappings($assignmentids) {
1206         global $DB;
1207         $params = self::validate_parameters(self::get_user_mappings_parameters(),
1208                         array('assignmentids' => $assignmentids));
1210         $assignments = array();
1211         $warnings = array();
1212         $requestedassignmentids = $params['assignmentids'];
1214         // Check the user is allowed to get the mappings for the assignments requested.
1215         $placeholders = array();
1216         list($sqlassignmentids, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1217         $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
1218                "WHERE md.name = :modname AND cm.instance ".$sqlassignmentids;
1219         $placeholders['modname'] = 'assign';
1220         $cms = $DB->get_records_sql($sql, $placeholders);
1221         foreach ($cms as $cm) {
1222             try {
1223                 $context = context_module::instance($cm->id);
1224                 self::validate_context($context);
1225                 require_capability('mod/assign:revealidentities', $context);
1226             } catch (Exception $e) {
1227                 $requestedassignmentids = array_diff($requestedassignmentids, array($cm->instance));
1228                 $warning = array();
1229                 $warning['item'] = 'assignment';
1230                 $warning['itemid'] = $cm->instance;
1231                 $warning['warningcode'] = '1';
1232                 $warning['message'] = 'No access rights in module context';
1233                 $warnings[] = $warning;
1234             }
1235         }
1237         // Create the query and populate an array of assign_user_mapping records from the recordset results.
1238         if (count ($requestedassignmentids) > 0) {
1239             $placeholders = array();
1240             list($inorequalsql, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1242             $sql = "SELECT aum.id,aum.assignment,aum.userid ".
1243                    "FROM {assign_user_mapping} aum ".
1244                    "WHERE aum.assignment ".$inorequalsql.
1245                    " ORDER BY aum.assignment, aum.id";
1247             $rs = $DB->get_recordset_sql($sql, $placeholders);
1248             $currentassignmentid = null;
1249             $assignment = null;
1250             foreach ($rs as $rd) {
1251                 $mapping = array();
1252                 $mapping['id'] = $rd->id;
1253                 $mapping['userid'] = $rd->userid;
1255                 if (is_null($currentassignmentid) || ($rd->assignment != $currentassignmentid )) {
1256                     if (!is_null($assignment)) {
1257                         $assignments[] = $assignment;
1258                     }
1259                     $assignment = array();
1260                     $assignment['assignmentid'] = $rd->assignment;
1261                     $assignment['mappings'] = array();
1262                     $requestedassignmentids = array_diff($requestedassignmentids, array($rd->assignment));
1263                 }
1264                 $assignment['mappings'][] = $mapping;
1266                 $currentassignmentid = $rd->assignment;
1267             }
1268             if (!is_null($assignment)) {
1269                 $assignments[] = $assignment;
1270             }
1271             $rs->close();
1273         }
1275         foreach ($requestedassignmentids as $assignmentid) {
1276             $warning = array();
1277             $warning['item'] = 'assignment';
1278             $warning['itemid'] = $assignmentid;
1279             $warning['warningcode'] = '3';
1280             $warning['message'] = 'No mappings found';
1281             $warnings[] = $warning;
1282         }
1284         $result = array();
1285         $result['assignments'] = $assignments;
1286         $result['warnings'] = $warnings;
1287         return $result;
1288     }
1290     /**
1291      * Creates an assign_user_mappings external_single_structure
1292      * @return external_single_structure
1293      * @since  Moodle 2.6
1294      */
1295     private static function assign_user_mappings() {
1296         return new external_single_structure(
1297             array (
1298                 'assignmentid'    => new external_value(PARAM_INT, 'assignment id'),
1299                 'mappings'   => new external_multiple_structure(new external_single_structure(
1300                         array(
1301                             'id'     => new external_value(PARAM_INT, 'user mapping id'),
1302                             'userid' => new external_value(PARAM_INT, 'student id')
1303                         )
1304                     )
1305                 )
1306             )
1307         );
1308     }
1310     /**
1311      * Describes the get_user_mappings return value
1312      * @return external_single_structure
1313      * @since  Moodle 2.6
1314      */
1315     public static function get_user_mappings_returns() {
1316         return new external_single_structure(
1317             array(
1318                 'assignments' => new external_multiple_structure(self::assign_user_mappings(), 'list of assign user mapping data'),
1319                 'warnings'      => new external_warnings('item is always \'assignment\'',
1320                     'when errorcode is 3 then itemid is an assignment id. When errorcode is 1, itemid is a course module id',
1321                     'errorcode can be 3 (no user mappings found) or 1 (no permission to get user mappings)')
1322             )
1323         );
1324     }
1326     /**
1327      * Describes the parameters for lock_submissions
1328      * @return external_external_function_parameters
1329      * @since  Moodle 2.6
1330      */
1331     public static function lock_submissions_parameters() {
1332         return new external_function_parameters(
1333             array(
1334                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1335                 'userids' => new external_multiple_structure(
1336                     new external_value(PARAM_INT, 'user id'),
1337                     '1 or more user ids',
1338                     VALUE_REQUIRED),
1339             )
1340         );
1341     }
1343     /**
1344      * Locks (prevent updates to) submissions in this assignment.
1345      *
1346      * @param int $assignmentid The id of the assignment
1347      * @param array $userids Array of user ids to lock
1348      * @return array of warnings for each submission that could not be locked.
1349      * @since Moodle 2.6
1350      */
1351     public static function lock_submissions($assignmentid, $userids) {
1352         global $CFG;
1354         $params = self::validate_parameters(self::lock_submissions_parameters(),
1355                         array('assignmentid' => $assignmentid,
1356                               'userids' => $userids));
1358         $cm = get_coursemodule_from_instance('assign', $params['assignmentid'], 0, false, MUST_EXIST);
1359         $context = context_module::instance($cm->id);
1360         self::validate_context($context);
1362         $assignment = new assign($context, $cm, null);
1364         $warnings = array();
1365         foreach ($params['userids'] as $userid) {
1366             if (!$assignment->lock_submission($userid)) {
1367                 $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'];
1368                 $warnings[] = self::generate_warning($params['assignmentid'],
1369                                                      'couldnotlock',
1370                                                      $detail);
1371             }
1372         }
1374         return $warnings;
1375     }
1377     /**
1378      * Describes the return value for lock_submissions
1379      *
1380      * @return external_single_structure
1381      * @since Moodle 2.6
1382      */
1383     public static function lock_submissions_returns() {
1384         return new external_warnings();
1385     }
1387     /**
1388      * Describes the parameters for revert_submissions_to_draft
1389      * @return external_external_function_parameters
1390      * @since  Moodle 2.6
1391      */
1392     public static function revert_submissions_to_draft_parameters() {
1393         return new external_function_parameters(
1394             array(
1395                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1396                 'userids' => new external_multiple_structure(
1397                     new external_value(PARAM_INT, 'user id'),
1398                     '1 or more user ids',
1399                     VALUE_REQUIRED),
1400             )
1401         );
1402     }
1404     /**
1405      * Reverts a list of user submissions to draft for a single assignment.
1406      *
1407      * @param int $assignmentid The id of the assignment
1408      * @param array $userids Array of user ids to revert
1409      * @return array of warnings for each submission that could not be reverted.
1410      * @since Moodle 2.6
1411      */
1412     public static function revert_submissions_to_draft($assignmentid, $userids) {
1413         global $CFG;
1415         $params = self::validate_parameters(self::revert_submissions_to_draft_parameters(),
1416                         array('assignmentid' => $assignmentid,
1417                               'userids' => $userids));
1419         $cm = get_coursemodule_from_instance('assign', $params['assignmentid'], 0, false, MUST_EXIST);
1420         $context = context_module::instance($cm->id);
1421         self::validate_context($context);
1423         $assignment = new assign($context, $cm, null);
1425         $warnings = array();
1426         foreach ($params['userids'] as $userid) {
1427             if (!$assignment->revert_to_draft($userid)) {
1428                 $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'];
1429                 $warnings[] = self::generate_warning($params['assignmentid'],
1430                                                      'couldnotrevert',
1431                                                      $detail);
1432             }
1433         }
1435         return $warnings;
1436     }
1438     /**
1439      * Describes the return value for revert_submissions_to_draft
1440      *
1441      * @return external_single_structure
1442      * @since Moodle 2.6
1443      */
1444     public static function revert_submissions_to_draft_returns() {
1445         return new external_warnings();
1446     }
1448     /**
1449      * Describes the parameters for unlock_submissions
1450      * @return external_external_function_parameters
1451      * @since  Moodle 2.6
1452      */
1453     public static function unlock_submissions_parameters() {
1454         return new external_function_parameters(
1455             array(
1456                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1457                 'userids' => new external_multiple_structure(
1458                     new external_value(PARAM_INT, 'user id'),
1459                     '1 or more user ids',
1460                     VALUE_REQUIRED),
1461             )
1462         );
1463     }
1465     /**
1466      * Locks (prevent updates to) submissions in this assignment.
1467      *
1468      * @param int $assignmentid The id of the assignment
1469      * @param array $userids Array of user ids to lock
1470      * @return array of warnings for each submission that could not be locked.
1471      * @since Moodle 2.6
1472      */
1473     public static function unlock_submissions($assignmentid, $userids) {
1474         global $CFG;
1476         $params = self::validate_parameters(self::unlock_submissions_parameters(),
1477                         array('assignmentid' => $assignmentid,
1478                               'userids' => $userids));
1480         $cm = get_coursemodule_from_instance('assign', $params['assignmentid'], 0, false, MUST_EXIST);
1481         $context = context_module::instance($cm->id);
1482         self::validate_context($context);
1484         $assignment = new assign($context, $cm, null);
1486         $warnings = array();
1487         foreach ($params['userids'] as $userid) {
1488             if (!$assignment->unlock_submission($userid)) {
1489                 $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'];
1490                 $warnings[] = self::generate_warning($params['assignmentid'],
1491                                                      'couldnotunlock',
1492                                                      $detail);
1493             }
1494         }
1496         return $warnings;
1497     }
1499     /**
1500      * Describes the return value for unlock_submissions
1501      *
1502      * @return external_single_structure
1503      * @since Moodle 2.6
1504      */
1505     public static function unlock_submissions_returns() {
1506         return new external_warnings();
1507     }
1509     /**
1510      * Describes the parameters for submit_for_grading
1511      * @return external_external_function_parameters
1512      * @since  Moodle 2.6
1513      */
1514     public static function submit_for_grading_parameters() {
1515         return new external_function_parameters(
1516             array(
1517                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1518                 'acceptsubmissionstatement' => new external_value(PARAM_BOOL, 'Accept the assignment submission statement')
1519             )
1520         );
1521     }
1523     /**
1524      * Submit the logged in users assignment for grading.
1525      *
1526      * @param int $assignmentid The id of the assignment
1527      * @return array of warnings to indicate any errors.
1528      * @since Moodle 2.6
1529      */
1530     public static function submit_for_grading($assignmentid, $acceptsubmissionstatement) {
1531         global $CFG, $USER;
1533         $params = self::validate_parameters(self::submit_for_grading_parameters(),
1534                                             array('assignmentid' => $assignmentid,
1535                                                   'acceptsubmissionstatement' => $acceptsubmissionstatement));
1537         $cm = get_coursemodule_from_instance('assign', $params['assignmentid'], 0, false, MUST_EXIST);
1538         $context = context_module::instance($cm->id);
1539         self::validate_context($context);
1541         $assignment = new assign($context, $cm, null);
1543         $warnings = array();
1544         $data = new stdClass();
1545         $data->submissionstatement = $params['acceptsubmissionstatement'];
1546         $notices = array();
1548         if (!$assignment->submit_for_grading($data, $notices)) {
1549             $detail = 'User id: ' . $USER->id . ', Assignment id: ' . $params['assignmentid'] . ' Notices:' . implode(', ', $notices);
1550             $warnings[] = self::generate_warning($params['assignmentid'],
1551                                                  'couldnotsubmitforgrading',
1552                                                  $detail);
1553         }
1555         return $warnings;
1556     }
1558     /**
1559      * Describes the return value for submit_for_grading
1560      *
1561      * @return external_single_structure
1562      * @since Moodle 2.6
1563      */
1564     public static function submit_for_grading_returns() {
1565         return new external_warnings();
1566     }
1568     /**
1569      * Describes the parameters for save_user_extensions
1570      * @return external_external_function_parameters
1571      * @since  Moodle 2.6
1572      */
1573     public static function save_user_extensions_parameters() {
1574         return new external_function_parameters(
1575             array(
1576                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1577                 'userids' => new external_multiple_structure(
1578                     new external_value(PARAM_INT, 'user id'),
1579                     '1 or more user ids',
1580                     VALUE_REQUIRED),
1581                 'dates' => new external_multiple_structure(
1582                     new external_value(PARAM_INT, 'dates'),
1583                     '1 or more extension dates (timestamp)',
1584                     VALUE_REQUIRED),
1585             )
1586         );
1587     }
1589     /**
1590      * Grant extension dates to students for an assignment.
1591      *
1592      * @param int $assignmentid The id of the assignment
1593      * @param array $userids Array of user ids to grant extensions to
1594      * @param array $dates Array of extension dates
1595      * @return array of warnings for each extension date that could not be granted
1596      * @since Moodle 2.6
1597      */
1598     public static function save_user_extensions($assignmentid, $userids, $dates) {
1599         global $CFG;
1601         $params = self::validate_parameters(self::save_user_extensions_parameters(),
1602                         array('assignmentid' => $assignmentid,
1603                               'userids' => $userids,
1604                               'dates' => $dates));
1606         if (count($params['userids']) != count($params['dates'])) {
1607             $detail = 'Length of userids and dates parameters differ.';
1608             $warnings[] = self::generate_warning($params['assignmentid'],
1609                                                  'invalidparameters',
1610                                                  $detail);
1612             return $warnings;
1613         }
1615         $cm = get_coursemodule_from_instance('assign', $params['assignmentid'], 0, false, MUST_EXIST);
1616         $context = context_module::instance($cm->id);
1617         self::validate_context($context);
1619         $assignment = new assign($context, $cm, null);
1621         $warnings = array();
1622         foreach ($params['userids'] as $idx => $userid) {
1623             $duedate = $params['dates'][$idx];
1624             if (!$assignment->save_user_extension($userid, $duedate)) {
1625                 $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'] . ', Extension date: ' . $duedate;
1626                 $warnings[] = self::generate_warning($params['assignmentid'],
1627                                                      'couldnotgrantextensions',
1628                                                      $detail);
1629             }
1630         }
1632         return $warnings;
1633     }
1635     /**
1636      * Describes the return value for save_user_extensions
1637      *
1638      * @return external_single_structure
1639      * @since Moodle 2.6
1640      */
1641     public static function save_user_extensions_returns() {
1642         return new external_warnings();
1643     }
1645     /**
1646      * Describes the parameters for reveal_identities
1647      * @return external_external_function_parameters
1648      * @since  Moodle 2.6
1649      */
1650     public static function reveal_identities_parameters() {
1651         return new external_function_parameters(
1652             array(
1653                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on')
1654             )
1655         );
1656     }
1658     /**
1659      * Reveal the identities of anonymous students to markers for a single assignment.
1660      *
1661      * @param int $assignmentid The id of the assignment
1662      * @return array of warnings to indicate any errors.
1663      * @since Moodle 2.6
1664      */
1665     public static function reveal_identities($assignmentid) {
1666         global $CFG, $USER;
1668         $params = self::validate_parameters(self::reveal_identities_parameters(),
1669                                             array('assignmentid' => $assignmentid));
1671         $cm = get_coursemodule_from_instance('assign', $params['assignmentid'], 0, false, MUST_EXIST);
1672         $context = context_module::instance($cm->id);
1673         self::validate_context($context);
1675         $assignment = new assign($context, $cm, null);
1677         $warnings = array();
1678         if (!$assignment->reveal_identities()) {
1679             $detail = 'User id: ' . $USER->id . ', Assignment id: ' . $params['assignmentid'];
1680             $warnings[] = self::generate_warning($params['assignmentid'],
1681                                                  'couldnotrevealidentities',
1682                                                  $detail);
1683         }
1685         return $warnings;
1686     }
1688     /**
1689      * Describes the return value for reveal_identities
1690      *
1691      * @return external_single_structure
1692      * @since Moodle 2.6
1693      */
1694     public static function reveal_identities_returns() {
1695         return new external_warnings();
1696     }
1698     /**
1699      * Describes the parameters for save_submission
1700      * @return external_external_function_parameters
1701      * @since  Moodle 2.6
1702      */
1703     public static function save_submission_parameters() {
1704         global $CFG;
1705         $instance = new assign(null, null, null);
1706         $pluginsubmissionparams = array();
1708         foreach ($instance->get_submission_plugins() as $plugin) {
1709             if ($plugin->is_visible()) {
1710                 $pluginparams = $plugin->get_external_parameters();
1711                 if (!empty($pluginparams)) {
1712                     $pluginsubmissionparams = array_merge($pluginsubmissionparams, $pluginparams);
1713                 }
1714             }
1715         }
1717         return new external_function_parameters(
1718             array(
1719                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1720                 'plugindata' => new external_single_structure(
1721                     $pluginsubmissionparams
1722                 )
1723             )
1724         );
1725     }
1727     /**
1728      * Save a student submission for a single assignment
1729      *
1730      * @param int $assignmentid The id of the assignment
1731      * @param array $plugindata - The submitted data for plugins
1732      * @return array of warnings to indicate any errors
1733      * @since Moodle 2.6
1734      */
1735     public static function save_submission($assignmentid, $plugindata) {
1736         global $CFG, $USER;
1738         $params = self::validate_parameters(self::save_submission_parameters(),
1739                                             array('assignmentid' => $assignmentid,
1740                                                   'plugindata' => $plugindata));
1742         $cm = get_coursemodule_from_instance('assign', $params['assignmentid'], 0, false, MUST_EXIST);
1743         $context = context_module::instance($cm->id);
1744         self::validate_context($context);
1746         $assignment = new assign($context, $cm, null);
1748         $notices = array();
1750         if (!$assignment->submissions_open($USER->id)) {
1751             $notices[] = get_string('duedatereached', 'assign');
1752         } else {
1753             $submissiondata = (object)$params['plugindata'];
1754             $assignment->save_submission($submissiondata, $notices);
1755         }
1757         $warnings = array();
1758         foreach ($notices as $notice) {
1759             $warnings[] = self::generate_warning($params['assignmentid'],
1760                                                  'couldnotsavesubmission',
1761                                                  $notice);
1762         }
1764         return $warnings;
1765     }
1767     /**
1768      * Describes the return value for save_submission
1769      *
1770      * @return external_single_structure
1771      * @since Moodle 2.6
1772      */
1773     public static function save_submission_returns() {
1774         return new external_warnings();
1775     }
1777     /**
1778      * Describes the parameters for save_grade
1779      * @return external_external_function_parameters
1780      * @since  Moodle 2.6
1781      */
1782     public static function save_grade_parameters() {
1783         global $CFG;
1784         require_once("$CFG->dirroot/grade/grading/lib.php");
1785         $instance = new assign(null, null, null);
1786         $pluginfeedbackparams = array();
1788         foreach ($instance->get_feedback_plugins() as $plugin) {
1789             if ($plugin->is_visible()) {
1790                 $pluginparams = $plugin->get_external_parameters();
1791                 if (!empty($pluginparams)) {
1792                     $pluginfeedbackparams = array_merge($pluginfeedbackparams, $pluginparams);
1793                 }
1794             }
1795         }
1797         $advancedgradingdata = array();
1798         $methods = array_keys(grading_manager::available_methods(false));
1799         foreach ($methods as $method) {
1800             require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
1801             $details  = call_user_func('gradingform_'.$method.'_controller::get_external_instance_filling_details');
1802             if (!empty($details)) {
1803                 $items = array();
1804                 foreach ($details as $key => $value) {
1805                     $value->required = VALUE_OPTIONAL;
1806                     unset($value->content->keys['id']);
1807                     $items[$key] = new external_multiple_structure (new external_single_structure(
1808                         array(
1809                             'criterionid' => new external_value(PARAM_INT, 'criterion id'),
1810                             'fillings' => $value
1811                         )
1812                     ));
1813                 }
1814                 $advancedgradingdata[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL);
1815             }
1816         }
1818         return new external_function_parameters(
1819             array(
1820                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1821                 'userid' => new external_value(PARAM_INT, 'The student id to operate on'),
1822                 'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user. Ignored if advanced grading used'),
1823                 'attemptnumber' => new external_value(PARAM_INT, 'The attempt number (-1 means latest attempt)'),
1824                 'addattempt' => new external_value(PARAM_BOOL, 'Allow another attempt if the attempt reopen method is manual'),
1825                 'workflowstate' => new external_value(PARAM_ALPHA, 'The next marking workflow state'),
1826                 'applytoall' => new external_value(PARAM_BOOL, 'If true, this grade will be applied ' .
1827                                                                'to all members ' .
1828                                                                'of the group (for group assignments).'),
1829                 'plugindata' => new external_single_structure($pluginfeedbackparams, 'plugin data', VALUE_DEFAULT, array()),
1830                 'advancedgradingdata' => new external_single_structure($advancedgradingdata, 'advanced grading data',
1831                                                                        VALUE_DEFAULT, array())
1832             )
1833         );
1834     }
1836     /**
1837      * Save a student grade for a single assignment.
1838      *
1839      * @param int $assignmentid The id of the assignment
1840      * @param int $userid The id of the user
1841      * @param float $grade The grade (ignored if the assignment uses advanced grading)
1842      * @param int $attemptnumber The attempt number
1843      * @param bool $addattempt Allow another attempt
1844      * @param string $workflowstate New workflow state
1845      * @param bool $applytoall Apply the grade to all members of the group
1846      * @param array $plugindata Custom data used by plugins
1847      * @param array $advancedgradingdata Advanced grading data
1848      * @return null
1849      * @since Moodle 2.6
1850      */
1851     public static function save_grade($assignmentid,
1852                                       $userid,
1853                                       $grade,
1854                                       $attemptnumber,
1855                                       $addattempt,
1856                                       $workflowstate,
1857                                       $applytoall,
1858                                       $plugindata = array(),
1859                                       $advancedgradingdata = array()) {
1860         global $CFG, $USER;
1862         $params = self::validate_parameters(self::save_grade_parameters(),
1863                                             array('assignmentid' => $assignmentid,
1864                                                   'userid' => $userid,
1865                                                   'grade' => $grade,
1866                                                   'attemptnumber' => $attemptnumber,
1867                                                   'workflowstate' => $workflowstate,
1868                                                   'addattempt' => $addattempt,
1869                                                   'applytoall' => $applytoall,
1870                                                   'plugindata' => $plugindata,
1871                                                   'advancedgradingdata' => $advancedgradingdata));
1873         $cm = get_coursemodule_from_instance('assign', $params['assignmentid'], 0, false, MUST_EXIST);
1874         $context = context_module::instance($cm->id);
1875         self::validate_context($context);
1877         $assignment = new assign($context, $cm, null);
1879         $gradedata = (object)$params['plugindata'];
1881         $gradedata->addattempt = $params['addattempt'];
1882         $gradedata->attemptnumber = $params['attemptnumber'];
1883         $gradedata->workflowstate = $params['workflowstate'];
1884         $gradedata->applytoall = $params['applytoall'];
1885         $gradedata->grade = $params['grade'];
1887         if (!empty($params['advancedgradingdata'])) {
1888             $advancedgrading = array();
1889             $criteria = reset($params['advancedgradingdata']);
1890             foreach ($criteria as $key => $criterion) {
1891                 $details = array();
1892                 foreach ($criterion as $value) {
1893                     foreach ($value['fillings'] as $filling) {
1894                         $details[$value['criterionid']] = $filling;
1895                     }
1896                 }
1897                 $advancedgrading[$key] = $details;
1898             }
1899             $gradedata->advancedgrading = $advancedgrading;
1900         }
1902         $assignment->save_grade($params['userid'], $gradedata);
1904         return null;
1905     }
1907     /**
1908      * Describes the return value for save_grade
1909      *
1910      * @return external_single_structure
1911      * @since Moodle 2.6
1912      */
1913     public static function save_grade_returns() {
1914         return null;
1915     }
1917     /**
1918      * Describes the parameters for save_grades
1919      * @return external_external_function_parameters
1920      * @since  Moodle 2.7
1921      */
1922     public static function save_grades_parameters() {
1923         global $CFG;
1924         require_once("$CFG->dirroot/grade/grading/lib.php");
1925         $instance = new assign(null, null, null);
1926         $pluginfeedbackparams = array();
1928         foreach ($instance->get_feedback_plugins() as $plugin) {
1929             if ($plugin->is_visible()) {
1930                 $pluginparams = $plugin->get_external_parameters();
1931                 if (!empty($pluginparams)) {
1932                     $pluginfeedbackparams = array_merge($pluginfeedbackparams, $pluginparams);
1933                 }
1934             }
1935         }
1937         $advancedgradingdata = array();
1938         $methods = array_keys(grading_manager::available_methods(false));
1939         foreach ($methods as $method) {
1940             require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
1941             $details  = call_user_func('gradingform_'.$method.'_controller::get_external_instance_filling_details');
1942             if (!empty($details)) {
1943                 $items = array();
1944                 foreach ($details as $key => $value) {
1945                     $value->required = VALUE_OPTIONAL;
1946                     unset($value->content->keys['id']);
1947                     $items[$key] = new external_multiple_structure (new external_single_structure(
1948                         array(
1949                             'criterionid' => new external_value(PARAM_INT, 'criterion id'),
1950                             'fillings' => $value
1951                         )
1952                     ));
1953                 }
1954                 $advancedgradingdata[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL);
1955             }
1956         }
1958         return new external_function_parameters(
1959             array(
1960                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1961                 'applytoall' => new external_value(PARAM_BOOL, 'If true, this grade will be applied ' .
1962                                                                'to all members ' .
1963                                                                'of the group (for group assignments).'),
1964                 'grades' => new external_multiple_structure(
1965                     new external_single_structure(
1966                         array (
1967                             'userid' => new external_value(PARAM_INT, 'The student id to operate on'),
1968                             'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user. '.
1969                                                                        'Ignored if advanced grading used'),
1970                             'attemptnumber' => new external_value(PARAM_INT, 'The attempt number (-1 means latest attempt)'),
1971                             'addattempt' => new external_value(PARAM_BOOL, 'Allow another attempt if manual attempt reopen method'),
1972                             'workflowstate' => new external_value(PARAM_ALPHA, 'The next marking workflow state'),
1973                             'plugindata' => new external_single_structure($pluginfeedbackparams, 'plugin data',
1974                                                                           VALUE_DEFAULT, array()),
1975                             'advancedgradingdata' => new external_single_structure($advancedgradingdata, 'advanced grading data',
1976                                                                                    VALUE_DEFAULT, array())
1977                         )
1978                     )
1979                 )
1980             )
1981         );
1982     }
1984     /**
1985      * Save multiple student grades for a single assignment.
1986      *
1987      * @param int $assignmentid The id of the assignment
1988      * @param boolean $applytoall If set to true and this is a team assignment,
1989      * apply the grade to all members of the group
1990      * @param array $grades grade data for one or more students that includes
1991      *                  userid - The id of the student being graded
1992      *                  grade - The grade (ignored if the assignment uses advanced grading)
1993      *                  attemptnumber - The attempt number
1994      *                  addattempt - Allow another attempt
1995      *                  workflowstate - New workflow state
1996      *                  plugindata - Custom data used by plugins
1997      *                  advancedgradingdata - Optional Advanced grading data
1998      * @throws invalid_parameter_exception if multiple grades are supplied for
1999      * a team assignment that has $applytoall set to true
2000      * @return null
2001      * @since Moodle 2.7
2002      */
2003     public static function save_grades($assignmentid, $applytoall = false, $grades) {
2004         global $CFG, $USER;
2006         $params = self::validate_parameters(self::save_grades_parameters(),
2007                                             array('assignmentid' => $assignmentid,
2008                                                   'applytoall' => $applytoall,
2009                                                   'grades' => $grades));
2011         $cm = get_coursemodule_from_instance('assign', $params['assignmentid'], 0, false, MUST_EXIST);
2012         $context = context_module::instance($cm->id);
2013         self::validate_context($context);
2014         $assignment = new assign($context, $cm, null);
2016         if ($assignment->get_instance()->teamsubmission && $params['applytoall']) {
2017             // Check that only 1 user per submission group is provided.
2018             $groupids = array();
2019             foreach ($params['grades'] as $gradeinfo) {
2020                 $group = $assignment->get_submission_group($gradeinfo['userid']);
2021                 if (in_array($group->id, $groupids)) {
2022                     throw new invalid_parameter_exception('Multiple grades for the same team have been supplied '
2023                                                           .' this is not permitted when the applytoall flag is set');
2024                 } else {
2025                     $groupids[] = $group->id;
2026                 }
2027             }
2028         }
2030         foreach ($params['grades'] as $gradeinfo) {
2031             $gradedata = (object)$gradeinfo['plugindata'];
2032             $gradedata->addattempt = $gradeinfo['addattempt'];
2033             $gradedata->attemptnumber = $gradeinfo['attemptnumber'];
2034             $gradedata->workflowstate = $gradeinfo['workflowstate'];
2035             $gradedata->applytoall = $params['applytoall'];
2036             $gradedata->grade = $gradeinfo['grade'];
2038             if (!empty($gradeinfo['advancedgradingdata'])) {
2039                 $advancedgrading = array();
2040                 $criteria = reset($gradeinfo['advancedgradingdata']);
2041                 foreach ($criteria as $key => $criterion) {
2042                     $details = array();
2043                     foreach ($criterion as $value) {
2044                         foreach ($value['fillings'] as $filling) {
2045                             $details[$value['criterionid']] = $filling;
2046                         }
2047                     }
2048                     $advancedgrading[$key] = $details;
2049                 }
2050                 $gradedata->advancedgrading = $advancedgrading;
2051             }
2052             $assignment->save_grade($gradeinfo['userid'], $gradedata);
2053         }
2055         return null;
2056     }
2058     /**
2059      * Describes the return value for save_grades
2060      *
2061      * @return external_single_structure
2062      * @since Moodle 2.7
2063      */
2064     public static function save_grades_returns() {
2065         return null;
2066     }
2068     /**
2069      * Describes the parameters for copy_previous_attempt
2070      * @return external_external_function_parameters
2071      * @since  Moodle 2.6
2072      */
2073     public static function copy_previous_attempt_parameters() {
2074         return new external_function_parameters(
2075             array(
2076                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
2077             )
2078         );
2079     }
2081     /**
2082      * Copy a students previous attempt to a new attempt.
2083      *
2084      * @param int $assignmentid
2085      * @return array of warnings to indicate any errors.
2086      * @since Moodle 2.6
2087      */
2088     public static function copy_previous_attempt($assignmentid) {
2089         global $CFG, $USER;
2091         $params = self::validate_parameters(self::copy_previous_attempt_parameters(),
2092                                             array('assignmentid' => $assignmentid));
2094         $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
2095         $context = context_module::instance($cm->id);
2096         self::validate_context($context);
2098         $assignment = new assign($context, $cm, null);
2100         $notices = array();
2102         $assignment->copy_previous_attempt($submissiondata, $notices);
2104         $warnings = array();
2105         foreach ($notices as $notice) {
2106             $warnings[] = self::generate_warning($assignmentid,
2107                                                  'couldnotcopyprevioussubmission',
2108                                                  $notice);
2109         }
2111         return $warnings;
2112     }
2114     /**
2115      * Describes the return value for save_submission
2116      *
2117      * @return external_single_structure
2118      * @since Moodle 2.6
2119      */
2120     public static function copy_previous_attempt_returns() {
2121         return new external_warnings();
2122     }
2124     /**
2125      * Returns description of method parameters
2126      *
2127      * @return external_function_parameters
2128      * @since Moodle 3.0
2129      */
2130     public static function view_grading_table_parameters() {
2131         return new external_function_parameters(
2132             array(
2133                 'assignid' => new external_value(PARAM_INT, 'assign instance id')
2134             )
2135         );
2136     }
2138     /**
2139      * Trigger the grading_table_viewed event.
2140      *
2141      * @param int $assignid the assign instance id
2142      * @return array of warnings and status result
2143      * @since Moodle 3.0
2144      * @throws moodle_exception
2145      */
2146     public static function view_grading_table($assignid) {
2147         global $DB, $CFG;
2149         $params = self::validate_parameters(self::view_grading_table_parameters(),
2150                                             array(
2151                                                 'assignid' => $assignid
2152                                             ));
2153         $warnings = array();
2155         // Request and permission validation.
2156         $assign = $DB->get_record('assign', array('id' => $params['assignid']), 'id', MUST_EXIST);
2157         list($course, $cm) = get_course_and_cm_from_instance($assign, 'assign');
2159         $context = context_module::instance($cm->id);
2160         self::validate_context($context);
2162         require_capability('mod/assign:view', $context);
2164         $assign = new assign($context, null, null);
2165         $assign->require_view_grades();
2166         \mod_assign\event\grading_table_viewed::create_from_assign($assign)->trigger();
2168         $result = array();
2169         $result['status'] = true;
2170         $result['warnings'] = $warnings;
2171         return $result;
2172     }
2174     /**
2175      * Returns description of method result value
2176      *
2177      * @return external_description
2178      * @since Moodle 3.0
2179      */
2180     public static function view_grading_table_returns() {
2181         return new external_single_structure(
2182             array(
2183                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2184                 'warnings' => new external_warnings()
2185             )
2186         );
2187     }
2188     /**
2189      * Describes the parameters for view_submission_status.
2190      *
2191      * @return external_external_function_parameters
2192      * @since Moodle 3.1
2193      */
2194     public static function view_submission_status_parameters() {
2195         return new external_function_parameters (
2196             array(
2197                 'assignid' => new external_value(PARAM_INT, 'assign instance id'),
2198             )
2199         );
2200     }
2202     /**
2203      * Trigger the submission status viewed event.
2204      *
2205      * @param int $assignid assign instance id
2206      * @return array of warnings and status result
2207      * @since Moodle 3.1
2208      */
2209     public static function view_submission_status($assignid) {
2210         global $DB, $CFG;
2212         $warnings = array();
2213         $params = array(
2214             'assignid' => $assignid,
2215         );
2216         $params = self::validate_parameters(self::view_submission_status_parameters(), $params);
2218         // Request and permission validation.
2219         $assign = $DB->get_record('assign', array('id' => $params['assignid']), 'id', MUST_EXIST);
2220         list($course, $cm) = get_course_and_cm_from_instance($assign, 'assign');
2222         $context = context_module::instance($cm->id);
2223         // Please, note that is not required to check mod/assign:view because is done by validate_context->require_login.
2224         self::validate_context($context);
2226         $assign = new assign($context, $cm, $course);
2227         \mod_assign\event\submission_status_viewed::create_from_assign($assign)->trigger();
2229         $result = array();
2230         $result['status'] = true;
2231         $result['warnings'] = $warnings;
2232         return $result;
2233     }
2235     /**
2236      * Describes the view_submission_status return value.
2237      *
2238      * @return external_single_structure
2239      * @since Moodle 3.1
2240      */
2241     public static function view_submission_status_returns() {
2242         return new external_single_structure(
2243             array(
2244                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2245                 'warnings' => new external_warnings(),
2246             )
2247         );
2248     }
2250     /**
2251      * Describes the parameters for get_submission_status.
2252      *
2253      * @return external_external_function_parameters
2254      * @since Moodle 3.1
2255      */
2256     public static function get_submission_status_parameters() {
2257         return new external_function_parameters (
2258             array(
2259                 'assignid' => new external_value(PARAM_INT, 'assignment instance id'),
2260                 'userid' => new external_value(PARAM_INT, 'user id (empty for current user)', VALUE_DEFAULT, 0),
2261             )
2262         );
2263     }
2265     /**
2266      * Returns information about an assignment submission status for a given user.
2267      *
2268      * @param int $assignid assignment instance id
2269      * @param int $userid user id (empty for current user)
2270      * @return array of warnings and grading, status, feedback and previous attempts information
2271      * @since Moodle 3.1
2272      * @throws required_capability_exception
2273      */
2274     public static function get_submission_status($assignid, $userid = 0) {
2275         global $USER, $DB;
2277         $warnings = array();
2279         $params = array(
2280             'assignid' => $assignid,
2281             'userid' => $userid,
2282         );
2283         $params = self::validate_parameters(self::get_submission_status_parameters(), $params);
2285         // Request and permission validation.
2286         $assign = $DB->get_record('assign', array('id' => $params['assignid']), 'id', MUST_EXIST);
2287         list($course, $cm) = get_course_and_cm_from_instance($assign, 'assign');
2289         $context = context_module::instance($cm->id);
2290         self::validate_context($context);
2292         $assign = new assign($context, $cm, $course);
2294         // Default value for userid.
2295         if (empty($params['userid'])) {
2296             $params['userid'] = $USER->id;
2297         }
2298         $user = core_user::get_user($params['userid'], '*', MUST_EXIST);
2299         core_user::require_active_user($user);
2301         if (!$assign->can_view_submission($user->id)) {
2302             throw new required_capability_exception($context, 'mod/assign:viewgrades', 'nopermission', '');
2303         }
2305         $gradingsummary = $lastattempt = $feedback = $previousattempts = null;
2307         // Get the renderable since it contais all the info we need.
2308         if ($assign->can_view_grades()) {
2309             $gradingsummary = $assign->get_assign_grading_summary_renderable();
2310         }
2312         // Retrieve the rest of the renderable objects.
2313         if (has_capability('mod/assign:submit', $assign->get_context(), $user)) {
2314             $lastattempt = $assign->get_assign_submission_status_renderable($user, true);
2315         }
2317         $feedback = $assign->get_assign_feedback_status_renderable($user);
2319         $previousattempts = $assign->get_assign_attempt_history_renderable($user);
2321         // Now, build the result.
2322         $result = array();
2324         // First of all, grading summary, this is suitable for teachers/managers.
2325         if ($gradingsummary) {
2326             $result['gradingsummary'] = $gradingsummary;
2327         }
2329         // Did we submit anything?
2330         if ($lastattempt) {
2331             $submissionplugins = $assign->get_submission_plugins();
2333             if (empty($lastattempt->submission)) {
2334                 unset($lastattempt->submission);
2335             } else {
2336                 $lastattempt->submission->plugins = self::get_plugins_data($assign, $submissionplugins, $lastattempt->submission);
2337             }
2339             if (empty($lastattempt->teamsubmission)) {
2340                 unset($lastattempt->teamsubmission);
2341             } else {
2342                 $lastattempt->teamsubmission->plugins = self::get_plugins_data($assign, $submissionplugins,
2343                                                                                 $lastattempt->teamsubmission);
2344             }
2346             // We need to change the type of some of the structures retrieved from the renderable.
2347             if (!empty($lastattempt->submissiongroup)) {
2348                 $lastattempt->submissiongroup = $lastattempt->submissiongroup->id;
2349             }
2350             if (!empty($lastattempt->usergroups)) {
2351                 $lastattempt->usergroups = array_keys($lastattempt->usergroups);
2352             }
2353             // We cannot use array_keys here.
2354             if (!empty($lastattempt->submissiongroupmemberswhoneedtosubmit)) {
2355                 $lastattempt->submissiongroupmemberswhoneedtosubmit = array_map(
2356                                                                             function($e){
2357                                                                                 return $e->id;
2358                                                                             },
2359                                                                             $lastattempt->submissiongroupmemberswhoneedtosubmit);
2360             }
2362             $result['lastattempt'] = $lastattempt;
2363         }
2365         // The feedback for our latest submission.
2366         if ($feedback) {
2367             if ($feedback->grade) {
2368                 $feedbackplugins = $assign->get_feedback_plugins();
2369                 $feedback->plugins = self::get_plugins_data($assign, $feedbackplugins, $feedback->grade);
2370             } else {
2371                 unset($feedback->plugins);
2372                 unset($feedback->grade);
2373             }
2375             $result['feedback'] = $feedback;
2376         }
2378         // Retrieve only previous attempts.
2379         if ($previousattempts and count($previousattempts->submissions) > 1) {
2380             // Don't show the last one because it is the current submission.
2381             array_pop($previousattempts->submissions);
2383             // Show newest to oldest.
2384             $previousattempts->submissions = array_reverse($previousattempts->submissions);
2386             foreach ($previousattempts->submissions as $i => $submission) {
2387                 $attempt = array();
2389                 $grade = null;
2390                 foreach ($previousattempts->grades as $onegrade) {
2391                     if ($onegrade->attemptnumber == $submission->attemptnumber) {
2392                         $grade = $onegrade;
2393                         break;
2394                     }
2395                 }
2397                 $attempt['attemptnumber'] = $submission->attemptnumber;
2399                 if ($submission) {
2400                     $submission->plugins = self::get_plugins_data($assign, $previousattempts->submissionplugins, $submission);
2401                     $attempt['submission'] = $submission;
2402                 }
2404                 if ($grade) {
2405                     // From object to id.
2406                     $grade->grader = $grade->grader->id;
2407                     $feedbackplugins = self::get_plugins_data($assign, $previousattempts->feedbackplugins, $grade);
2409                     $attempt['grade'] = $grade;
2410                     $attempt['feedbackplugins'] = $feedbackplugins;
2411                 }
2412                 $result['previousattempts'][] = $attempt;
2413             }
2414         }
2416         $result['warnings'] = $warnings;
2417         return $result;
2418     }
2420     /**
2421      * Describes the get_submission_status return value.
2422      *
2423      * @return external_single_structure
2424      * @since Moodle 3.1
2425      */
2426     public static function get_submission_status_returns() {
2427         return new external_single_structure(
2428             array(
2429                 'gradingsummary' => new external_single_structure(
2430                     array(
2431                         'participantcount' => new external_value(PARAM_INT, 'Number of users who can submit.'),
2432                         'submissiondraftscount' => new external_value(PARAM_INT, 'Number of submissions in draft status.'),
2433                         'submissiondraftscount' => new external_value(PARAM_INT, 'Number of submissions in draft status.'),
2434                         'submissionsenabled' => new external_value(PARAM_BOOL, 'Whether submissions are enabled or not.'),
2435                         'submissionssubmittedcount' => new external_value(PARAM_INT, 'Number of submissions in submitted status.'),
2436                         'submissionsneedgradingcount' => new external_value(PARAM_INT, 'Number of submissions that need grading.'),
2437                         'warnofungroupedusers' => new external_value(PARAM_BOOL, 'Whether we need to warn people that there
2438                                                                         are users without groups.'),
2439                     ), 'Grading information.', VALUE_OPTIONAL
2440                 ),
2441                 'lastattempt' => new external_single_structure(
2442                     array(
2443                         'submission' => self::get_submission_structure(VALUE_OPTIONAL),
2444                         'teamsubmission' => self::get_submission_structure(VALUE_OPTIONAL),
2445                         'submissiongroup' => new external_value(PARAM_INT, 'The submission group id (for group submissions only).',
2446                                                                 VALUE_OPTIONAL),
2447                         'submissiongroupmemberswhoneedtosubmit' => new external_multiple_structure(
2448                             new external_value(PARAM_INT, 'USER id.'),
2449                             'List of users who still need to submit (for group submissions only).',
2450                             VALUE_OPTIONAL
2451                         ),
2452                         'submissionsenabled' => new external_value(PARAM_BOOL, 'Whether submissions are enabled or not.'),
2453                         'locked' => new external_value(PARAM_BOOL, 'Whether new submissions are locked.'),
2454                         'graded' => new external_value(PARAM_BOOL, 'Whether the submission is graded.'),
2455                         'canedit' => new external_value(PARAM_BOOL, 'Whether the user can edit the current submission.'),
2456                         'cansubmit' => new external_value(PARAM_BOOL, 'Whether the user can submit.'),
2457                         'extensionduedate' => new external_value(PARAM_INT, 'Extension due date.'),
2458                         'blindmarking' => new external_value(PARAM_BOOL, 'Whether blind marking is enabled.'),
2459                         'gradingstatus' => new external_value(PARAM_ALPHANUMEXT, 'Grading status.'),
2460                         'usergroups' => new external_multiple_structure(
2461                             new external_value(PARAM_INT, 'Group id.'), 'User groups in the course.'
2462                         ),
2463                     ), 'Last attempt information.', VALUE_OPTIONAL
2464                 ),
2465                 'feedback' => new external_single_structure(
2466                     array(
2467                         'grade' => self::get_grade_structure(VALUE_OPTIONAL),
2468                         'gradefordisplay' => new external_value(PARAM_RAW, 'Grade rendered into a format suitable for display.'),
2469                         'gradeddate' => new external_value(PARAM_INT, 'The date the user was graded.'),
2470                         'plugins' => new external_multiple_structure(self::get_plugin_structure(), 'Plugins info.', VALUE_OPTIONAL),
2471                     ), 'Feedback for the last attempt.', VALUE_OPTIONAL
2472                 ),
2473                 'previousattempts' => new external_multiple_structure(
2474                     new external_single_structure(
2475                         array(
2476                             'attemptnumber' => new external_value(PARAM_INT, 'Attempt number.'),
2477                             'submission' => self::get_submission_structure(VALUE_OPTIONAL),
2478                             'grade' => self::get_grade_structure(VALUE_OPTIONAL),
2479                             'feedbackplugins' => new external_multiple_structure(self::get_plugin_structure(), 'Feedback info.',
2480                                                                                     VALUE_OPTIONAL),
2481                         )
2482                     ), 'List all the previous attempts did by the user.', VALUE_OPTIONAL
2483                 ),
2484                 'warnings' => new external_warnings(),
2485             )
2486         );
2487     }