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