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