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