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