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