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