2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
22 * @copyright 2012 Paul Charsley
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') || die;
28 require_once("$CFG->libdir/externallib.php");
33 class mod_assign_external extends external_api {
36 * Describes the parameters for get_grades
37 * @return external_external_function_parameters
40 public static function get_grades_parameters() {
41 return new external_function_parameters(
43 'assignmentids' => new external_multiple_structure(
44 new external_value(PARAM_INT, 'assignment id'),
45 '1 or more assignment ids',
47 'since' => new external_value(PARAM_INT,
48 'timestamp, only return records where timemodified >= since',
55 * Returns grade information from assign_grades for the requested assignment ids
56 * @param array of ints $assignmentids
57 * @param int $since only return records with timemodified >= since
58 * @return array of grade records for each requested assignment
61 public static function get_grades($assignmentids, $since = 0) {
63 $params = self::validate_parameters(self::get_grades_parameters(),
64 array('assignmentids' => $assignmentids,
67 $assignments = array();
69 $requestedassignmentids = $params['assignmentids'];
71 // Check the user is allowed to get the grades for the assignments requested.
72 $placeholders = array();
73 list($sqlassignmentids, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
74 $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
75 "WHERE md.name = :modname AND cm.instance ".$sqlassignmentids;
76 $placeholders['modname'] = 'assign';
77 $cms = $DB->get_records_sql($sql, $placeholders);
78 foreach ($cms as $cm) {
80 $context = context_module::instance($cm->id);
81 self::validate_context($context);
82 require_capability('mod/assign:grade', $context);
83 } catch (Exception $e) {
84 $requestedassignmentids = array_diff($requestedassignmentids, array($cm->instance));
86 $warning['item'] = 'assignment';
87 $warning['itemid'] = $cm->instance;
88 $warning['warningcode'] = '1';
89 $warning['message'] = 'No access rights in module context';
90 $warnings[] = $warning;
94 // Create the query and populate an array of grade records from the recordset results.
95 if (count ($requestedassignmentids) > 0) {
96 $placeholders = array();
97 list($inorequalsql, $placeholders) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
98 list($inorequalsql2, $placeholders2) = $DB->get_in_or_equal($requestedassignmentids, SQL_PARAMS_NAMED);
100 $grademaxattempt = 'SELECT mxg.userid, MAX(mxg.attemptnumber) AS maxattempt
101 FROM {assign_grades} mxg
102 WHERE mxg.assignment ' . $inorequalsql2 . ' GROUP BY mxg.userid';
104 $sql = "SELECT ag.id,ag.assignment,ag.userid,ag.timecreated,ag.timemodified,".
105 "ag.grader,ag.grade ".
106 "FROM {assign_grades} ag ".
107 "JOIN ( " . $grademaxattempt . " ) gmx ON ag.userid = gmx.userid".
108 " WHERE ag.assignment ".$inorequalsql.
109 " AND ag.timemodified >= :since".
110 " AND ag.attemptnumber = gmx.maxattempt" .
111 " ORDER BY ag.assignment, ag.id";
112 $placeholders['since'] = $params['since'];
113 // Combine the parameters.
114 $placeholders += $placeholders2;
115 $rs = $DB->get_recordset_sql($sql, $placeholders);
116 $currentassignmentid = null;
118 foreach ($rs as $rd) {
120 $grade['id'] = $rd->id;
121 $grade['userid'] = $rd->userid;
122 $grade['timecreated'] = $rd->timecreated;
123 $grade['timemodified'] = $rd->timemodified;
124 $grade['grader'] = $rd->grader;
125 $grade['grade'] = (string)$rd->grade;
127 if (is_null($currentassignmentid) || ($rd->assignment != $currentassignmentid )) {
128 if (!is_null($assignment)) {
129 $assignments[] = $assignment;
131 $assignment = array();
132 $assignment['assignmentid'] = $rd->assignment;
133 $assignment['grades'] = array();
134 $requestedassignmentids = array_diff($requestedassignmentids, array($rd->assignment));
136 $assignment['grades'][] = $grade;
138 $currentassignmentid = $rd->assignment;
140 if (!is_null($assignment)) {
141 $assignments[] = $assignment;
145 foreach ($requestedassignmentids as $assignmentid) {
147 $warning['item'] = 'assignment';
148 $warning['itemid'] = $assignmentid;
149 $warning['warningcode'] = '3';
150 $warning['message'] = 'No grades found';
151 $warnings[] = $warning;
155 $result['assignments'] = $assignments;
156 $result['warnings'] = $warnings;
161 * Creates an assign_grades external_single_structure
162 * @return external_single_structure
165 private static function assign_grades() {
166 return new external_single_structure(
168 'assignmentid' => new external_value(PARAM_INT, 'assignment id'),
169 'grades' => new external_multiple_structure(new external_single_structure(
171 'id' => new external_value(PARAM_INT, 'grade id'),
172 'userid' => new external_value(PARAM_INT, 'student id'),
173 'timecreated' => new external_value(PARAM_INT, 'grade creation time'),
174 'timemodified' => new external_value(PARAM_INT, 'grade last modified time'),
175 'grader' => new external_value(PARAM_INT, 'grader'),
176 'grade' => new external_value(PARAM_TEXT, 'grade')
185 * Describes the get_grades return value
186 * @return external_single_structure
189 public static function get_grades_returns() {
190 return new external_single_structure(
192 'assignments' => new external_multiple_structure(self::assign_grades(), 'list of assignment grade information'),
193 'warnings' => new external_warnings('item is always \'assignment\'',
194 'when errorcode is 3 then itemid is an assignment id. When errorcode is 1, itemid is a course module id',
195 'errorcode can be 3 (no grades found) or 1 (no permission to get grades)')
201 * Returns description of method parameters
203 * @return external_function_parameters
206 public static function get_assignments_parameters() {
207 return new external_function_parameters(
209 'courseids' => new external_multiple_structure(
210 new external_value(PARAM_INT, 'course id'),
211 '0 or more course ids',
212 VALUE_DEFAULT, array()
214 'capabilities' => new external_multiple_structure(
215 new external_value(PARAM_CAPABILITY, 'capability'),
216 'list of capabilities used to filter courses',
217 VALUE_DEFAULT, array()
224 * Returns an array of courses the user is enrolled in, and for each course all of the assignments that the user can
225 * view within that course.
227 * @param array $courseids An optional array of course ids. If provided only assignments within the given course
228 * will be returned. If the user is not enrolled in a given course a warning will be generated and returned.
229 * @param array $capabilities An array of additional capability checks you wish to be made on the course context.
230 * @return An array of courses and warnings.
233 public static function get_assignments($courseids = array(), $capabilities = array()) {
236 $params = self::validate_parameters(
237 self::get_assignments_parameters(),
238 array('courseids' => $courseids, 'capabilities' => $capabilities)
242 $fields = 'sortorder,shortname,fullname,timemodified';
243 $courses = enrol_get_users_courses($USER->id, true, $fields);
244 // Used to test for ids that have been requested but can't be returned.
245 if (count($params['courseids']) > 0) {
246 foreach ($params['courseids'] as $courseid) {
247 if (!in_array($courseid, array_keys($courses))) {
248 unset($courses[$courseid]);
251 'itemid' => $courseid,
252 'warningcode' => '2',
253 'message' => 'User is not enrolled or does not have requested capability'
258 foreach ($courses as $id => $course) {
259 if (count($params['courseids']) > 0 && !in_array($id, $params['courseids'])) {
260 unset($courses[$id]);
262 $context = context_course::instance($id);
264 self::validate_context($context);
265 } catch (Exception $e) {
266 unset($courses[$id]);
270 'warningcode' => '1',
271 'message' => 'No access rights in course context '.$e->getMessage().$e->getTraceAsString()
275 if (count($params['capabilities']) > 0 && !has_all_capabilities($params['capabilities'], $context)) {
276 unset($courses[$id]);
279 $extrafields='m.id as assignmentid, m.course, m.nosubmissions, m.submissiondrafts, m.sendnotifications, '.
280 'm.sendlatenotifications, m.duedate, m.allowsubmissionsfromdate, m.grade, m.timemodified, '.
281 'm.completionsubmit, m.cutoffdate, m.teamsubmission, m.requireallteammemberssubmit, '.
282 'm.teamsubmissiongroupingid, m.blindmarking, m.revealidentities, m.requiresubmissionstatement';
283 $coursearray = array();
284 foreach ($courses as $id => $course) {
285 $assignmentarray = array();
286 // Get a list of assignments for the course.
287 if ($modules = get_coursemodules_in_course('assign', $courses[$id]->id, $extrafields)) {
288 foreach ($modules as $module) {
289 $context = context_module::instance($module->id);
291 self::validate_context($context);
292 require_capability('mod/assign:view', $context);
293 } catch (Exception $e) {
296 'itemid' => $module->id,
297 'warningcode' => '1',
298 'message' => 'No access rights in module context'
302 $configrecords = $DB->get_recordset('assign_plugin_config', array('assignment' => $module->assignmentid));
303 $configarray = array();
304 foreach ($configrecords as $configrecord) {
305 $configarray[] = array(
306 'id' => $configrecord->id,
307 'assignment' => $configrecord->assignment,
308 'plugin' => $configrecord->plugin,
309 'subtype' => $configrecord->subtype,
310 'name' => $configrecord->name,
311 'value' => $configrecord->value
314 $configrecords->close();
316 $assignmentarray[]= array(
317 'id' => $module->assignmentid,
318 'cmid' => $module->id,
319 'course' => $module->course,
320 'name' => $module->name,
321 'nosubmissions' => $module->nosubmissions,
322 'submissiondrafts' => $module->submissiondrafts,
323 'sendnotifications' => $module->sendnotifications,
324 'sendlatenotifications' => $module->sendlatenotifications,
325 'duedate' => $module->duedate,
326 'allowsubmissionsfromdate' => $module->allowsubmissionsfromdate,
327 'grade' => $module->grade,
328 'timemodified' => $module->timemodified,
329 'completionsubmit' => $module->completionsubmit,
330 'cutoffdate' => $module->cutoffdate,
331 'teamsubmission' => $module->teamsubmission,
332 'requireallteammemberssubmit' => $module->requireallteammemberssubmit,
333 'teamsubmissiongroupingid' => $module->teamsubmissiongroupingid,
334 'blindmarking' => $module->blindmarking,
335 'revealidentities' => $module->revealidentities,
336 'requiresubmissionstatement' => $module->requiresubmissionstatement,
337 'configs' => $configarray
341 $coursearray[]= array(
342 'id' => $courses[$id]->id,
343 'fullname' => $courses[$id]->fullname,
344 'shortname' => $courses[$id]->shortname,
345 'timemodified' => $courses[$id]->timemodified,
346 'assignments' => $assignmentarray
351 'courses' => $coursearray,
352 'warnings' => $warnings
358 * Creates an assignment external_single_structure
360 * @return external_single_structure
363 private static function get_assignments_assignment_structure() {
364 return new external_single_structure(
366 'id' => new external_value(PARAM_INT, 'assignment id'),
367 'course' => new external_value(PARAM_INT, 'course id'),
368 'name' => new external_value(PARAM_TEXT, 'assignment name'),
369 'nosubmissions' => new external_value(PARAM_INT, 'no submissions'),
370 'submissiondrafts' => new external_value(PARAM_INT, 'submissions drafts'),
371 'sendnotifications' => new external_value(PARAM_INT, 'send notifications'),
372 'sendlatenotifications' => new external_value(PARAM_INT, 'send notifications'),
373 'duedate' => new external_value(PARAM_INT, 'assignment due date'),
374 'allowsubmissionsfromdate' => new external_value(PARAM_INT, 'allow submissions from date'),
375 'grade' => new external_value(PARAM_INT, 'grade type'),
376 'timemodified' => new external_value(PARAM_INT, 'last time assignment was modified'),
377 'completionsubmit' => new external_value(PARAM_INT, 'if enabled, set activity as complete following submission'),
378 'cutoffdate' => new external_value(PARAM_INT, 'date after which submission is not accepted without an extension'),
379 'teamsubmission' => new external_value(PARAM_INT, 'if enabled, students submit as a team'),
380 'requireallteammemberssubmit' => new external_value(PARAM_INT, 'if enabled, all team members must submit'),
381 'teamsubmissiongroupingid' => new external_value(PARAM_INT, 'the grouping id for the team submission groups'),
382 'blindmarking' => new external_value(PARAM_INT, 'if enabled, hide identities until reveal identities actioned'),
383 'revealidentities' => new external_value(PARAM_INT, 'show identities for a blind marking assignment'),
384 'requiresubmissionstatement' => new external_value(PARAM_INT, 'student must accept submission statement'),
385 'configs' => new external_multiple_structure(self::get_assignments_config_structure(), 'configuration settings')
386 ), 'assignment information object');
390 * Creates an assign_plugin_config external_single_structure
392 * @return external_single_structure
395 private static function get_assignments_config_structure() {
396 return new external_single_structure(
398 'id' => new external_value(PARAM_INT, 'assign_plugin_config id'),
399 'assignment' => new external_value(PARAM_INT, 'assignment id'),
400 'plugin' => new external_value(PARAM_TEXT, 'plugin'),
401 'subtype' => new external_value(PARAM_TEXT, 'subtype'),
402 'name' => new external_value(PARAM_TEXT, 'name'),
403 'value' => new external_value(PARAM_TEXT, 'value')
404 ), 'assignment configuration object'
409 * Creates a course external_single_structure
411 * @return external_single_structure
414 private static function get_assignments_course_structure() {
415 return new external_single_structure(
417 'id' => new external_value(PARAM_INT, 'course id'),
418 'fullname' => new external_value(PARAM_TEXT, 'course full name'),
419 'shortname' => new external_value(PARAM_TEXT, 'course short name'),
420 'timemodified' => new external_value(PARAM_INT, 'last time modified'),
421 'assignments' => new external_multiple_structure(self::get_assignments_assignment_structure(), 'assignment info')
422 ), 'course information object'
427 * Describes the return value for get_assignments
429 * @return external_single_structure
432 public static function get_assignments_returns() {
433 return new external_single_structure(
435 'courses' => new external_multiple_structure(self::get_assignments_course_structure(), 'list of courses'),
436 'warnings' => new external_warnings('item can be \'course\' (errorcode 1 or 2) or \'module\' (errorcode 1)',
437 'When item is a course then itemid is a course id. When the item is a module then itemid is a module id',
438 'errorcode can be 1 (no access rights) or 2 (not enrolled or no permissions)')
444 * Describes the parameters for get_submissions
446 * @return external_external_function_parameters
449 public static function get_submissions_parameters() {
450 return new external_function_parameters(
452 'assignmentids' => new external_multiple_structure(
453 new external_value(PARAM_INT, 'assignment id'),
454 '1 or more assignment ids',
456 'status' => new external_value(PARAM_ALPHA, 'status', VALUE_DEFAULT, ''),
457 'since' => new external_value(PARAM_INT, 'submitted since', VALUE_DEFAULT, 0),
458 'before' => new external_value(PARAM_INT, 'submitted before', VALUE_DEFAULT, 0)
464 * Returns submissions for the requested assignment ids
466 * @param array of ints $assignmentids
467 * @param string $status only return submissions with this status
468 * @param int $since only return submissions with timemodified >= since
469 * @param int $before only return submissions with timemodified <= before
470 * @return array of submissions for each requested assignment
473 public static function get_submissions($assignmentids, $status = '', $since = 0, $before = 0) {
475 require_once("$CFG->dirroot/mod/assign/locallib.php");
476 $params = self::validate_parameters(self::get_submissions_parameters(),
477 array('assignmentids' => $assignmentids,
480 'before' => $before));
483 $assignments = array();
485 // Check the user is allowed to get the submissions for the assignments requested.
486 $placeholders = array();
487 list($inorequalsql, $placeholders) = $DB->get_in_or_equal($params['assignmentids'], SQL_PARAMS_NAMED);
488 $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
489 "WHERE md.name = :modname AND cm.instance ".$inorequalsql;
490 $placeholders['modname'] = 'assign';
491 $cms = $DB->get_records_sql($sql, $placeholders);
493 foreach ($cms as $cm) {
495 $context = context_module::instance($cm->id);
496 self::validate_context($context);
497 require_capability('mod/assign:grade', $context);
498 $assign = new assign($context, null, null);
499 $assigns[] = $assign;
500 } catch (Exception $e) {
502 'item' => 'assignment',
503 'itemid' => $cm->instance,
504 'warningcode' => '1',
505 'message' => 'No access rights in module context'
510 foreach ($assigns as $assign) {
511 $submissions = array();
512 $submissionplugins = $assign->get_submission_plugins();
513 $placeholders = array('assignid1' => $assign->get_instance()->id,
514 'assignid2' => $assign->get_instance()->id);
516 $submissionmaxattempt = 'SELECT mxs.userid, MAX(mxs.attemptnumber) AS maxattempt
517 FROM {assign_submission} mxs
518 WHERE mxs.assignment = :assignid1 GROUP BY mxs.userid';
520 $sql = "SELECT mas.id, mas.assignment,mas.userid,".
521 "mas.timecreated,mas.timemodified,mas.status,mas.groupid ".
522 "FROM {assign_submission} mas ".
523 "JOIN ( " . $submissionmaxattempt . " ) smx ON mas.userid = smx.userid ".
524 "WHERE mas.assignment = :assignid2 AND mas.attemptnumber = smx.maxattempt";
526 if (!empty($params['status'])) {
527 $placeholders['status'] = $params['status'];
528 $sql = $sql." AND mas.status = :status";
530 if (!empty($params['before'])) {
531 $placeholders['since'] = $params['since'];
532 $placeholders['before'] = $params['before'];
533 $sql = $sql." AND mas.timemodified BETWEEN :since AND :before";
535 $placeholders['since'] = $params['since'];
536 $sql = $sql." AND mas.timemodified >= :since";
539 $submissionrecords = $DB->get_records_sql($sql, $placeholders);
541 if (!empty($submissionrecords)) {
542 $fs = get_file_storage();
543 foreach ($submissionrecords as $submissionrecord) {
545 'id' => $submissionrecord->id,
546 'userid' => $submissionrecord->userid,
547 'timecreated' => $submissionrecord->timecreated,
548 'timemodified' => $submissionrecord->timemodified,
549 'status' => $submissionrecord->status,
550 'groupid' => $submissionrecord->groupid
552 foreach ($submissionplugins as $submissionplugin) {
554 'name' => $submissionplugin->get_name(),
555 'type' => $submissionplugin->get_type()
557 // Subtype is 'assignsubmission', type is currently 'file' or 'onlinetext'.
558 $component = $submissionplugin->get_subtype().'_'.$submissionplugin->get_type();
560 $fileareas = $submissionplugin->get_file_areas();
561 foreach ($fileareas as $filearea => $name) {
562 $fileareainfo = array('area' => $filearea);
563 $files = $fs->get_area_files(
564 $assign->get_context()->id,
567 $submissionrecord->id,
571 foreach ($files as $file) {
572 $filepath = array('filepath' => $file->get_filepath().$file->get_filename());
573 $fileareainfo['files'][] = $filepath;
575 $plugin['fileareas'][] = $fileareainfo;
578 $editorfields = $submissionplugin->get_editor_fields();
579 foreach ($editorfields as $name => $description) {
580 $editorfieldinfo = array(
582 'description' => $description,
583 'text' => $submissionplugin->get_editor_text($name, $submissionrecord->id),
584 'format' => $submissionplugin->get_editor_format($name, $submissionrecord->id)
586 $plugin['editorfields'][] = $editorfieldinfo;
589 $submission['plugins'][] = $plugin;
591 $submissions[] = $submission;
596 'itemid' => $assign->get_instance()->id,
597 'warningcode' => '3',
598 'message' => 'No submissions found'
602 $assignments[] = array(
603 'assignmentid' => $assign->get_instance()->id,
604 'submissions' => $submissions
610 'assignments' => $assignments,
611 'warnings' => $warnings
617 * Creates an assign_submissions external_single_structure
619 * @return external_single_structure
622 private static function get_submissions_structure() {
623 return new external_single_structure(
625 'assignmentid' => new external_value(PARAM_INT, 'assignment id'),
626 'submissions' => new external_multiple_structure(
627 new external_single_structure(
629 'id' => new external_value(PARAM_INT, 'submission id'),
630 'userid' => new external_value(PARAM_INT, 'student id'),
631 'timecreated' => new external_value(PARAM_INT, 'submission creation time'),
632 'timemodified' => new external_value(PARAM_INT, 'submission last modified time'),
633 'status' => new external_value(PARAM_TEXT, 'submission status'),
634 'groupid' => new external_value(PARAM_INT, 'group id'),
635 'plugins' => new external_multiple_structure(
636 new external_single_structure(
638 'type' => new external_value(PARAM_TEXT, 'submission plugin type'),
639 'name' => new external_value(PARAM_TEXT, 'submission plugin name'),
640 'fileareas' => new external_multiple_structure(
641 new external_single_structure(
643 'area' => new external_value (PARAM_TEXT, 'file area'),
644 'files' => new external_multiple_structure(
645 new external_single_structure(
647 'filepath' => new external_value (PARAM_TEXT, 'file path')
649 ), 'files', VALUE_OPTIONAL
652 ), 'fileareas', VALUE_OPTIONAL
654 'editorfields' => new external_multiple_structure(
655 new external_single_structure(
657 'name' => new external_value(PARAM_TEXT, 'field name'),
658 'description' => new external_value(PARAM_TEXT, 'field description'),
659 'text' => new external_value (PARAM_RAW, 'field value'),
660 'format' => new external_format_value ('text')
663 , 'editorfields', VALUE_OPTIONAL
667 , 'plugins', VALUE_OPTIONAL
677 * Describes the get_submissions return value
679 * @return external_single_structure
682 public static function get_submissions_returns() {
683 return new external_single_structure(
685 'assignments' => new external_multiple_structure(self::get_submissions_structure(), 'assignment submissions'),
686 'warnings' => new external_warnings()