b7325e2afbd3225ae38c6dfd7c1a7beba4dcdd79
[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' => $module->name,
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()) {
466                         list($assignment['intro'], $assignment['introformat']) = external_format_text($module->intro,
467                             $module->introformat, $context->id, 'mod_assign', 'intro', null);
468                         $assignment['introfiles'] = external_util::get_area_files($context->id, 'mod_assign', 'intro', false,
469                                                                                     false);
471                         $assignment['introattachments'] = external_util::get_area_files($context->id, 'mod_assign',
472                                                             ASSIGN_INTROATTACHMENT_FILEAREA, 0);
473                     }
475                     if ($module->requiresubmissionstatement) {
476                         // Submission statement is required, return the submission statement value.
477                         $adminconfig = get_config('assign');
478                         // Single submission.
479                         if (!$module->teamsubmission) {
480                             list($assignment['submissionstatement'], $assignment['submissionstatementformat']) =
481                                 external_format_text($adminconfig->submissionstatement, FORMAT_MOODLE, $context->id,
482                                     'mod_assign', '', 0);
483                         } else { // Team submission.
484                             // One user can submit for the whole team.
485                             if (!empty($adminconfig->submissionstatementteamsubmission) && !$module->requireallteammemberssubmit) {
486                                 list($assignment['submissionstatement'], $assignment['submissionstatementformat']) =
487                                     external_format_text($adminconfig->submissionstatementteamsubmission,
488                                         FORMAT_MOODLE, $context->id, 'mod_assign', '', 0);
489                             } else if (!empty($adminconfig->submissionstatementteamsubmissionallsubmit) &&
490                                 $module->requireallteammemberssubmit) {
491                                 // All team members must submit.
492                                 list($assignment['submissionstatement'], $assignment['submissionstatementformat']) =
493                                     external_format_text($adminconfig->submissionstatementteamsubmissionallsubmit,
494                                         FORMAT_MOODLE, $context->id, 'mod_assign', '', 0);
495                             }
496                         }
497                     }
499                     $assignmentarray[] = $assignment;
500                 }
501             }
502             $coursearray[]= array(
503                 'id' => $courses[$id]->id,
504                 'fullname' => external_format_string($courses[$id]->fullname, $course->contextid),
505                 'shortname' => external_format_string($courses[$id]->shortname, $course->contextid),
506                 'timemodified' => $courses[$id]->timemodified,
507                 'assignments' => $assignmentarray
508             );
509         }
511         $result = array(
512             'courses' => $coursearray,
513             'warnings' => $warnings
514         );
515         return $result;
516     }
518     /**
519      * Creates an assignment external_single_structure
520      *
521      * @return external_single_structure
522      * @since Moodle 2.4
523      */
524     private static function get_assignments_assignment_structure() {
525         return new external_single_structure(
526             array(
527                 'id' => new external_value(PARAM_INT, 'assignment id'),
528                 'cmid' => new external_value(PARAM_INT, 'course module id'),
529                 'course' => new external_value(PARAM_INT, 'course id'),
530                 'name' => new external_value(PARAM_TEXT, 'assignment name'),
531                 'nosubmissions' => new external_value(PARAM_INT, 'no submissions'),
532                 'submissiondrafts' => new external_value(PARAM_INT, 'submissions drafts'),
533                 'sendnotifications' => new external_value(PARAM_INT, 'send notifications'),
534                 'sendlatenotifications' => new external_value(PARAM_INT, 'send notifications'),
535                 'sendstudentnotifications' => new external_value(PARAM_INT, 'send student notifications (default)'),
536                 'duedate' => new external_value(PARAM_INT, 'assignment due date'),
537                 'allowsubmissionsfromdate' => new external_value(PARAM_INT, 'allow submissions from date'),
538                 'grade' => new external_value(PARAM_INT, 'grade type'),
539                 'timemodified' => new external_value(PARAM_INT, 'last time assignment was modified'),
540                 'completionsubmit' => new external_value(PARAM_INT, 'if enabled, set activity as complete following submission'),
541                 'cutoffdate' => new external_value(PARAM_INT, 'date after which submission is not accepted without an extension'),
542                 'gradingduedate' => new external_value(PARAM_INT, 'the expected date for marking the submissions'),
543                 'teamsubmission' => new external_value(PARAM_INT, 'if enabled, students submit as a team'),
544                 'requireallteammemberssubmit' => new external_value(PARAM_INT, 'if enabled, all team members must submit'),
545                 'teamsubmissiongroupingid' => new external_value(PARAM_INT, 'the grouping id for the team submission groups'),
546                 'blindmarking' => new external_value(PARAM_INT, 'if enabled, hide identities until reveal identities actioned'),
547                 'hidegrader' => new external_value(PARAM_INT, 'If enabled, hide grader to student'),
548                 'revealidentities' => new external_value(PARAM_INT, 'show identities for a blind marking assignment'),
549                 'attemptreopenmethod' => new external_value(PARAM_TEXT, 'method used to control opening new attempts'),
550                 'maxattempts' => new external_value(PARAM_INT, 'maximum number of attempts allowed'),
551                 'markingworkflow' => new external_value(PARAM_INT, 'enable marking workflow'),
552                 'markingallocation' => new external_value(PARAM_INT, 'enable marking allocation'),
553                 'requiresubmissionstatement' => new external_value(PARAM_INT, 'student must accept submission statement'),
554                 'preventsubmissionnotingroup' => new external_value(PARAM_INT, 'Prevent submission not in group', VALUE_OPTIONAL),
555                 'submissionstatement' => new external_value(PARAM_RAW, 'Submission statement formatted.', VALUE_OPTIONAL),
556                 'submissionstatementformat' => new external_format_value('submissionstatement', VALUE_OPTIONAL),
557                 'configs' => new external_multiple_structure(self::get_assignments_config_structure(), 'configuration settings'),
558                 'intro' => new external_value(PARAM_RAW,
559                     'assignment intro, not allways returned because it deppends on the activity configuration', VALUE_OPTIONAL),
560                 'introformat' => new external_format_value('intro', VALUE_OPTIONAL),
561                 'introfiles' => new external_files('Files in the introduction text', VALUE_OPTIONAL),
562                 'introattachments' => new external_files('intro attachments files', VALUE_OPTIONAL),
563             ), 'assignment information object');
564     }
566     /**
567      * Creates an assign_plugin_config external_single_structure
568      *
569      * @return external_single_structure
570      * @since Moodle 2.4
571      */
572     private static function get_assignments_config_structure() {
573         return new external_single_structure(
574             array(
575                 'id' => new external_value(PARAM_INT, 'assign_plugin_config id', VALUE_OPTIONAL),
576                 'assignment' => new external_value(PARAM_INT, 'assignment id', VALUE_OPTIONAL),
577                 'plugin' => new external_value(PARAM_TEXT, 'plugin'),
578                 'subtype' => new external_value(PARAM_TEXT, 'subtype'),
579                 'name' => new external_value(PARAM_TEXT, 'name'),
580                 'value' => new external_value(PARAM_TEXT, 'value')
581             ), 'assignment configuration object'
582         );
583     }
585     /**
586      * Creates a course external_single_structure
587      *
588      * @return external_single_structure
589      * @since Moodle 2.4
590      */
591     private static function get_assignments_course_structure() {
592         return new external_single_structure(
593             array(
594                 'id' => new external_value(PARAM_INT, 'course id'),
595                 'fullname' => new external_value(PARAM_TEXT, 'course full name'),
596                 'shortname' => new external_value(PARAM_TEXT, 'course short name'),
597                 'timemodified' => new external_value(PARAM_INT, 'last time modified'),
598                 'assignments' => new external_multiple_structure(self::get_assignments_assignment_structure(), 'assignment info')
599               ), 'course information object'
600         );
601     }
603     /**
604      * Describes the return value for get_assignments
605      *
606      * @return external_single_structure
607      * @since Moodle 2.4
608      */
609     public static function get_assignments_returns() {
610         return new external_single_structure(
611             array(
612                 'courses' => new external_multiple_structure(self::get_assignments_course_structure(), 'list of courses'),
613                 'warnings'  => new external_warnings('item can be \'course\' (errorcode 1 or 2) or \'module\' (errorcode 1)',
614                     'When item is a course then itemid is a course id. When the item is a module then itemid is a module id',
615                     'errorcode can be 1 (no access rights) or 2 (not enrolled or no permissions)')
616             )
617         );
618     }
620     /**
621      * Return information (files and text fields) for the given plugins in the assignment.
622      *
623      * @param  assign $assign the assignment object
624      * @param  array $assignplugins array of assignment plugins (submission or feedback)
625      * @param  stdClass $item the item object (submission or grade)
626      * @return array an array containing the plugins returned information
627      */
628     private static function get_plugins_data($assign, $assignplugins, $item) {
629         global $CFG;
631         $plugins = array();
632         $fs = get_file_storage();
634         foreach ($assignplugins as $assignplugin) {
636             if (!$assignplugin->is_enabled() or !$assignplugin->is_visible()) {
637                 continue;
638             }
640             $plugin = array(
641                 'name' => $assignplugin->get_name(),
642                 'type' => $assignplugin->get_type()
643             );
644             // Subtype is 'assignsubmission', type is currently 'file' or 'onlinetext'.
645             $component = $assignplugin->get_subtype().'_'.$assignplugin->get_type();
647             $fileareas = $assignplugin->get_file_areas();
648             foreach ($fileareas as $filearea => $name) {
649                 $fileareainfo = array('area' => $filearea);
651                 $fileareainfo['files'] = external_util::get_area_files(
652                     $assign->get_context()->id,
653                     $component,
654                     $filearea,
655                     $item->id
656                 );
658                 $plugin['fileareas'][] = $fileareainfo;
659             }
661             $editorfields = $assignplugin->get_editor_fields();
662             foreach ($editorfields as $name => $description) {
663                 $editorfieldinfo = array(
664                     'name' => $name,
665                     'description' => $description,
666                     'text' => $assignplugin->get_editor_text($name, $item->id),
667                     'format' => $assignplugin->get_editor_format($name, $item->id)
668                 );
670                 // Now format the text.
671                 foreach ($fileareas as $filearea => $name) {
672                     list($editorfieldinfo['text'], $editorfieldinfo['format']) = external_format_text(
673                         $editorfieldinfo['text'], $editorfieldinfo['format'], $assign->get_context()->id,
674                         $component, $filearea, $item->id);
675                 }
677                 $plugin['editorfields'][] = $editorfieldinfo;
678             }
679             $plugins[] = $plugin;
680         }
681         return $plugins;
682     }
684     /**
685      * Describes the parameters for get_submissions
686      *
687      * @return external_function_parameters
688      * @since Moodle 2.5
689      */
690     public static function get_submissions_parameters() {
691         return new external_function_parameters(
692             array(
693                 'assignmentids' => new external_multiple_structure(
694                     new external_value(PARAM_INT, 'assignment id'),
695                     '1 or more assignment ids',
696                     VALUE_REQUIRED),
697                 'status' => new external_value(PARAM_ALPHA, 'status', VALUE_DEFAULT, ''),
698                 'since' => new external_value(PARAM_INT, 'submitted since', VALUE_DEFAULT, 0),
699                 'before' => new external_value(PARAM_INT, 'submitted before', VALUE_DEFAULT, 0)
700             )
701         );
702     }
704     /**
705      * Returns submissions for the requested assignment ids
706      *
707      * @param int[] $assignmentids
708      * @param string $status only return submissions with this status
709      * @param int $since only return submissions with timemodified >= since
710      * @param int $before only return submissions with timemodified <= before
711      * @return array of submissions for each requested assignment
712      * @since Moodle 2.5
713      */
714     public static function get_submissions($assignmentids, $status = '', $since = 0, $before = 0) {
715         global $DB, $CFG;
717         $params = self::validate_parameters(self::get_submissions_parameters(),
718                         array('assignmentids' => $assignmentids,
719                               'status' => $status,
720                               'since' => $since,
721                               'before' => $before));
723         $warnings = array();
724         $assignments = array();
726         // Check the user is allowed to get the submissions for the assignments requested.
727         $placeholders = array();
728         list($inorequalsql, $placeholders) = $DB->get_in_or_equal($params['assignmentids'], SQL_PARAMS_NAMED);
729         $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
730                "WHERE md.name = :modname AND cm.instance ".$inorequalsql;
731         $placeholders['modname'] = 'assign';
732         $cms = $DB->get_records_sql($sql, $placeholders);
733         $assigns = array();
734         foreach ($cms as $cm) {
735             try {
736                 $context = context_module::instance($cm->id);
737                 self::validate_context($context);
738                 $assign = new assign($context, null, null);
739                 $assign->require_view_grades();
740                 $assigns[] = $assign;
741             } catch (Exception $e) {
742                 $warnings[] = array(
743                     'item' => 'assignment',
744                     'itemid' => $cm->instance,
745                     'warningcode' => '1',
746                     'message' => 'No access rights in module context'
747                 );
748             }
749         }
751         foreach ($assigns as $assign) {
752             $submissions = array();
753             $placeholders = array('assignid1' => $assign->get_instance()->id,
754                                   'assignid2' => $assign->get_instance()->id);
756             $submissionmaxattempt = 'SELECT mxs.userid, MAX(mxs.attemptnumber) AS maxattempt
757                                      FROM {assign_submission} mxs
758                                      WHERE mxs.assignment = :assignid1 GROUP BY mxs.userid';
760             $sql = "SELECT mas.id, mas.assignment,mas.userid,".
761                    "mas.timecreated,mas.timemodified,mas.status,mas.groupid,mas.attemptnumber ".
762                    "FROM {assign_submission} mas ".
763                    "JOIN ( " . $submissionmaxattempt . " ) smx ON mas.userid = smx.userid ".
764                    "WHERE mas.assignment = :assignid2 AND mas.attemptnumber = smx.maxattempt";
766             if (!empty($params['status'])) {
767                 $placeholders['status'] = $params['status'];
768                 $sql = $sql." AND mas.status = :status";
769             }
770             if (!empty($params['before'])) {
771                 $placeholders['since'] = $params['since'];
772                 $placeholders['before'] = $params['before'];
773                 $sql = $sql." AND mas.timemodified BETWEEN :since AND :before";
774             } else {
775                 $placeholders['since'] = $params['since'];
776                 $sql = $sql." AND mas.timemodified >= :since";
777             }
779             $submissionrecords = $DB->get_records_sql($sql, $placeholders);
781             if (!empty($submissionrecords)) {
782                 $submissionplugins = $assign->get_submission_plugins();
783                 foreach ($submissionrecords as $submissionrecord) {
784                     $submission = array(
785                         'id' => $submissionrecord->id,
786                         'userid' => $submissionrecord->userid,
787                         'timecreated' => $submissionrecord->timecreated,
788                         'timemodified' => $submissionrecord->timemodified,
789                         'status' => $submissionrecord->status,
790                         'attemptnumber' => $submissionrecord->attemptnumber,
791                         'groupid' => $submissionrecord->groupid,
792                         'plugins' => self::get_plugins_data($assign, $submissionplugins, $submissionrecord),
793                         'gradingstatus' => $assign->get_grading_status($submissionrecord->userid)
794                     );
796                     if ($assign->can_view_submission($submissionrecord->userid)) {
797                         $submissions[] = $submission;
798                     }
799                 }
800             } else {
801                 $warnings[] = array(
802                     'item' => 'module',
803                     'itemid' => $assign->get_instance()->id,
804                     'warningcode' => '3',
805                     'message' => 'No submissions found'
806                 );
807             }
809             $assignments[] = array(
810                 'assignmentid' => $assign->get_instance()->id,
811                 'submissions' => $submissions
812             );
814         }
816         $result = array(
817             'assignments' => $assignments,
818             'warnings' => $warnings
819         );
820         return $result;
821     }
823     /**
824      * Creates an assignment plugin structure.
825      *
826      * @return external_single_structure the plugin structure
827      */
828     private static function get_plugin_structure() {
829         return new external_single_structure(
830             array(
831                 'type' => new external_value(PARAM_TEXT, 'submission plugin type'),
832                 'name' => new external_value(PARAM_TEXT, 'submission plugin name'),
833                 'fileareas' => new external_multiple_structure(
834                     new external_single_structure(
835                         array (
836                             'area' => new external_value (PARAM_TEXT, 'file area'),
837                             'files' => new external_files('files', VALUE_OPTIONAL),
838                         )
839                     ), 'fileareas', VALUE_OPTIONAL
840                 ),
841                 'editorfields' => new external_multiple_structure(
842                     new external_single_structure(
843                         array(
844                             'name' => new external_value(PARAM_TEXT, 'field name'),
845                             'description' => new external_value(PARAM_RAW, 'field description'),
846                             'text' => new external_value (PARAM_RAW, 'field value'),
847                             'format' => new external_format_value ('text')
848                         )
849                     )
850                     , 'editorfields', VALUE_OPTIONAL
851                 )
852             )
853         );
854     }
856     /**
857      * Creates a submission structure.
858      *
859      * @return external_single_structure the submission structure
860      */
861     private static function get_submission_structure($required = VALUE_REQUIRED) {
862         return new external_single_structure(
863             array(
864                 'id' => new external_value(PARAM_INT, 'submission id'),
865                 'userid' => new external_value(PARAM_INT, 'student id'),
866                 'attemptnumber' => new external_value(PARAM_INT, 'attempt number'),
867                 'timecreated' => new external_value(PARAM_INT, 'submission creation time'),
868                 'timemodified' => new external_value(PARAM_INT, 'submission last modified time'),
869                 'status' => new external_value(PARAM_TEXT, 'submission status'),
870                 'groupid' => new external_value(PARAM_INT, 'group id'),
871                 'assignment' => new external_value(PARAM_INT, 'assignment id', VALUE_OPTIONAL),
872                 'latest' => new external_value(PARAM_INT, 'latest attempt', VALUE_OPTIONAL),
873                 'plugins' => new external_multiple_structure(self::get_plugin_structure(), 'plugins', VALUE_OPTIONAL),
874                 'gradingstatus' => new external_value(PARAM_ALPHANUMEXT, 'Grading status.', VALUE_OPTIONAL),
875             ), 'submission info', $required
876         );
877     }
879     /**
880      * Creates an assign_submissions external_single_structure
881      *
882      * @return external_single_structure
883      * @since Moodle 2.5
884      */
885     private static function get_submissions_structure() {
886         return new external_single_structure(
887             array (
888                 'assignmentid' => new external_value(PARAM_INT, 'assignment id'),
889                 'submissions' => new external_multiple_structure(self::get_submission_structure())
890             )
891         );
892     }
894     /**
895      * Describes the get_submissions return value
896      *
897      * @return external_single_structure
898      * @since Moodle 2.5
899      */
900     public static function get_submissions_returns() {
901         return new external_single_structure(
902             array(
903                 'assignments' => new external_multiple_structure(self::get_submissions_structure(), 'assignment submissions'),
904                 'warnings' => new external_warnings()
905             )
906         );
907     }
909     /**
910      * Describes the parameters for set_user_flags
911      * @return external_function_parameters
912      * @since  Moodle 2.6
913      */
914     public static function set_user_flags_parameters() {
915         return new external_function_parameters(
916             array(
917                 'assignmentid'    => new external_value(PARAM_INT, 'assignment id'),
918                 'userflags' => new external_multiple_structure(
919                     new external_single_structure(
920                         array(
921                             'userid'           => new external_value(PARAM_INT, 'student id'),
922                             'locked'           => new external_value(PARAM_INT, 'locked', VALUE_OPTIONAL),
923                             'mailed'           => new external_value(PARAM_INT, 'mailed', VALUE_OPTIONAL),
924                             'extensionduedate' => new external_value(PARAM_INT, 'extension due date', VALUE_OPTIONAL),
925                             'workflowstate'    => new external_value(PARAM_ALPHA, 'marking workflow state', VALUE_OPTIONAL),
926                             'allocatedmarker'  => new external_value(PARAM_INT, 'allocated marker', VALUE_OPTIONAL)
927                         )
928                     )
929                 )
930             )
931         );
932     }
934     /**
935      * Create or update user_flags records
936      *
937      * @param int $assignmentid the assignment for which the userflags are created or updated
938      * @param array $userflags  An array of userflags to create or update
939      * @return array containing success or failure information for each record
940      * @since Moodle 2.6
941      */
942     public static function set_user_flags($assignmentid, $userflags = array()) {
943         global $CFG, $DB;
945         $params = self::validate_parameters(self::set_user_flags_parameters(),
946                                             array('assignmentid' => $assignmentid,
947                                                   'userflags' => $userflags));
949         // Load assignment if it exists and if the user has the capability.
950         list($assign, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
951         require_capability('mod/assign:grade', $context);
953         $results = array();
954         foreach ($params['userflags'] as $userflag) {
955             $success = true;
956             $result = array();
958             $record = $assign->get_user_flags($userflag['userid'], false);
959             if ($record) {
960                 if (isset($userflag['locked'])) {
961                     $record->locked = $userflag['locked'];
962                 }
963                 if (isset($userflag['mailed'])) {
964                     $record->mailed = $userflag['mailed'];
965                 }
966                 if (isset($userflag['extensionduedate'])) {
967                     $record->extensionduedate = $userflag['extensionduedate'];
968                 }
969                 if (isset($userflag['workflowstate'])) {
970                     $record->workflowstate = $userflag['workflowstate'];
971                 }
972                 if (isset($userflag['allocatedmarker'])) {
973                     $record->allocatedmarker = $userflag['allocatedmarker'];
974                 }
975                 if ($assign->update_user_flags($record)) {
976                     $result['id'] = $record->id;
977                     $result['userid'] = $userflag['userid'];
978                 } else {
979                     $result['id'] = $record->id;
980                     $result['userid'] = $userflag['userid'];
981                     $result['errormessage'] = 'Record created but values could not be set';
982                 }
983             } else {
984                 $record = $assign->get_user_flags($userflag['userid'], true);
985                 $setfields = isset($userflag['locked'])
986                              || isset($userflag['mailed'])
987                              || isset($userflag['extensionduedate'])
988                              || isset($userflag['workflowstate'])
989                              || isset($userflag['allocatedmarker']);
990                 if ($record) {
991                     if ($setfields) {
992                         if (isset($userflag['locked'])) {
993                             $record->locked = $userflag['locked'];
994                         }
995                         if (isset($userflag['mailed'])) {
996                             $record->mailed = $userflag['mailed'];
997                         }
998                         if (isset($userflag['extensionduedate'])) {
999                             $record->extensionduedate = $userflag['extensionduedate'];
1000                         }
1001                         if (isset($userflag['workflowstate'])) {
1002                             $record->workflowstate = $userflag['workflowstate'];
1003                         }
1004                         if (isset($userflag['allocatedmarker'])) {
1005                             $record->allocatedmarker = $userflag['allocatedmarker'];
1006                         }
1007                         if ($assign->update_user_flags($record)) {
1008                             $result['id'] = $record->id;
1009                             $result['userid'] = $userflag['userid'];
1010                         } else {
1011                             $result['id'] = $record->id;
1012                             $result['userid'] = $userflag['userid'];
1013                             $result['errormessage'] = 'Record created but values could not be set';
1014                         }
1015                     } else {
1016                         $result['id'] = $record->id;
1017                         $result['userid'] = $userflag['userid'];
1018                     }
1019                 } else {
1020                     $result['id'] = -1;
1021                     $result['userid'] = $userflag['userid'];
1022                     $result['errormessage'] = 'Record could not be created';
1023                 }
1024             }
1026             $results[] = $result;
1027         }
1028         return $results;
1029     }
1031     /**
1032      * Describes the set_user_flags return value
1033      * @return external_multiple_structure
1034      * @since  Moodle 2.6
1035      */
1036     public static function set_user_flags_returns() {
1037         return new external_multiple_structure(
1038             new external_single_structure(
1039                 array(
1040                     'id' => new external_value(PARAM_INT, 'id of record if successful, -1 for failure'),
1041                     'userid' => new external_value(PARAM_INT, 'userid of record'),
1042                     'errormessage' => new external_value(PARAM_TEXT, 'Failure error message', VALUE_OPTIONAL)
1043                 )
1044             )
1045         );
1046     }
1048     /**
1049      * Describes the parameters for get_user_flags
1050      * @return external_function_parameters
1051      * @since  Moodle 2.6
1052      */
1053     public static function get_user_flags_parameters() {
1054         return new external_function_parameters(
1055             array(
1056                 'assignmentids' => new external_multiple_structure(
1057                     new external_value(PARAM_INT, 'assignment id'),
1058                     '1 or more assignment ids',
1059                     VALUE_REQUIRED)
1060             )
1061         );
1062     }
1064     /**
1065      * Returns user flag information from assign_user_flags for the requested assignment ids
1066      * @param int[] $assignmentids
1067      * @return array of user flag records for each requested assignment
1068      * @since  Moodle 2.6
1069      */
1070     public static function get_user_flags($assignmentids) {
1071         global $DB;
1072         $params = self::validate_parameters(self::get_user_flags_parameters(),
1073                         array('assignmentids' => $assignmentids));
1075         $assignments = array();
1076         $warnings = array();
1077         $requestedassignmentids = $params['assignmentids'];
1079         // Check the user is allowed to get the user flags for the assignments requested.
1080         $placeholders = array();
1081         list($sqlassignmentids, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1082         $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
1083                "WHERE md.name = :modname AND cm.instance ".$sqlassignmentids;
1084         $placeholders['modname'] = 'assign';
1085         $cms = $DB->get_records_sql($sql, $placeholders);
1086         foreach ($cms as $cm) {
1087             try {
1088                 $context = context_module::instance($cm->id);
1089                 self::validate_context($context);
1090                 require_capability('mod/assign:grade', $context);
1091             } catch (Exception $e) {
1092                 $requestedassignmentids = array_diff($requestedassignmentids, array($cm->instance));
1093                 $warning = array();
1094                 $warning['item'] = 'assignment';
1095                 $warning['itemid'] = $cm->instance;
1096                 $warning['warningcode'] = '1';
1097                 $warning['message'] = 'No access rights in module context';
1098                 $warnings[] = $warning;
1099             }
1100         }
1102         // Create the query and populate an array of assign_user_flags records from the recordset results.
1103         if (count ($requestedassignmentids) > 0) {
1104             $placeholders = array();
1105             list($inorequalsql, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1107             $sql = "SELECT auf.id,auf.assignment,auf.userid,auf.locked,auf.mailed,".
1108                    "auf.extensionduedate,auf.workflowstate,auf.allocatedmarker ".
1109                    "FROM {assign_user_flags} auf ".
1110                    "WHERE auf.assignment ".$inorequalsql.
1111                    " ORDER BY auf.assignment, auf.id";
1113             $rs = $DB->get_recordset_sql($sql, $placeholders);
1114             $currentassignmentid = null;
1115             $assignment = null;
1116             foreach ($rs as $rd) {
1117                 $userflag = array();
1118                 $userflag['id'] = $rd->id;
1119                 $userflag['userid'] = $rd->userid;
1120                 $userflag['locked'] = $rd->locked;
1121                 $userflag['mailed'] = $rd->mailed;
1122                 $userflag['extensionduedate'] = $rd->extensionduedate;
1123                 $userflag['workflowstate'] = $rd->workflowstate;
1124                 $userflag['allocatedmarker'] = $rd->allocatedmarker;
1126                 if (is_null($currentassignmentid) || ($rd->assignment != $currentassignmentid )) {
1127                     if (!is_null($assignment)) {
1128                         $assignments[] = $assignment;
1129                     }
1130                     $assignment = array();
1131                     $assignment['assignmentid'] = $rd->assignment;
1132                     $assignment['userflags'] = array();
1133                     $requestedassignmentids = array_diff($requestedassignmentids, array($rd->assignment));
1134                 }
1135                 $assignment['userflags'][] = $userflag;
1137                 $currentassignmentid = $rd->assignment;
1138             }
1139             if (!is_null($assignment)) {
1140                 $assignments[] = $assignment;
1141             }
1142             $rs->close();
1144         }
1146         foreach ($requestedassignmentids as $assignmentid) {
1147             $warning = array();
1148             $warning['item'] = 'assignment';
1149             $warning['itemid'] = $assignmentid;
1150             $warning['warningcode'] = '3';
1151             $warning['message'] = 'No user flags found';
1152             $warnings[] = $warning;
1153         }
1155         $result = array();
1156         $result['assignments'] = $assignments;
1157         $result['warnings'] = $warnings;
1158         return $result;
1159     }
1161     /**
1162      * Creates an assign_user_flags external_single_structure
1163      * @return external_single_structure
1164      * @since  Moodle 2.6
1165      */
1166     private static function assign_user_flags() {
1167         return new external_single_structure(
1168             array (
1169                 'assignmentid'    => new external_value(PARAM_INT, 'assignment id'),
1170                 'userflags'   => new external_multiple_structure(new external_single_structure(
1171                         array(
1172                             'id'               => new external_value(PARAM_INT, 'user flag id'),
1173                             'userid'           => new external_value(PARAM_INT, 'student id'),
1174                             'locked'           => new external_value(PARAM_INT, 'locked'),
1175                             'mailed'           => new external_value(PARAM_INT, 'mailed'),
1176                             'extensionduedate' => new external_value(PARAM_INT, 'extension due date'),
1177                             'workflowstate'    => new external_value(PARAM_ALPHA, 'marking workflow state', VALUE_OPTIONAL),
1178                             'allocatedmarker'  => new external_value(PARAM_INT, 'allocated marker')
1179                         )
1180                     )
1181                 )
1182             )
1183         );
1184     }
1186     /**
1187      * Describes the get_user_flags return value
1188      * @return external_single_structure
1189      * @since  Moodle 2.6
1190      */
1191     public static function get_user_flags_returns() {
1192         return new external_single_structure(
1193             array(
1194                 'assignments' => new external_multiple_structure(self::assign_user_flags(), 'list of assign user flag information'),
1195                 'warnings'      => new external_warnings('item is always \'assignment\'',
1196                     'when errorcode is 3 then itemid is an assignment id. When errorcode is 1, itemid is a course module id',
1197                     'errorcode can be 3 (no user flags found) or 1 (no permission to get user flags)')
1198             )
1199         );
1200     }
1202     /**
1203      * Describes the parameters for get_user_mappings
1204      * @return external_function_parameters
1205      * @since  Moodle 2.6
1206      */
1207     public static function get_user_mappings_parameters() {
1208         return new external_function_parameters(
1209             array(
1210                 'assignmentids' => new external_multiple_structure(
1211                     new external_value(PARAM_INT, 'assignment id'),
1212                     '1 or more assignment ids',
1213                     VALUE_REQUIRED)
1214             )
1215         );
1216     }
1218     /**
1219      * Returns user mapping information from assign_user_mapping for the requested assignment ids
1220      * @param int[] $assignmentids
1221      * @return array of user mapping records for each requested assignment
1222      * @since  Moodle 2.6
1223      */
1224     public static function get_user_mappings($assignmentids) {
1225         global $DB;
1226         $params = self::validate_parameters(self::get_user_mappings_parameters(),
1227                         array('assignmentids' => $assignmentids));
1229         $assignments = array();
1230         $warnings = array();
1231         $requestedassignmentids = $params['assignmentids'];
1233         // Check the user is allowed to get the mappings for the assignments requested.
1234         $placeholders = array();
1235         list($sqlassignmentids, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1236         $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
1237                "WHERE md.name = :modname AND cm.instance ".$sqlassignmentids;
1238         $placeholders['modname'] = 'assign';
1239         $cms = $DB->get_records_sql($sql, $placeholders);
1240         foreach ($cms as $cm) {
1241             try {
1242                 $context = context_module::instance($cm->id);
1243                 self::validate_context($context);
1244                 require_capability('mod/assign:revealidentities', $context);
1245             } catch (Exception $e) {
1246                 $requestedassignmentids = array_diff($requestedassignmentids, array($cm->instance));
1247                 $warning = array();
1248                 $warning['item'] = 'assignment';
1249                 $warning['itemid'] = $cm->instance;
1250                 $warning['warningcode'] = '1';
1251                 $warning['message'] = 'No access rights in module context';
1252                 $warnings[] = $warning;
1253             }
1254         }
1256         // Create the query and populate an array of assign_user_mapping records from the recordset results.
1257         if (count ($requestedassignmentids) > 0) {
1258             $placeholders = array();
1259             list($inorequalsql, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
1261             $sql = "SELECT aum.id,aum.assignment,aum.userid ".
1262                    "FROM {assign_user_mapping} aum ".
1263                    "WHERE aum.assignment ".$inorequalsql.
1264                    " ORDER BY aum.assignment, aum.id";
1266             $rs = $DB->get_recordset_sql($sql, $placeholders);
1267             $currentassignmentid = null;
1268             $assignment = null;
1269             foreach ($rs as $rd) {
1270                 $mapping = array();
1271                 $mapping['id'] = $rd->id;
1272                 $mapping['userid'] = $rd->userid;
1274                 if (is_null($currentassignmentid) || ($rd->assignment != $currentassignmentid )) {
1275                     if (!is_null($assignment)) {
1276                         $assignments[] = $assignment;
1277                     }
1278                     $assignment = array();
1279                     $assignment['assignmentid'] = $rd->assignment;
1280                     $assignment['mappings'] = array();
1281                     $requestedassignmentids = array_diff($requestedassignmentids, array($rd->assignment));
1282                 }
1283                 $assignment['mappings'][] = $mapping;
1285                 $currentassignmentid = $rd->assignment;
1286             }
1287             if (!is_null($assignment)) {
1288                 $assignments[] = $assignment;
1289             }
1290             $rs->close();
1292         }
1294         foreach ($requestedassignmentids as $assignmentid) {
1295             $warning = array();
1296             $warning['item'] = 'assignment';
1297             $warning['itemid'] = $assignmentid;
1298             $warning['warningcode'] = '3';
1299             $warning['message'] = 'No mappings found';
1300             $warnings[] = $warning;
1301         }
1303         $result = array();
1304         $result['assignments'] = $assignments;
1305         $result['warnings'] = $warnings;
1306         return $result;
1307     }
1309     /**
1310      * Creates an assign_user_mappings external_single_structure
1311      * @return external_single_structure
1312      * @since  Moodle 2.6
1313      */
1314     private static function assign_user_mappings() {
1315         return new external_single_structure(
1316             array (
1317                 'assignmentid'    => new external_value(PARAM_INT, 'assignment id'),
1318                 'mappings'   => new external_multiple_structure(new external_single_structure(
1319                         array(
1320                             'id'     => new external_value(PARAM_INT, 'user mapping id'),
1321                             'userid' => new external_value(PARAM_INT, 'student id')
1322                         )
1323                     )
1324                 )
1325             )
1326         );
1327     }
1329     /**
1330      * Describes the get_user_mappings return value
1331      * @return external_single_structure
1332      * @since  Moodle 2.6
1333      */
1334     public static function get_user_mappings_returns() {
1335         return new external_single_structure(
1336             array(
1337                 'assignments' => new external_multiple_structure(self::assign_user_mappings(), 'list of assign user mapping data'),
1338                 'warnings'      => new external_warnings('item is always \'assignment\'',
1339                     'when errorcode is 3 then itemid is an assignment id. When errorcode is 1, itemid is a course module id',
1340                     'errorcode can be 3 (no user mappings found) or 1 (no permission to get user mappings)')
1341             )
1342         );
1343     }
1345     /**
1346      * Describes the parameters for lock_submissions
1347      * @return external_function_parameters
1348      * @since  Moodle 2.6
1349      */
1350     public static function lock_submissions_parameters() {
1351         return new external_function_parameters(
1352             array(
1353                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1354                 'userids' => new external_multiple_structure(
1355                     new external_value(PARAM_INT, 'user id'),
1356                     '1 or more user ids',
1357                     VALUE_REQUIRED),
1358             )
1359         );
1360     }
1362     /**
1363      * Locks (prevent updates to) submissions in this assignment.
1364      *
1365      * @param int $assignmentid The id of the assignment
1366      * @param array $userids Array of user ids to lock
1367      * @return array of warnings for each submission that could not be locked.
1368      * @since Moodle 2.6
1369      */
1370     public static function lock_submissions($assignmentid, $userids) {
1371         global $CFG;
1373         $params = self::validate_parameters(self::lock_submissions_parameters(),
1374                         array('assignmentid' => $assignmentid,
1375                               'userids' => $userids));
1377         list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1379         $warnings = array();
1380         foreach ($params['userids'] as $userid) {
1381             if (!$assignment->lock_submission($userid)) {
1382                 $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'];
1383                 $warnings[] = self::generate_warning($params['assignmentid'],
1384                                                      'couldnotlock',
1385                                                      $detail);
1386             }
1387         }
1389         return $warnings;
1390     }
1392     /**
1393      * Describes the return value for lock_submissions
1394      *
1395      * @return external_single_structure
1396      * @since Moodle 2.6
1397      */
1398     public static function lock_submissions_returns() {
1399         return new external_warnings();
1400     }
1402     /**
1403      * Describes the parameters for revert_submissions_to_draft
1404      * @return external_function_parameters
1405      * @since  Moodle 2.6
1406      */
1407     public static function revert_submissions_to_draft_parameters() {
1408         return new external_function_parameters(
1409             array(
1410                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1411                 'userids' => new external_multiple_structure(
1412                     new external_value(PARAM_INT, 'user id'),
1413                     '1 or more user ids',
1414                     VALUE_REQUIRED),
1415             )
1416         );
1417     }
1419     /**
1420      * Reverts a list of user submissions to draft for a single assignment.
1421      *
1422      * @param int $assignmentid The id of the assignment
1423      * @param array $userids Array of user ids to revert
1424      * @return array of warnings for each submission that could not be reverted.
1425      * @since Moodle 2.6
1426      */
1427     public static function revert_submissions_to_draft($assignmentid, $userids) {
1428         global $CFG;
1430         $params = self::validate_parameters(self::revert_submissions_to_draft_parameters(),
1431                         array('assignmentid' => $assignmentid,
1432                               'userids' => $userids));
1434         list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1436         $warnings = array();
1437         foreach ($params['userids'] as $userid) {
1438             if (!$assignment->revert_to_draft($userid)) {
1439                 $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'];
1440                 $warnings[] = self::generate_warning($params['assignmentid'],
1441                                                      'couldnotrevert',
1442                                                      $detail);
1443             }
1444         }
1446         return $warnings;
1447     }
1449     /**
1450      * Describes the return value for revert_submissions_to_draft
1451      *
1452      * @return external_single_structure
1453      * @since Moodle 2.6
1454      */
1455     public static function revert_submissions_to_draft_returns() {
1456         return new external_warnings();
1457     }
1459     /**
1460      * Describes the parameters for unlock_submissions
1461      * @return external_function_parameters
1462      * @since  Moodle 2.6
1463      */
1464     public static function unlock_submissions_parameters() {
1465         return new external_function_parameters(
1466             array(
1467                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1468                 'userids' => new external_multiple_structure(
1469                     new external_value(PARAM_INT, 'user id'),
1470                     '1 or more user ids',
1471                     VALUE_REQUIRED),
1472             )
1473         );
1474     }
1476     /**
1477      * Locks (prevent updates to) submissions in this assignment.
1478      *
1479      * @param int $assignmentid The id of the assignment
1480      * @param array $userids Array of user ids to lock
1481      * @return array of warnings for each submission that could not be locked.
1482      * @since Moodle 2.6
1483      */
1484     public static function unlock_submissions($assignmentid, $userids) {
1485         global $CFG;
1487         $params = self::validate_parameters(self::unlock_submissions_parameters(),
1488                         array('assignmentid' => $assignmentid,
1489                               'userids' => $userids));
1491         list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1493         $warnings = array();
1494         foreach ($params['userids'] as $userid) {
1495             if (!$assignment->unlock_submission($userid)) {
1496                 $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'];
1497                 $warnings[] = self::generate_warning($params['assignmentid'],
1498                                                      'couldnotunlock',
1499                                                      $detail);
1500             }
1501         }
1503         return $warnings;
1504     }
1506     /**
1507      * Describes the return value for unlock_submissions
1508      *
1509      * @return external_single_structure
1510      * @since Moodle 2.6
1511      */
1512     public static function unlock_submissions_returns() {
1513         return new external_warnings();
1514     }
1516     /**
1517      * Describes the parameters for submit_grading_form webservice.
1518      * @return external_function_parameters
1519      * @since  Moodle 3.1
1520      */
1521     public static function submit_grading_form_parameters() {
1522         return new external_function_parameters(
1523             array(
1524                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1525                 'userid' => new external_value(PARAM_INT, 'The user id the submission belongs to'),
1526                 'jsonformdata' => new external_value(PARAM_RAW, 'The data from the grading form, encoded as a json array')
1527             )
1528         );
1529     }
1531     /**
1532      * Submit the logged in users assignment for grading.
1533      *
1534      * @param int $assignmentid The id of the assignment
1535      * @param int $userid The id of the user the submission belongs to.
1536      * @param string $jsonformdata The data from the form, encoded as a json array.
1537      * @return array of warnings to indicate any errors.
1538      * @since Moodle 3.1
1539      */
1540     public static function submit_grading_form($assignmentid, $userid, $jsonformdata) {
1541         global $CFG, $USER;
1543         require_once($CFG->dirroot . '/mod/assign/locallib.php');
1544         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
1546         $params = self::validate_parameters(self::submit_grading_form_parameters(),
1547                                             array(
1548                                                 'assignmentid' => $assignmentid,
1549                                                 'userid' => $userid,
1550                                                 'jsonformdata' => $jsonformdata
1551                                             ));
1553         list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1555         $serialiseddata = json_decode($params['jsonformdata']);
1557         $data = array();
1558         parse_str($serialiseddata, $data);
1560         $warnings = array();
1562         $options = array(
1563             'userid' => $params['userid'],
1564             'attemptnumber' => $data['attemptnumber'],
1565             'rownum' => 0,
1566             'gradingpanel' => true
1567         );
1569         if (WS_SERVER) {
1570             // Assume form submission if coming from WS.
1571             $USER->ignoresesskey = true;
1572             $data['_qf__mod_assign_grade_form_'.$params['userid']] = 1;
1573         }
1575         $customdata = (object) $data;
1576         $formparams = array($assignment, $customdata, $options);
1578         // Data is injected into the form by the last param for the constructor.
1579         $mform = new mod_assign_grade_form(null, $formparams, 'post', '', null, true, $data);
1580         $validateddata = $mform->get_data();
1582         if ($validateddata) {
1583             $assignment->save_grade($params['userid'], $validateddata);
1584         } else {
1585             $warnings[] = self::generate_warning($params['assignmentid'],
1586                                                  'couldnotsavegrade',
1587                                                  'Form validation failed.');
1588         }
1591         return $warnings;
1592     }
1594     /**
1595      * Describes the return for submit_grading_form
1596      * @return external_function_parameters
1597      * @since  Moodle 3.1
1598      */
1599     public static function submit_grading_form_returns() {
1600         return new external_warnings();
1601     }
1603     /**
1604      * Describes the parameters for submit_for_grading
1605      * @return external_function_parameters
1606      * @since  Moodle 2.6
1607      */
1608     public static function submit_for_grading_parameters() {
1609         return new external_function_parameters(
1610             array(
1611                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1612                 'acceptsubmissionstatement' => new external_value(PARAM_BOOL, 'Accept the assignment submission statement')
1613             )
1614         );
1615     }
1617     /**
1618      * Submit the logged in users assignment for grading.
1619      *
1620      * @param int $assignmentid The id of the assignment
1621      * @return array of warnings to indicate any errors.
1622      * @since Moodle 2.6
1623      */
1624     public static function submit_for_grading($assignmentid, $acceptsubmissionstatement) {
1625         global $CFG, $USER;
1627         $params = self::validate_parameters(self::submit_for_grading_parameters(),
1628                                             array('assignmentid' => $assignmentid,
1629                                                   'acceptsubmissionstatement' => $acceptsubmissionstatement));
1631         list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1633         $warnings = array();
1634         $data = new stdClass();
1635         $data->submissionstatement = $params['acceptsubmissionstatement'];
1636         $notices = array();
1638         if (!$assignment->submit_for_grading($data, $notices)) {
1639             $detail = 'User id: ' . $USER->id . ', Assignment id: ' . $params['assignmentid'] . ' Notices:' . implode(', ', $notices);
1640             $warnings[] = self::generate_warning($params['assignmentid'],
1641                                                  'couldnotsubmitforgrading',
1642                                                  $detail);
1643         }
1645         return $warnings;
1646     }
1648     /**
1649      * Describes the return value for submit_for_grading
1650      *
1651      * @return external_single_structure
1652      * @since Moodle 2.6
1653      */
1654     public static function submit_for_grading_returns() {
1655         return new external_warnings();
1656     }
1658     /**
1659      * Describes the parameters for save_user_extensions
1660      * @return external_function_parameters
1661      * @since  Moodle 2.6
1662      */
1663     public static function save_user_extensions_parameters() {
1664         return new external_function_parameters(
1665             array(
1666                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1667                 'userids' => new external_multiple_structure(
1668                     new external_value(PARAM_INT, 'user id'),
1669                     '1 or more user ids',
1670                     VALUE_REQUIRED),
1671                 'dates' => new external_multiple_structure(
1672                     new external_value(PARAM_INT, 'dates'),
1673                     '1 or more extension dates (timestamp)',
1674                     VALUE_REQUIRED),
1675             )
1676         );
1677     }
1679     /**
1680      * Grant extension dates to students for an assignment.
1681      *
1682      * @param int $assignmentid The id of the assignment
1683      * @param array $userids Array of user ids to grant extensions to
1684      * @param array $dates Array of extension dates
1685      * @return array of warnings for each extension date that could not be granted
1686      * @since Moodle 2.6
1687      */
1688     public static function save_user_extensions($assignmentid, $userids, $dates) {
1689         global $CFG;
1691         $params = self::validate_parameters(self::save_user_extensions_parameters(),
1692                         array('assignmentid' => $assignmentid,
1693                               'userids' => $userids,
1694                               'dates' => $dates));
1696         if (count($params['userids']) != count($params['dates'])) {
1697             $detail = 'Length of userids and dates parameters differ.';
1698             $warnings[] = self::generate_warning($params['assignmentid'],
1699                                                  'invalidparameters',
1700                                                  $detail);
1702             return $warnings;
1703         }
1705         list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1707         $warnings = array();
1708         foreach ($params['userids'] as $idx => $userid) {
1709             $duedate = $params['dates'][$idx];
1710             if (!$assignment->save_user_extension($userid, $duedate)) {
1711                 $detail = 'User id: ' . $userid . ', Assignment id: ' . $params['assignmentid'] . ', Extension date: ' . $duedate;
1712                 $warnings[] = self::generate_warning($params['assignmentid'],
1713                                                      'couldnotgrantextensions',
1714                                                      $detail);
1715             }
1716         }
1718         return $warnings;
1719     }
1721     /**
1722      * Describes the return value for save_user_extensions
1723      *
1724      * @return external_single_structure
1725      * @since Moodle 2.6
1726      */
1727     public static function save_user_extensions_returns() {
1728         return new external_warnings();
1729     }
1731     /**
1732      * Describes the parameters for reveal_identities
1733      * @return external_function_parameters
1734      * @since  Moodle 2.6
1735      */
1736     public static function reveal_identities_parameters() {
1737         return new external_function_parameters(
1738             array(
1739                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on')
1740             )
1741         );
1742     }
1744     /**
1745      * Reveal the identities of anonymous students to markers for a single assignment.
1746      *
1747      * @param int $assignmentid The id of the assignment
1748      * @return array of warnings to indicate any errors.
1749      * @since Moodle 2.6
1750      */
1751     public static function reveal_identities($assignmentid) {
1752         global $CFG, $USER;
1754         $params = self::validate_parameters(self::reveal_identities_parameters(),
1755                                             array('assignmentid' => $assignmentid));
1757         list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1759         $warnings = array();
1760         if (!$assignment->reveal_identities()) {
1761             $detail = 'User id: ' . $USER->id . ', Assignment id: ' . $params['assignmentid'];
1762             $warnings[] = self::generate_warning($params['assignmentid'],
1763                                                  'couldnotrevealidentities',
1764                                                  $detail);
1765         }
1767         return $warnings;
1768     }
1770     /**
1771      * Describes the return value for reveal_identities
1772      *
1773      * @return external_single_structure
1774      * @since Moodle 2.6
1775      */
1776     public static function reveal_identities_returns() {
1777         return new external_warnings();
1778     }
1780     /**
1781      * Describes the parameters for save_submission
1782      * @return external_function_parameters
1783      * @since  Moodle 2.6
1784      */
1785     public static function save_submission_parameters() {
1786         global $CFG;
1787         $instance = new assign(null, null, null);
1788         $pluginsubmissionparams = array();
1790         foreach ($instance->get_submission_plugins() as $plugin) {
1791             if ($plugin->is_visible()) {
1792                 $pluginparams = $plugin->get_external_parameters();
1793                 if (!empty($pluginparams)) {
1794                     $pluginsubmissionparams = array_merge($pluginsubmissionparams, $pluginparams);
1795                 }
1796             }
1797         }
1799         return new external_function_parameters(
1800             array(
1801                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1802                 'plugindata' => new external_single_structure(
1803                     $pluginsubmissionparams
1804                 )
1805             )
1806         );
1807     }
1809     /**
1810      * Save a student submission for a single assignment
1811      *
1812      * @param int $assignmentid The id of the assignment
1813      * @param array $plugindata - The submitted data for plugins
1814      * @return array of warnings to indicate any errors
1815      * @since Moodle 2.6
1816      */
1817     public static function save_submission($assignmentid, $plugindata) {
1818         global $CFG, $USER;
1820         $params = self::validate_parameters(self::save_submission_parameters(),
1821                                             array('assignmentid' => $assignmentid,
1822                                                   'plugindata' => $plugindata));
1824         list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1826         $notices = array();
1828         $assignment->update_effective_access($USER->id);
1829         if (!$assignment->submissions_open($USER->id)) {
1830             $notices[] = get_string('duedatereached', 'assign');
1831         } else {
1832             $submissiondata = (object)$params['plugindata'];
1833             $assignment->save_submission($submissiondata, $notices);
1834         }
1836         $warnings = array();
1837         foreach ($notices as $notice) {
1838             $warnings[] = self::generate_warning($params['assignmentid'],
1839                                                  'couldnotsavesubmission',
1840                                                  $notice);
1841         }
1843         return $warnings;
1844     }
1846     /**
1847      * Describes the return value for save_submission
1848      *
1849      * @return external_single_structure
1850      * @since Moodle 2.6
1851      */
1852     public static function save_submission_returns() {
1853         return new external_warnings();
1854     }
1856     /**
1857      * Describes the parameters for save_grade
1858      * @return external_function_parameters
1859      * @since  Moodle 2.6
1860      */
1861     public static function save_grade_parameters() {
1862         global $CFG;
1863         require_once("$CFG->dirroot/grade/grading/lib.php");
1864         $instance = new assign(null, null, null);
1865         $pluginfeedbackparams = array();
1867         foreach ($instance->get_feedback_plugins() as $plugin) {
1868             if ($plugin->is_visible()) {
1869                 $pluginparams = $plugin->get_external_parameters();
1870                 if (!empty($pluginparams)) {
1871                     $pluginfeedbackparams = array_merge($pluginfeedbackparams, $pluginparams);
1872                 }
1873             }
1874         }
1876         $advancedgradingdata = array();
1877         $methods = array_keys(grading_manager::available_methods(false));
1878         foreach ($methods as $method) {
1879             require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
1880             $details  = call_user_func('gradingform_'.$method.'_controller::get_external_instance_filling_details');
1881             if (!empty($details)) {
1882                 $items = array();
1883                 foreach ($details as $key => $value) {
1884                     $value->required = VALUE_OPTIONAL;
1885                     unset($value->content->keys['id']);
1886                     $items[$key] = new external_multiple_structure (new external_single_structure(
1887                         array(
1888                             'criterionid' => new external_value(PARAM_INT, 'criterion id'),
1889                             'fillings' => $value
1890                         )
1891                     ));
1892                 }
1893                 $advancedgradingdata[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL);
1894             }
1895         }
1897         return new external_function_parameters(
1898             array(
1899                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
1900                 'userid' => new external_value(PARAM_INT, 'The student id to operate on'),
1901                 'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user. Ignored if advanced grading used'),
1902                 'attemptnumber' => new external_value(PARAM_INT, 'The attempt number (-1 means latest attempt)'),
1903                 'addattempt' => new external_value(PARAM_BOOL, 'Allow another attempt if the attempt reopen method is manual'),
1904                 'workflowstate' => new external_value(PARAM_ALPHA, 'The next marking workflow state'),
1905                 'applytoall' => new external_value(PARAM_BOOL, 'If true, this grade will be applied ' .
1906                                                                'to all members ' .
1907                                                                'of the group (for group assignments).'),
1908                 'plugindata' => new external_single_structure($pluginfeedbackparams, 'plugin data', VALUE_DEFAULT, array()),
1909                 'advancedgradingdata' => new external_single_structure($advancedgradingdata, 'advanced grading data',
1910                                                                        VALUE_DEFAULT, array())
1911             )
1912         );
1913     }
1915     /**
1916      * Save a student grade for a single assignment.
1917      *
1918      * @param int $assignmentid The id of the assignment
1919      * @param int $userid The id of the user
1920      * @param float $grade The grade (ignored if the assignment uses advanced grading)
1921      * @param int $attemptnumber The attempt number
1922      * @param bool $addattempt Allow another attempt
1923      * @param string $workflowstate New workflow state
1924      * @param bool $applytoall Apply the grade to all members of the group
1925      * @param array $plugindata Custom data used by plugins
1926      * @param array $advancedgradingdata Advanced grading data
1927      * @return null
1928      * @since Moodle 2.6
1929      */
1930     public static function save_grade($assignmentid,
1931                                       $userid,
1932                                       $grade,
1933                                       $attemptnumber,
1934                                       $addattempt,
1935                                       $workflowstate,
1936                                       $applytoall,
1937                                       $plugindata = array(),
1938                                       $advancedgradingdata = array()) {
1939         global $CFG, $USER;
1941         $params = self::validate_parameters(self::save_grade_parameters(),
1942                                             array('assignmentid' => $assignmentid,
1943                                                   'userid' => $userid,
1944                                                   'grade' => $grade,
1945                                                   'attemptnumber' => $attemptnumber,
1946                                                   'workflowstate' => $workflowstate,
1947                                                   'addattempt' => $addattempt,
1948                                                   'applytoall' => $applytoall,
1949                                                   'plugindata' => $plugindata,
1950                                                   'advancedgradingdata' => $advancedgradingdata));
1952         list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
1954         $gradedata = (object)$params['plugindata'];
1956         $gradedata->addattempt = $params['addattempt'];
1957         $gradedata->attemptnumber = $params['attemptnumber'];
1958         $gradedata->workflowstate = $params['workflowstate'];
1959         $gradedata->applytoall = $params['applytoall'];
1960         $gradedata->grade = $params['grade'];
1962         if (!empty($params['advancedgradingdata'])) {
1963             $advancedgrading = array();
1964             $criteria = reset($params['advancedgradingdata']);
1965             foreach ($criteria as $key => $criterion) {
1966                 $details = array();
1967                 foreach ($criterion as $value) {
1968                     foreach ($value['fillings'] as $filling) {
1969                         $details[$value['criterionid']] = $filling;
1970                     }
1971                 }
1972                 $advancedgrading[$key] = $details;
1973             }
1974             $gradedata->advancedgrading = $advancedgrading;
1975         }
1977         $assignment->save_grade($params['userid'], $gradedata);
1979         return null;
1980     }
1982     /**
1983      * Describes the return value for save_grade
1984      *
1985      * @return external_single_structure
1986      * @since Moodle 2.6
1987      */
1988     public static function save_grade_returns() {
1989         return null;
1990     }
1992     /**
1993      * Describes the parameters for save_grades
1994      * @return external_function_parameters
1995      * @since  Moodle 2.7
1996      */
1997     public static function save_grades_parameters() {
1998         global $CFG;
1999         require_once("$CFG->dirroot/grade/grading/lib.php");
2000         $instance = new assign(null, null, null);
2001         $pluginfeedbackparams = array();
2003         foreach ($instance->get_feedback_plugins() as $plugin) {
2004             if ($plugin->is_visible()) {
2005                 $pluginparams = $plugin->get_external_parameters();
2006                 if (!empty($pluginparams)) {
2007                     $pluginfeedbackparams = array_merge($pluginfeedbackparams, $pluginparams);
2008                 }
2009             }
2010         }
2012         $advancedgradingdata = array();
2013         $methods = array_keys(grading_manager::available_methods(false));
2014         foreach ($methods as $method) {
2015             require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
2016             $details  = call_user_func('gradingform_'.$method.'_controller::get_external_instance_filling_details');
2017             if (!empty($details)) {
2018                 $items = array();
2019                 foreach ($details as $key => $value) {
2020                     $value->required = VALUE_OPTIONAL;
2021                     unset($value->content->keys['id']);
2022                     $items[$key] = new external_multiple_structure (new external_single_structure(
2023                         array(
2024                             'criterionid' => new external_value(PARAM_INT, 'criterion id'),
2025                             'fillings' => $value
2026                         )
2027                     ));
2028                 }
2029                 $advancedgradingdata[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL);
2030             }
2031         }
2033         return new external_function_parameters(
2034             array(
2035                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
2036                 'applytoall' => new external_value(PARAM_BOOL, 'If true, this grade will be applied ' .
2037                                                                'to all members ' .
2038                                                                'of the group (for group assignments).'),
2039                 'grades' => new external_multiple_structure(
2040                     new external_single_structure(
2041                         array (
2042                             'userid' => new external_value(PARAM_INT, 'The student id to operate on'),
2043                             'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user. '.
2044                                                                        'Ignored if advanced grading used'),
2045                             'attemptnumber' => new external_value(PARAM_INT, 'The attempt number (-1 means latest attempt)'),
2046                             'addattempt' => new external_value(PARAM_BOOL, 'Allow another attempt if manual attempt reopen method'),
2047                             'workflowstate' => new external_value(PARAM_ALPHA, 'The next marking workflow state'),
2048                             'plugindata' => new external_single_structure($pluginfeedbackparams, 'plugin data',
2049                                                                           VALUE_DEFAULT, array()),
2050                             'advancedgradingdata' => new external_single_structure($advancedgradingdata, 'advanced grading data',
2051                                                                                    VALUE_DEFAULT, array())
2052                         )
2053                     )
2054                 )
2055             )
2056         );
2057     }
2059     /**
2060      * Save multiple student grades for a single assignment.
2061      *
2062      * @param int $assignmentid The id of the assignment
2063      * @param boolean $applytoall If set to true and this is a team assignment,
2064      * apply the grade to all members of the group
2065      * @param array $grades grade data for one or more students that includes
2066      *                  userid - The id of the student being graded
2067      *                  grade - The grade (ignored if the assignment uses advanced grading)
2068      *                  attemptnumber - The attempt number
2069      *                  addattempt - Allow another attempt
2070      *                  workflowstate - New workflow state
2071      *                  plugindata - Custom data used by plugins
2072      *                  advancedgradingdata - Optional Advanced grading data
2073      * @throws invalid_parameter_exception if multiple grades are supplied for
2074      * a team assignment that has $applytoall set to true
2075      * @return null
2076      * @since Moodle 2.7
2077      */
2078     public static function save_grades($assignmentid, $applytoall = false, $grades) {
2079         global $CFG, $USER;
2081         $params = self::validate_parameters(self::save_grades_parameters(),
2082                                             array('assignmentid' => $assignmentid,
2083                                                   'applytoall' => $applytoall,
2084                                                   'grades' => $grades));
2086         list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
2088         if ($assignment->get_instance()->teamsubmission && $params['applytoall']) {
2089             // Check that only 1 user per submission group is provided.
2090             $groupids = array();
2091             foreach ($params['grades'] as $gradeinfo) {
2092                 $group = $assignment->get_submission_group($gradeinfo['userid']);
2093                 if (in_array($group->id, $groupids)) {
2094                     throw new invalid_parameter_exception('Multiple grades for the same team have been supplied '
2095                                                           .' this is not permitted when the applytoall flag is set');
2096                 } else {
2097                     $groupids[] = $group->id;
2098                 }
2099             }
2100         }
2102         foreach ($params['grades'] as $gradeinfo) {
2103             $gradedata = (object)$gradeinfo['plugindata'];
2104             $gradedata->addattempt = $gradeinfo['addattempt'];
2105             $gradedata->attemptnumber = $gradeinfo['attemptnumber'];
2106             $gradedata->workflowstate = $gradeinfo['workflowstate'];
2107             $gradedata->applytoall = $params['applytoall'];
2108             $gradedata->grade = $gradeinfo['grade'];
2110             if (!empty($gradeinfo['advancedgradingdata'])) {
2111                 $advancedgrading = array();
2112                 $criteria = reset($gradeinfo['advancedgradingdata']);
2113                 foreach ($criteria as $key => $criterion) {
2114                     $details = array();
2115                     foreach ($criterion as $value) {
2116                         foreach ($value['fillings'] as $filling) {
2117                             $details[$value['criterionid']] = $filling;
2118                         }
2119                     }
2120                     $advancedgrading[$key] = $details;
2121                 }
2122                 $gradedata->advancedgrading = $advancedgrading;
2123             }
2124             $assignment->save_grade($gradeinfo['userid'], $gradedata);
2125         }
2127         return null;
2128     }
2130     /**
2131      * Describes the return value for save_grades
2132      *
2133      * @return external_single_structure
2134      * @since Moodle 2.7
2135      */
2136     public static function save_grades_returns() {
2137         return null;
2138     }
2140     /**
2141      * Describes the parameters for copy_previous_attempt
2142      * @return external_function_parameters
2143      * @since  Moodle 2.6
2144      */
2145     public static function copy_previous_attempt_parameters() {
2146         return new external_function_parameters(
2147             array(
2148                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
2149             )
2150         );
2151     }
2153     /**
2154      * Copy a students previous attempt to a new attempt.
2155      *
2156      * @param int $assignmentid
2157      * @return array of warnings to indicate any errors.
2158      * @since Moodle 2.6
2159      */
2160     public static function copy_previous_attempt($assignmentid) {
2162         $params = self::validate_parameters(self::copy_previous_attempt_parameters(),
2163                                             array('assignmentid' => $assignmentid));
2165         list($assignment, $course, $cm, $context) = self::validate_assign($params['assignmentid']);
2167         $notices = array();
2169         $assignment->copy_previous_attempt($notices);
2171         $warnings = array();
2172         foreach ($notices as $notice) {
2173             $warnings[] = self::generate_warning($assignmentid,
2174                                                  'couldnotcopyprevioussubmission',
2175                                                  $notice);
2176         }
2178         return $warnings;
2179     }
2181     /**
2182      * Describes the return value for save_submission
2183      *
2184      * @return external_single_structure
2185      * @since Moodle 2.6
2186      */
2187     public static function copy_previous_attempt_returns() {
2188         return new external_warnings();
2189     }
2191     /**
2192      * Returns description of method parameters
2193      *
2194      * @return external_function_parameters
2195      * @since Moodle 3.0
2196      */
2197     public static function view_grading_table_parameters() {
2198         return new external_function_parameters(
2199             array(
2200                 'assignid' => new external_value(PARAM_INT, 'assign instance id')
2201             )
2202         );
2203     }
2205     /**
2206      * Trigger the grading_table_viewed event.
2207      *
2208      * @param int $assignid the assign instance id
2209      * @return array of warnings and status result
2210      * @since Moodle 3.0
2211      * @throws moodle_exception
2212      */
2213     public static function view_grading_table($assignid) {
2215         $params = self::validate_parameters(self::view_grading_table_parameters(),
2216                                             array(
2217                                                 'assignid' => $assignid
2218                                             ));
2219         $warnings = array();
2221         list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2223         $assign->require_view_grades();
2224         \mod_assign\event\grading_table_viewed::create_from_assign($assign)->trigger();
2226         $result = array();
2227         $result['status'] = true;
2228         $result['warnings'] = $warnings;
2229         return $result;
2230     }
2232     /**
2233      * Returns description of method result value
2234      *
2235      * @return external_description
2236      * @since Moodle 3.0
2237      */
2238     public static function view_grading_table_returns() {
2239         return new external_single_structure(
2240             array(
2241                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2242                 'warnings' => new external_warnings()
2243             )
2244         );
2245     }
2247     /**
2248      * Describes the parameters for view_submission_status.
2249      *
2250      * @return external_function_parameters
2251      * @since Moodle 3.1
2252      */
2253     public static function view_submission_status_parameters() {
2254         return new external_function_parameters (
2255             array(
2256                 'assignid' => new external_value(PARAM_INT, 'assign instance id'),
2257             )
2258         );
2259     }
2261     /**
2262      * Trigger the submission status viewed event.
2263      *
2264      * @param int $assignid assign instance id
2265      * @return array of warnings and status result
2266      * @since Moodle 3.1
2267      */
2268     public static function view_submission_status($assignid) {
2270         $warnings = array();
2271         $params = array(
2272             'assignid' => $assignid,
2273         );
2274         $params = self::validate_parameters(self::view_submission_status_parameters(), $params);
2276         list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2278         \mod_assign\event\submission_status_viewed::create_from_assign($assign)->trigger();
2280         $result = array();
2281         $result['status'] = true;
2282         $result['warnings'] = $warnings;
2283         return $result;
2284     }
2286     /**
2287      * Describes the view_submission_status return value.
2288      *
2289      * @return external_single_structure
2290      * @since Moodle 3.1
2291      */
2292     public static function view_submission_status_returns() {
2293         return new external_single_structure(
2294             array(
2295                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2296                 'warnings' => new external_warnings(),
2297             )
2298         );
2299     }
2301     /**
2302      * Describes the parameters for get_submission_status.
2303      *
2304      * @return external_function_parameters
2305      * @since Moodle 3.1
2306      */
2307     public static function get_submission_status_parameters() {
2308         return new external_function_parameters (
2309             array(
2310                 'assignid' => new external_value(PARAM_INT, 'assignment instance id'),
2311                 'userid' => new external_value(PARAM_INT, 'user id (empty for current user)', VALUE_DEFAULT, 0),
2312                 'groupid' => new external_value(PARAM_INT, 'filter by users in group (used for generating the grading summary).
2313                     Empty or 0 for all groups information.', VALUE_DEFAULT, 0),
2314             )
2315         );
2316     }
2318     /**
2319      * Returns information about an assignment submission status for a given user.
2320      *
2321      * @param int $assignid assignment instance id
2322      * @param int $userid user id (empty for current user)
2323      * @param int $groupid filter by users in group id (used for generating the grading summary). Use 0 for all groups information.
2324      * @return array of warnings and grading, status, feedback and previous attempts information
2325      * @since Moodle 3.1
2326      * @throws required_capability_exception
2327      */
2328     public static function get_submission_status($assignid, $userid = 0, $groupid = 0) {
2329         global $USER;
2331         $warnings = array();
2333         $params = array(
2334             'assignid' => $assignid,
2335             'userid' => $userid,
2336             'groupid' => $groupid,
2337         );
2338         $params = self::validate_parameters(self::get_submission_status_parameters(), $params);
2340         list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2342         // Default value for userid.
2343         if (empty($params['userid'])) {
2344             $params['userid'] = $USER->id;
2345         }
2346         $user = core_user::get_user($params['userid'], '*', MUST_EXIST);
2347         core_user::require_active_user($user);
2349         if (!$assign->can_view_submission($user->id)) {
2350             throw new required_capability_exception($context, 'mod/assign:viewgrades', 'nopermission', '');
2351         }
2353         $gradingsummary = $lastattempt = $feedback = $previousattempts = null;
2355         // Get the renderable since it contais all the info we need.
2356         if (!empty($params['groupid'])) {
2357             $groupid = $params['groupid'];
2358             // Determine is the group is visible to user.
2359             if (!groups_group_visible($groupid, $course, $cm)) {
2360                 throw new moodle_exception('notingroup');
2361             }
2362         } else {
2363             // A null gorups means that following functions will calculate the current group.
2364             $groupid = null;
2365         }
2366         if ($assign->can_view_grades($groupid)) {
2367             $gradingsummary = $assign->get_assign_grading_summary_renderable($groupid);
2368         }
2370         // Retrieve the rest of the renderable objects.
2371         if (has_capability('mod/assign:submit', $assign->get_context(), $user)) {
2372             $lastattempt = $assign->get_assign_submission_status_renderable($user, true);
2373         }
2375         $feedback = $assign->get_assign_feedback_status_renderable($user);
2377         $previousattempts = $assign->get_assign_attempt_history_renderable($user);
2379         // Now, build the result.
2380         $result = array();
2382         // First of all, grading summary, this is suitable for teachers/managers.
2383         if ($gradingsummary) {
2384             $result['gradingsummary'] = $gradingsummary;
2385         }
2386         // Show the grader's identity if 'Hide Grader' is disabled or has the 'Show Hidden Grader' capability.
2387         $showgradername = (has_capability('mod/assign:showhiddengrader', $context) or
2388             !$assign->is_hidden_grader());
2390         // Did we submit anything?
2391         if ($lastattempt) {
2392             $submissionplugins = $assign->get_submission_plugins();
2394             if (empty($lastattempt->submission)) {
2395                 unset($lastattempt->submission);
2396             } else {
2397                 $lastattempt->submission->plugins = self::get_plugins_data($assign, $submissionplugins, $lastattempt->submission);
2398             }
2400             if (empty($lastattempt->teamsubmission)) {
2401                 unset($lastattempt->teamsubmission);
2402             } else {
2403                 $lastattempt->teamsubmission->plugins = self::get_plugins_data($assign, $submissionplugins,
2404                                                                                 $lastattempt->teamsubmission);
2405             }
2407             // We need to change the type of some of the structures retrieved from the renderable.
2408             if (!empty($lastattempt->submissiongroup)) {
2409                 $lastattempt->submissiongroup = $lastattempt->submissiongroup->id;
2410             } else {
2411                 unset($lastattempt->submissiongroup);
2412             }
2414             if (!empty($lastattempt->usergroups)) {
2415                 $lastattempt->usergroups = array_keys($lastattempt->usergroups);
2416             }
2417             // We cannot use array_keys here.
2418             if (!empty($lastattempt->submissiongroupmemberswhoneedtosubmit)) {
2419                 $lastattempt->submissiongroupmemberswhoneedtosubmit = array_map(
2420                                                                             function($e){
2421                                                                                 return $e->id;
2422                                                                             },
2423                                                                             $lastattempt->submissiongroupmemberswhoneedtosubmit);
2424             }
2426             // Can edit its own submission?
2427             $lastattempt->caneditowner = $assign->submissions_open($user->id) && $assign->is_any_submission_plugin_enabled();
2429             $result['lastattempt'] = $lastattempt;
2430         }
2432         // The feedback for our latest submission.
2433         if ($feedback) {
2434             if ($feedback->grade) {
2435                 if (!$showgradername) {
2436                     $feedback->grade->grader = -1;
2437                 }
2438                 $feedbackplugins = $assign->get_feedback_plugins();
2439                 $feedback->plugins = self::get_plugins_data($assign, $feedbackplugins, $feedback->grade);
2440             } else {
2441                 unset($feedback->plugins);
2442                 unset($feedback->grade);
2443             }
2445             $result['feedback'] = $feedback;
2446         }
2448         // Retrieve only previous attempts.
2449         if ($previousattempts and count($previousattempts->submissions) > 1) {
2450             // Don't show the last one because it is the current submission.
2451             array_pop($previousattempts->submissions);
2453             // Show newest to oldest.
2454             $previousattempts->submissions = array_reverse($previousattempts->submissions);
2456             foreach ($previousattempts->submissions as $i => $submission) {
2457                 $attempt = array();
2459                 $grade = null;
2460                 foreach ($previousattempts->grades as $onegrade) {
2461                     if ($onegrade->attemptnumber == $submission->attemptnumber) {
2462                         $grade = $onegrade;
2463                         break;
2464                     }
2465                 }
2467                 $attempt['attemptnumber'] = $submission->attemptnumber;
2469                 if ($submission) {
2470                     $submission->plugins = self::get_plugins_data($assign, $previousattempts->submissionplugins, $submission);
2471                     $attempt['submission'] = $submission;
2472                 }
2474                 if ($grade) {
2475                     // From object to id.
2476                     if (!$showgradername) {
2477                         $grade->grader = -1;
2478                     } else {
2479                         $grade->grader = $grade->grader->id;
2480                     }
2482                     $feedbackplugins = self::get_plugins_data($assign, $previousattempts->feedbackplugins, $grade);
2484                     $attempt['grade'] = $grade;
2485                     $attempt['feedbackplugins'] = $feedbackplugins;
2486                 }
2487                 $result['previousattempts'][] = $attempt;
2488             }
2489         }
2491         $result['warnings'] = $warnings;
2492         return $result;
2493     }
2495     /**
2496      * Describes the get_submission_status return value.
2497      *
2498      * @return external_single_structure
2499      * @since Moodle 3.1
2500      */
2501     public static function get_submission_status_returns() {
2502         return new external_single_structure(
2503             array(
2504                 'gradingsummary' => new external_single_structure(
2505                     array(
2506                         'participantcount' => new external_value(PARAM_INT, 'Number of users who can submit.'),
2507                         'submissiondraftscount' => new external_value(PARAM_INT, 'Number of submissions in draft status.'),
2508                         'submissiondraftscount' => new external_value(PARAM_INT, 'Number of submissions in draft status.'),
2509                         'submissionsenabled' => new external_value(PARAM_BOOL, 'Whether submissions are enabled or not.'),
2510                         'submissionssubmittedcount' => new external_value(PARAM_INT, 'Number of submissions in submitted status.'),
2511                         'submissionsneedgradingcount' => new external_value(PARAM_INT, 'Number of submissions that need grading.'),
2512                         'warnofungroupedusers' => new external_value(PARAM_ALPHA, 'Whether we need to warn people that there
2513                                                                         are users without groups (\'warningrequired\'), warn
2514                                                                         people there are users who will submit in the default
2515                                                                         group (\'warningoptional\') or no warning (\'\').'),
2516                     ), 'Grading information.', VALUE_OPTIONAL
2517                 ),
2518                 'lastattempt' => new external_single_structure(
2519                     array(
2520                         'submission' => self::get_submission_structure(VALUE_OPTIONAL),
2521                         'teamsubmission' => self::get_submission_structure(VALUE_OPTIONAL),
2522                         'submissiongroup' => new external_value(PARAM_INT, 'The submission group id (for group submissions only).',
2523                                                                 VALUE_OPTIONAL),
2524                         'submissiongroupmemberswhoneedtosubmit' => new external_multiple_structure(
2525                             new external_value(PARAM_INT, 'USER id.'),
2526                             'List of users who still need to submit (for group submissions only).',
2527                             VALUE_OPTIONAL
2528                         ),
2529                         'submissionsenabled' => new external_value(PARAM_BOOL, 'Whether submissions are enabled or not.'),
2530                         'locked' => new external_value(PARAM_BOOL, 'Whether new submissions are locked.'),
2531                         'graded' => new external_value(PARAM_BOOL, 'Whether the submission is graded.'),
2532                         'canedit' => new external_value(PARAM_BOOL, 'Whether the user can edit the current submission.'),
2533                         'caneditowner' => new external_value(PARAM_BOOL, 'Whether the owner of the submission can edit it.'),
2534                         'cansubmit' => new external_value(PARAM_BOOL, 'Whether the user can submit.'),
2535                         'extensionduedate' => new external_value(PARAM_INT, 'Extension due date.'),
2536                         'blindmarking' => new external_value(PARAM_BOOL, 'Whether blind marking is enabled.'),
2537                         'gradingstatus' => new external_value(PARAM_ALPHANUMEXT, 'Grading status.'),
2538                         'usergroups' => new external_multiple_structure(
2539                             new external_value(PARAM_INT, 'Group id.'), 'User groups in the course.'
2540                         ),
2541                     ), 'Last attempt information.', VALUE_OPTIONAL
2542                 ),
2543                 'feedback' => new external_single_structure(
2544                     array(
2545                         'grade' => self::get_grade_structure(VALUE_OPTIONAL),
2546                         'gradefordisplay' => new external_value(PARAM_RAW, 'Grade rendered into a format suitable for display.'),
2547                         'gradeddate' => new external_value(PARAM_INT, 'The date the user was graded.'),
2548                         'plugins' => new external_multiple_structure(self::get_plugin_structure(), 'Plugins info.', VALUE_OPTIONAL),
2549                     ), 'Feedback for the last attempt.', VALUE_OPTIONAL
2550                 ),
2551                 'previousattempts' => new external_multiple_structure(
2552                     new external_single_structure(
2553                         array(
2554                             'attemptnumber' => new external_value(PARAM_INT, 'Attempt number.'),
2555                             'submission' => self::get_submission_structure(VALUE_OPTIONAL),
2556                             'grade' => self::get_grade_structure(VALUE_OPTIONAL),
2557                             'feedbackplugins' => new external_multiple_structure(self::get_plugin_structure(), 'Feedback info.',
2558                                                                                     VALUE_OPTIONAL),
2559                         )
2560                     ), 'List all the previous attempts did by the user.', VALUE_OPTIONAL
2561                 ),
2562                 'warnings' => new external_warnings(),
2563             )
2564         );
2565     }
2567     /**
2568      * Returns description of method parameters
2569      *
2570      * @return external_function_parameters
2571      * @since Moodle 3.1
2572      */
2573     public static function list_participants_parameters() {
2574         return new external_function_parameters(
2575             array(
2576                 'assignid' => new external_value(PARAM_INT, 'assign instance id'),
2577                 'groupid' => new external_value(PARAM_INT, 'group id'),
2578                 'filter' => new external_value(PARAM_RAW, 'search string to filter the results'),
2579                 'skip' => new external_value(PARAM_INT, 'number of records to skip', VALUE_DEFAULT, 0),
2580                 'limit' => new external_value(PARAM_INT, 'maximum number of records to return', VALUE_DEFAULT, 0),
2581                 'onlyids' => new external_value(PARAM_BOOL, 'Do not return all user fields', VALUE_DEFAULT, false),
2582                 'includeenrolments' => new external_value(PARAM_BOOL, 'Do return courses where the user is enrolled',
2583                                                           VALUE_DEFAULT, true),
2584                 'tablesort' => new external_value(PARAM_BOOL, 'Apply current user table sorting preferences.',
2585                                                           VALUE_DEFAULT, false)
2586             )
2587         );
2588     }
2590     /**
2591      * Retrieves the list of students to be graded for the assignment.
2592      *
2593      * @param int $assignid the assign instance id
2594      * @param int $groupid the current group id
2595      * @param string $filter search string to filter the results.
2596      * @param int $skip Number of records to skip
2597      * @param int $limit Maximum number of records to return
2598      * @param bool $onlyids Only return user ids.
2599      * @param bool $includeenrolments Return courses where the user is enrolled.
2600      * @param bool $tablesort Apply current user table sorting params from the grading table.
2601      * @return array of warnings and status result
2602      * @since Moodle 3.1
2603      * @throws moodle_exception
2604      */
2605     public static function list_participants($assignid, $groupid, $filter, $skip,
2606             $limit, $onlyids, $includeenrolments, $tablesort) {
2607         global $DB, $CFG;
2608         require_once($CFG->dirroot . "/mod/assign/locallib.php");
2609         require_once($CFG->dirroot . "/user/lib.php");
2611         $params = self::validate_parameters(self::list_participants_parameters(),
2612                                             array(
2613                                                 'assignid' => $assignid,
2614                                                 'groupid' => $groupid,
2615                                                 'filter' => $filter,
2616                                                 'skip' => $skip,
2617                                                 'limit' => $limit,
2618                                                 'onlyids' => $onlyids,
2619                                                 'includeenrolments' => $includeenrolments,
2620                                                 'tablesort' => $tablesort
2621                                             ));
2622         $warnings = array();
2624         list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2626         require_capability('mod/assign:view', $context);
2628         $assign->require_view_grades();
2630         $participants = array();
2631         if (groups_group_visible($params['groupid'], $course, $cm)) {
2632             $participants = $assign->list_participants_with_filter_status_and_group($params['groupid'], $params['tablesort']);
2633         }
2635         $userfields = user_get_default_fields();
2636         if (!$params['includeenrolments']) {
2637             // Remove enrolled courses from users fields to be returned.
2638             $key = array_search('enrolledcourses', $userfields);
2639             if ($key !== false) {
2640                 unset($userfields[$key]);
2641             } else {
2642                 throw new moodle_exception('invaliduserfield', 'error', '', 'enrolledcourses');
2643             }
2644         }
2646         $result = array();
2647         $index = 0;
2648         foreach ($participants as $record) {
2649             // Preserve the fullname set by the assignment.
2650             $fullname = $record->fullname;
2651             $searchable = $fullname;
2652             $match = false;
2653             if (empty($filter)) {
2654                 $match = true;
2655             } else {
2656                 $filter = core_text::strtolower($filter);
2657                 $value = core_text::strtolower($searchable);
2658                 if (is_string($value) && (core_text::strpos($value, $filter) !== false)) {
2659                     $match = true;
2660                 }
2661             }
2662             if ($match) {
2663                 $index++;
2664                 if ($index <= $params['skip']) {
2665                     continue;
2666                 }
2667                 if (($params['limit'] > 0) && (($index - $params['skip']) > $params['limit'])) {
2668                     break;
2669                 }
2670                 // Now we do the expensive lookup of user details because we completed the filtering.
2671                 if (!$assign->is_blind_marking() && !$params['onlyids']) {
2672                     $userdetails = user_get_user_details($record, $course, $userfields);
2673                 } else {
2674                     $userdetails = array('id' => $record->id);
2675                 }
2676                 $userdetails['fullname'] = $fullname;
2677                 $userdetails['submitted'] = $record->submitted;
2678                 $userdetails['requiregrading'] = $record->requiregrading;
2679                 $userdetails['grantedextension'] = $record->grantedextension;
2680                 if (!empty($record->groupid)) {
2681                     $userdetails['groupid'] = $record->groupid;
2682                 }
2683                 if (!empty($record->groupname)) {
2684                     $userdetails['groupname'] = $record->groupname;
2685                 }
2686                 // Unique id is required for blind marking.
2687                 $userdetails['recordid'] = -1;
2688                 if (!empty($record->recordid)) {
2689                     $userdetails['recordid'] = $record->recordid;
2690                 }
2692                 $result[] = $userdetails;
2693             }
2694         }
2695         return $result;
2696     }
2698     /**
2699      * Returns the description of the results of the mod_assign_external::list_participants() method.
2700      *
2701      * @return external_description
2702      * @since Moodle 3.1
2703      */
2704     public static function list_participants_returns() {
2705         // Get user description.
2706         $userdesc = core_user_external::user_description();
2707         // List unneeded properties.
2708         $unneededproperties = [
2709             'auth', 'confirmed', 'lang', 'calendartype', 'theme', 'timezone', 'mailformat'
2710         ];
2711         // Remove unneeded properties for consistency with the previous version.
2712         foreach ($unneededproperties as $prop) {
2713             unset($userdesc->keys[$prop]);
2714         }
2716         // Override property attributes for consistency with the previous version.
2717         $userdesc->keys['fullname']->type = PARAM_NOTAGS;
2718         $userdesc->keys['profileimageurlsmall']->required = VALUE_OPTIONAL;
2719         $userdesc->keys['profileimageurl']->required = VALUE_OPTIONAL;
2720         $userdesc->keys['email']->desc = 'Email address';
2721         $userdesc->keys['idnumber']->desc = 'The idnumber of the user';
2722         $userdesc->keys['recordid'] = new external_value(PARAM_INT, 'record id');
2724         // Define other keys.
2725         $otherkeys = [
2726             'groups' => new external_multiple_structure(
2727                 new external_single_structure(
2728                     [
2729                         'id' => new external_value(PARAM_INT, 'group id'),
2730                         'name' => new external_value(PARAM_RAW, 'group name'),
2731                         'description' => new external_value(PARAM_RAW, 'group description'),
2732                     ]
2733                 ), 'user groups', VALUE_OPTIONAL
2734             ),
2735             'roles' => new external_multiple_structure(
2736                 new external_single_structure(
2737                     [
2738                         'roleid' => new external_value(PARAM_INT, 'role id'),
2739                         'name' => new external_value(PARAM_RAW, 'role name'),
2740                         'shortname' => new external_value(PARAM_ALPHANUMEXT, 'role shortname'),
2741                         'sortorder' => new external_value(PARAM_INT, 'role sortorder')
2742                     ]
2743                 ), 'user roles', VALUE_OPTIONAL
2744             ),
2745             'enrolledcourses' => new external_multiple_structure(
2746                 new external_single_structure(
2747                     [
2748                         'id' => new external_value(PARAM_INT, 'Id of the course'),
2749                         'fullname' => new external_value(PARAM_RAW, 'Fullname of the course'),
2750                         'shortname' => new external_value(PARAM_RAW, 'Shortname of the course')
2751                     ]
2752                 ), 'Courses where the user is enrolled - limited by which courses the user is able to see', VALUE_OPTIONAL
2753             ),
2754             'submitted' => new external_value(PARAM_BOOL, 'have they submitted their assignment'),
2755             'requiregrading' => new external_value(PARAM_BOOL, 'is their submission waiting for grading'),
2756             'grantedextension' => new external_value(PARAM_BOOL, 'have they been granted an extension'),
2757             'groupid' => new external_value(PARAM_INT, 'for group assignments this is the group id', VALUE_OPTIONAL),
2758             'groupname' => new external_value(PARAM_NOTAGS, 'for group assignments this is the group name', VALUE_OPTIONAL),
2759         ];
2761         // Merge keys.
2762         $userdesc->keys = array_merge($userdesc->keys, $otherkeys);
2763         return new external_multiple_structure($userdesc);
2764     }
2766     /**
2767      * Returns description of method parameters
2768      *
2769      * @return external_function_parameters
2770      * @since Moodle 3.1
2771      */
2772     public static function get_participant_parameters() {
2773         return new external_function_parameters(
2774             array(
2775                 'assignid' => new external_value(PARAM_INT, 'assign instance id'),
2776                 'userid' => new external_value(PARAM_INT, 'user id'),
2777                 'embeduser' => new external_value(PARAM_BOOL, 'user id', VALUE_DEFAULT, false),
2778             )
2779         );
2780     }
2782     /**
2783      * Get the user participating in the given assignment. An error with code 'usernotincourse'
2784      * is thrown is the user isn't a participant of the given assignment.
2785      *
2786      * @param int $assignid the assign instance id
2787      * @param int $userid the user id
2788      * @param bool $embeduser return user details (only applicable if not blind marking)
2789      * @return array of warnings and status result
2790      * @since Moodle 3.1
2791      * @throws moodle_exception
2792      */
2793     public static function get_participant($assignid, $userid, $embeduser) {
2794         global $DB, $CFG;
2795         require_once($CFG->dirroot . "/mod/assign/locallib.php");
2796         require_once($CFG->dirroot . "/user/lib.php");
2798         $params = self::validate_parameters(self::get_participant_parameters(), array(
2799             'assignid' => $assignid,
2800             'userid' => $userid,
2801             'embeduser' => $embeduser
2802         ));
2804         list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2805         $assign->require_view_grades();
2807         $participant = $assign->get_participant($params['userid']);
2809         // Update assign with override information.
2810         $assign->update_effective_access($params['userid']);
2812         if (!$participant) {
2813             // No participant found so we can return early.
2814             throw new moodle_exception('usernotincourse');
2815         }
2817         $return = array(
2818             'id' => $participant->id,
2819             'fullname' => $participant->fullname,
2820             'submitted' => $participant->submitted,
2821             'requiregrading' => $participant->requiregrading,
2822             'grantedextension' => $participant->grantedextension,
2823             'blindmarking' => $assign->is_blind_marking(),
2824             'allowsubmissionsfromdate' => $assign->get_instance()->allowsubmissionsfromdate,
2825             'duedate' => $assign->get_instance()->duedate,
2826             'cutoffdate' => $assign->get_instance()->cutoffdate,
2827             'duedatestr' => userdate($assign->get_instance()->duedate, get_string('strftimedatetime', 'langconfig')),
2828         );
2830         if (!empty($participant->groupid)) {
2831             $return['groupid'] = $participant->groupid;
2832         }
2833         if (!empty($participant->groupname)) {
2834             $return['groupname'] = $participant->groupname;
2835         }
2837         // Skip the expensive lookup of user detail if we're blind marking or the caller
2838         // hasn't asked for user details to be embedded.
2839         if (!$assign->is_blind_marking() && $embeduser) {
2840             if ($userdetails = user_get_user_details($participant, $course)) {
2841                 $return['user'] = $userdetails;
2842             }
2843         }
2845         return $return;
2846     }
2848     /**
2849      * Returns description of method result value
2850      *
2851      * @return external_description
2852      * @since Moodle 3.1
2853      */
2854     public static function get_participant_returns() {
2855         $userdescription = core_user_external::user_description();
2856         $userdescription->default = [];
2857         $userdescription->required = VALUE_OPTIONAL;
2859         return new external_single_structure(array(
2860             'id' => new external_value(PARAM_INT, 'ID of the user'),
2861             'fullname' => new external_value(PARAM_NOTAGS, 'The fullname of the user'),
2862             'submitted' => new external_value(PARAM_BOOL, 'have they submitted their assignment'),
2863             'requiregrading' => new external_value(PARAM_BOOL, 'is their submission waiting for grading'),
2864             'grantedextension' => new external_value(PARAM_BOOL, 'have they been granted an extension'),
2865             'blindmarking' => new external_value(PARAM_BOOL, 'is blind marking enabled for this assignment'),
2866             'allowsubmissionsfromdate' => new external_value(PARAM_INT, 'allowsubmissionsfromdate for the user'),
2867             'duedate' => new external_value(PARAM_INT, 'duedate for the user'),
2868             'cutoffdate' => new external_value(PARAM_INT, 'cutoffdate for the user'),
2869             'duedatestr' => new external_value(PARAM_TEXT, 'duedate for the user'),
2870             'groupid' => new external_value(PARAM_INT, 'for group assignments this is the group id', VALUE_OPTIONAL),
2871             'groupname' => new external_value(PARAM_NOTAGS, 'for group assignments this is the group name', VALUE_OPTIONAL),
2872             'user' => $userdescription,
2873         ));
2874     }
2876     /**
2877      * Utility function for validating an assign.
2878      *
2879      * @param int $assignid assign instance id
2880      * @return array array containing the assign, course, context and course module objects
2881      * @since  Moodle 3.2
2882      */
2883     protected static function validate_assign($assignid) {
2884         global $DB;
2886         // Request and permission validation.
2887         $assign = $DB->get_record('assign', array('id' => $assignid), 'id', MUST_EXIST);
2888         list($course, $cm) = get_course_and_cm_from_instance($assign, 'assign');
2890         $context = context_module::instance($cm->id);
2891         // Please, note that is not required to check mod/assign:view because is done by validate_context->require_login.
2892         self::validate_context($context);
2893         $assign = new assign($context, $cm, $course);
2895         return array($assign, $course, $cm, $context);
2896     }
2898     /**
2899      * Describes the parameters for view_assign.
2900      *
2901      * @return external_function_parameters
2902      * @since Moodle 3.2
2903      */
2904     public static function view_assign_parameters() {
2905         return new external_function_parameters (
2906             array(
2907                 'assignid' => new external_value(PARAM_INT, 'assign instance id'),
2908             )
2909         );
2910     }
2912     /**
2913      * Update the module completion status.
2914      *
2915      * @param int $assignid assign instance id
2916      * @return array of warnings and status result
2917      * @since Moodle 3.2
2918      */
2919     public static function view_assign($assignid) {
2920         $warnings = array();
2921         $params = array(
2922             'assignid' => $assignid,
2923         );
2924         $params = self::validate_parameters(self::view_assign_parameters(), $params);
2926         list($assign, $course, $cm, $context) = self::validate_assign($params['assignid']);
2928         $assign->set_module_viewed();
2930         $result = array();
2931         $result['status'] = true;
2932         $result['warnings'] = $warnings;
2933         return $result;
2934     }
2936     /**
2937      * Describes the view_assign return value.
2938      *
2939      * @return external_single_structure
2940      * @since Moodle 3.2
2941      */
2942     public static function view_assign_returns() {
2943         return new external_single_structure(
2944             array(
2945                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
2946                 'warnings' => new external_warnings(),
2947             )
2948         );
2949     }