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