MDL-37148 Assignment: Lots more webservices
authorDamyon Wiese <damyon@moodle.com>
Mon, 10 Dec 2012 06:47:31 +0000 (14:47 +0800)
committerDamyon Wiese <damyon@moodle.com>
Fri, 13 Sep 2013 05:29:51 +0000 (13:29 +0800)
This patch adds lots of webservices to the assignment module.
* mod_assign_revert_submissions_to_draft
* mod_assign_lock_submissions
* mod_assign_unlock_submissions
* mod_assign_save_submission
* mod_assign_submit_for_grading
* mod_assign_save_grade
* mod_assign_save_user_extensions
* mod_assign_reveal_identities
* mod_assign_copy_previous_attempt

All features such as reopening attempts and marking workflow are supported through
the save_grade and save_submission functions.

Uploading files is supported by sending draft item ids for the files_filemanager param the same functions.

14 files changed:
mod/assign/assignmentplugin.php
mod/assign/db/services.php
mod/assign/externallib.php
mod/assign/feedback/comments/locallib.php
mod/assign/feedback/file/locallib.php
mod/assign/locallib.php
mod/assign/submission/file/locallib.php
mod/assign/submission/onlinetext/locallib.php
mod/assign/submissionplugin.php
mod/assign/tests/base_test.php
mod/assign/tests/externallib_test.php
mod/assign/tests/locallib_test.php
mod/assign/upgrade.txt
mod/assign/version.php

index cda69f1..ea6a36a 100644 (file)
@@ -627,5 +627,13 @@ abstract class assign_plugin {
         return true;
     }
 
-
+    /**
+     * If this plugin can participate in a webservice (save_submission or save_grade),
+     * return a list of external_params to be included in the definition of that webservice.
+     *
+     * @return external_description|null
+     */
+    public function get_external_parameters() {
+        return null;
+    }
 }
index 1fe8cd2..b2d12fc 100644 (file)
@@ -63,5 +63,70 @@ $functions = array(
                 'classpath' => 'mod/assign/externallib.php',
                 'description' => 'Returns the blind marking mappings for assignments',
                 'type' => 'read'
+        ),
+
+        'mod_assign_revert_submissions_to_draft' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'revert_submissions_to_draft',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Reverts the list of submissions to draft status',
+                'type' => 'write'
+        ),
+
+        'mod_assign_lock_submissions' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'lock_submissions',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Prevent students from making changes to a list of submissions',
+                'type' => 'write'
+        ),
+
+        'mod_assign_unlock_submissions' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'unlock_submissions',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Allow students to make changes to a list of submissions',
+                'type' => 'write'
+        ),
+
+        'mod_assign_save_submission' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'save_submission',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Update the current students submission',
+                'type' => 'write'
+        ),
+
+        'mod_assign_submit_for_grading' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'submit_for_grading',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Submit the current students assignment for grading',
+                'type' => 'write'
+        ),
+
+        'mod_assign_save_grade' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'save_grade',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Save a grade update for a single student.',
+                'type' => 'write'
+        ),
+
+        'mod_assign_save_user_extensions' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'save_user_extensions',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Save a list of assignment extensions',
+                'type' => 'write'
+        ),
+
+        'mod_assign_reveal_identities' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'reveal_identities',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Reveal the identities for a blind marking assignment',
+                'type' => 'write'
         )
+
 );
index 2b03f07..09db0b8 100644 (file)
@@ -32,6 +32,38 @@ require_once("$CFG->libdir/externallib.php");
  */
 class mod_assign_external extends external_api {
 
+    /**
+     * Generate a warning in a standard structure for a known failure.
+     *
+     * @param int $assignmentid - The assignment
+     * @param string $warningcode - The key for the warning message (@see $warningmessages)
+     * @param string $detail - A description of the error
+     * @return array - Warning structure containing item, itemid, warningcode, message
+     */
+    private static function generate_warning($assignmentid, $warningcode, $detail) {
+        $warningmessages = array(
+            'couldnotlock'=>'Could not lock the submission for this user.',
+            'couldnotunlock'=>'Could not unlock the submission for this user.',
+            'couldnotsubmitforgrading'=>'Could not submit assignment for grading.',
+            'couldnotrevealidentities'=>'Could not reveal identities.',
+            'couldnotgrantextensions'=>'Could not grant submission date extensions.',
+            'couldnotrevert'=>'Could not revert submission to draft.',
+            'invalidparameters'=>'Invalid parameters.',
+            'couldnotsavesubmission'=>'Could not save submission.',
+            'couldnotsavegrade'=>'Could not save grade.'
+        );
+
+        $message = $warningmessages[$warningcode];
+        if (empty($message)) {
+            $message = 'Unknown warning type.';
+        }
+
+        return array('item'=>$detail,
+                     'itemid'=>$assignmentid,
+                     'warningcode'=>$warningcode,
+                     'message'=>$message);
+    }
+
     /**
      * Describes the parameters for get_grades
      * @return external_external_function_parameters
@@ -102,7 +134,7 @@ class mod_assign_external extends external_api {
                                 WHERE mxg.assignment ' . $inorequalsql2 . ' GROUP BY mxg.userid';
 
             $sql = "SELECT ag.id,ag.assignment,ag.userid,ag.timecreated,ag.timemodified,".
-                   "ag.grader,ag.grade ".
+                   "ag.grader,ag.grade,ag.attemptnumber ".
                    "FROM {assign_grades} ag ".
                    "JOIN ( " . $grademaxattempt . " ) gmx ON ag.userid = gmx.userid".
                    " WHERE ag.assignment ".$inorequalsql.
@@ -122,6 +154,7 @@ class mod_assign_external extends external_api {
                 $grade['timecreated'] = $rd->timecreated;
                 $grade['timemodified'] = $rd->timemodified;
                 $grade['grader'] = $rd->grader;
+                $grade['attemptnumber'] = $rd->attemptnumber;
                 $grade['grade'] = (string)$rd->grade;
 
                 if (is_null($currentassignmentid) || ($rd->assignment != $currentassignmentid )) {
@@ -170,6 +203,7 @@ class mod_assign_external extends external_api {
                         array(
                             'id'            => new external_value(PARAM_INT, 'grade id'),
                             'userid'        => new external_value(PARAM_INT, 'student id'),
+                            'attemptnumber' => new external_value(PARAM_INT, 'attempt number'),
                             'timecreated'   => new external_value(PARAM_INT, 'grade creation time'),
                             'timemodified'  => new external_value(PARAM_INT, 'grade last modified time'),
                             'grader'        => new external_value(PARAM_INT, 'grader'),
@@ -528,7 +562,7 @@ class mod_assign_external extends external_api {
                                      WHERE mxs.assignment = :assignid1 GROUP BY mxs.userid';
 
             $sql = "SELECT mas.id, mas.assignment,mas.userid,".
-                   "mas.timecreated,mas.timemodified,mas.status,mas.groupid ".
+                   "mas.timecreated,mas.timemodified,mas.status,mas.groupid,mas.attemptnumber ".
                    "FROM {assign_submission} mas ".
                    "JOIN ( " . $submissionmaxattempt . " ) smx ON mas.userid = smx.userid ".
                    "WHERE mas.assignment = :assignid2 AND mas.attemptnumber = smx.maxattempt";
@@ -557,6 +591,7 @@ class mod_assign_external extends external_api {
                         'timecreated' => $submissionrecord->timecreated,
                         'timemodified' => $submissionrecord->timemodified,
                         'status' => $submissionrecord->status,
+                        'attemptnumber' => $submissionrecord->attemptnumber,
                         'groupid' => $submissionrecord->groupid
                     );
                     foreach ($submissionplugins as $submissionplugin) {
@@ -638,6 +673,7 @@ class mod_assign_external extends external_api {
                         array(
                             'id' => new external_value(PARAM_INT, 'submission id'),
                             'userid' => new external_value(PARAM_INT, 'student id'),
+                            'attemptnumber' => new external_value(PARAM_INT, 'attempt number'),
                             'timecreated' => new external_value(PARAM_INT, 'submission creation time'),
                             'timemodified' => new external_value(PARAM_INT, 'submission last modified time'),
                             'status' => new external_value(PARAM_TEXT, 'submission status'),
@@ -995,4 +1031,599 @@ class mod_assign_external extends external_api {
         );
     }
 
+    /**
+     * Describes the parameters for lock_submissions
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function lock_submissions_parameters() {
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+                'userids' => new external_multiple_structure(
+                    new external_value(PARAM_INT, 'user id'),
+                    '1 or more user ids',
+                    VALUE_REQUIRED),
+            )
+        );
+    }
+
+    /**
+     * Locks (prevent updates to) submissions in this assignment.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @param array $userids Array of user ids to lock
+     * @return array of warnings for each submission that could not be locked.
+     * @since Moodle 2.6
+     */
+    public static function lock_submissions($assignmentid, $userids) {
+        global $CFG;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::lock_submissions_parameters(),
+                        array('assignmentid' => $assignmentid,
+                              'userids' => $userids));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $warnings = array();
+        foreach ($userids as $userid) {
+            if (!$assignment->lock_submission($userid)) {
+                $detail = 'User id: ' . $userid . ', Assignment id: ' . $assignmentid;
+                $warnings[] = self::generate_warning($assignmentid,
+                                                     'couldnotlock',
+                                                     $detail);
+            }
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for lock_submissions
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function lock_submissions_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
+
+    /**
+     * Describes the parameters for revert_submissions_to_draft
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function revert_submissions_to_draft_parameters() {
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+                'userids' => new external_multiple_structure(
+                    new external_value(PARAM_INT, 'user id'),
+                    '1 or more user ids',
+                    VALUE_REQUIRED),
+            )
+        );
+    }
+
+    /**
+     * Reverts a list of user submissions to draft for a single assignment.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @param array $userids Array of user ids to revert
+     * @return array of warnings for each submission that could not be reverted.
+     * @since Moodle 2.6
+     */
+    public static function revert_submissions_to_draft($assignmentid, $userids) {
+        global $CFG;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::revert_submissions_to_draft_parameters(),
+                        array('assignmentid' => $assignmentid,
+                              'userids' => $userids));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $warnings = array();
+        foreach ($userids as $userid) {
+            if (!$assignment->revert_to_draft($userid)) {
+                $detail = 'User id: ' . $userid . ', Assignment id: ' . $assignmentid;
+                $warnings[] = self::generate_warning($assignmentid,
+                                                     'couldnotrevert',
+                                                     $detail);
+            }
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for revert_submissions_to_draft
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function revert_submissions_to_draft_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
+
+    /**
+     * Describes the parameters for unlock_submissions
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function unlock_submissions_parameters() {
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+                'userids' => new external_multiple_structure(
+                    new external_value(PARAM_INT, 'user id'),
+                    '1 or more user ids',
+                    VALUE_REQUIRED),
+            )
+        );
+    }
+
+    /**
+     * Locks (prevent updates to) submissions in this assignment.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @param array $userids Array of user ids to lock
+     * @return array of warnings for each submission that could not be locked.
+     * @since Moodle 2.6
+     */
+    public static function unlock_submissions($assignmentid, $userids) {
+        global $CFG;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::unlock_submissions_parameters(),
+                        array('assignmentid' => $assignmentid,
+                              'userids' => $userids));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $warnings = array();
+        foreach ($userids as $userid) {
+            if (!$assignment->unlock_submission($userid)) {
+                $detail = 'User id: ' . $userid . ', Assignment id: ' . $assignmentid;
+                $warnings[] = self::generate_warning($assignmentid,
+                                                     'couldnotunlock',
+                                                     $detail);
+            }
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for unlock_submissions
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function unlock_submissions_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
+
+    /**
+     * Describes the parameters for unlock_submissions
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function submit_for_grading_parameters() {
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on')
+            )
+        );
+    }
+
+    /**
+     * Submit the logged in users assignment for grading.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @return array of warnings to indicate any errors.
+     * @since Moodle 2.6
+     */
+    public static function submit_for_grading($assignmentid) {
+        global $CFG, $USER;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::submit_for_grading_parameters(),
+                                            array('assignmentid' => $assignmentid));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $warnings = array();
+        if (!$assignment->submit_for_grading()) {
+            $detail = 'User id: ' . $USER->id . ', Assignment id: ' . $assignmentid;
+            $warnings[] = self::generate_warning($assignmentid,
+                                                 'couldnotsubmitforgrading',
+                                                 $detail);
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for submit_for_grading
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function submit_for_grading_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
+
+    /**
+     * Describes the parameters for save_user_extensions
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function save_user_extensions_parameters() {
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+                'userids' => new external_multiple_structure(
+                    new external_value(PARAM_INT, 'user id'),
+                    '1 or more user ids',
+                    VALUE_REQUIRED),
+                'dates' => new external_multiple_structure(
+                    new external_value(PARAM_INT, 'dates'),
+                    '1 or more extension dates (timestamp)',
+                    VALUE_REQUIRED),
+            )
+        );
+    }
+
+    /**
+     * Grant extension dates to students for an assignment.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @param array $userids Array of user ids to grant extensions to
+     * @param array $dates Array of extension dates
+     * @return array of warnings for each extension date that could not be granted
+     * @since Moodle 2.6
+     */
+    public static function save_user_extensions($assignmentid, $userids, $dates) {
+        global $CFG;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::save_user_extensions_parameters(),
+                        array('assignmentid' => $assignmentid,
+                              'userids' => $userids,
+                              'dates' => $dates));
+
+        if (count($userids) != count($dates)) {
+            $detail = 'Length of userids and dates parameters differ.';
+            $warnings[] = self::generate_warning($assignmentid,
+                                                 'invalidparameters',
+                                                 $detail);
+
+            return $warnings;
+        }
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $warnings = array();
+        foreach ($userids as $idx => $userid) {
+            $duedate = $dates[$idx];
+            if (!$assignment->save_user_extension($userid, $duedate)) {
+                $detail = 'User id: ' . $userid . ', Assignment id: ' . $assignmentid . ', Extension date: ' . $duedate;
+                $warnings[] = self::generate_warning($assignmentid,
+                                                     'couldnotgrantextensions',
+                                                     $detail);
+            }
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for save_user_extensions
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function save_user_extensions_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
+
+    /**
+     * Describes the parameters for reveal_identities
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function reveal_identities_parameters() {
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on')
+            )
+        );
+    }
+
+    /**
+     * Reveal the identities of anonymous students to markers for a single assignment.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @return array of warnings to indicate any errors.
+     * @since Moodle 2.6
+     */
+    public static function reveal_identities($assignmentid) {
+        global $CFG, $USER;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::reveal_identities_parameters(),
+                                            array('assignmentid' => $assignmentid));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $warnings = array();
+        if (!$assignment->reveal_identities()) {
+            $detail = 'User id: ' . $USER->id . ', Assignment id: ' . $assignmentid;
+            $warnings[] = self::generate_warning($assignmentid,
+                                                 'couldnotrevealidentities',
+                                                 $detail);
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for reveal_identities
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function reveal_identities_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
+
+    /**
+     * Describes the parameters for save_submission
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function save_submission_parameters() {
+        global $CFG;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+        $instance = new assign(null, null, null);
+        $pluginsubmissionparams = array();
+
+        foreach ($instance->get_submission_plugins() as $plugin) {
+            $pluginparams = $plugin->get_external_parameters();
+            if (!empty($pluginparams)) {
+                $pluginsubmissionparams = array_merge($pluginsubmissionparams, $pluginparams);
+            }
+        }
+
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+                'plugindata' => new external_single_structure(
+                    $pluginsubmissionparams
+                )
+            )
+        );
+    }
+
+    /**
+     * Save a student submission for a single assignment.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @return array of warnings to indicate any errors.
+     * @since Moodle 2.6
+     */
+    public static function save_submission($assignmentid, $plugindata) {
+        global $CFG, $USER;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::save_submission_parameters(),
+                                            array('assignmentid' => $assignmentid,
+                                                  'plugindata' => $plugindata));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $notices = array();
+
+        $submissiondata = (object)$plugindata;
+
+        $assignment->save_submission($submissiondata, $notices);
+
+        $warnings = array();
+        foreach ($notices as $notice) {
+            $warnings[] = self::generate_warning($assignmentid,
+                                                 'couldnotsavesubmission',
+                                                 $notice);
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for save_submission
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function save_submission_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
+
+    /**
+     * Describes the parameters for save_grade
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function save_grade_parameters() {
+        global $CFG;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+        $instance = new assign(null, null, null);
+        $pluginfeedbackparams = array();
+
+        foreach ($instance->get_feedback_plugins() as $plugin) {
+            $pluginparams = $plugin->get_external_parameters();
+            if (!empty($pluginparams)) {
+                $pluginfeedbackparams = array_merge($pluginfeedbackparams, $pluginparams);
+            }
+        }
+
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+                'userid' => new external_value(PARAM_INT, 'The student id to operate on'),
+                'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user'),
+                'attemptnumber' => new external_value(PARAM_INT, 'The attempt number (-1 means latest attempt)'),
+                'addattempt' => new external_value(PARAM_BOOL, 'Allow another attempt if the attempt reopen method is manual'),
+                'workflowstate' => new external_value(PARAM_ALPHA, 'The next marking workflow state'),
+                'applytoall' => new external_value(PARAM_BOOL, 'If true, this grade will be applied ' .
+                                                               'to all members ' .
+                                                               'of the group (for group assignments).'),
+                'plugindata' => new external_single_structure(
+                    $pluginfeedbackparams
+                )
+            )
+        );
+    }
+
+    /**
+     * Save a student grade for a single assignment.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @return null
+     * @since Moodle 2.6
+     */
+    public static function save_grade($assignmentid, $userid, $grade, $attemptnumber, $addattempt, $workflowstate, $applytoall, $plugindata) {
+        global $CFG, $USER;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::save_grade_parameters(),
+                                            array('assignmentid' => $assignmentid,
+                                                  'userid' => $userid,
+                                                  'grade' => $grade,
+                                                  'attemptnumber' => $attemptnumber,
+                                                  'workflowstate' => $workflowstate,
+                                                  'addattempt' => $addattempt,
+                                                  'applytoall' => $applytoall,
+                                                  'plugindata' => $plugindata));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $gradedata = (object)$plugindata;
+
+        $gradedata->addattempt = $addattempt;
+        $gradedata->attemptnumber = $attemptnumber;
+        $gradedata->workflowstate = $workflowstate;
+        $gradedata->applytoall = $applytoall;
+        $gradedata->grade = $grade;
+
+        $assignment->save_grade($userid, $gradedata);
+
+        return null;
+    }
+
+    /**
+     * Describes the return value for save_grade
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function save_grade_returns() {
+        return null;
+    }
+
+    /**
+     * Describes the parameters for copy_previous_attempt
+     * @return external_external_function_parameters
+     * @since  Moodle 2.6
+     */
+    public static function copy_previous_attempt_parameters() {
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+            )
+        );
+    }
+
+    /**
+     * Copy a students previous attempt to a new attempt.
+     *
+     * @return array of warnings to indicate any errors.
+     * @since Moodle 2.6
+     */
+    public static function copy_previous_attempt($assignmentid) {
+        global $CFG, $USER;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::copy_previous_attempt_parameters(),
+                                            array('assignmentid' => $assignmentid));
+
+        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $assignment = new assign($context, $cm, null);
+
+        $notices = array();
+
+        $assignment->copy_previous_attempt($submissiondata, $notices);
+
+        $warnings = array();
+        foreach ($notices as $notice) {
+            $warnings[] = self::generate_warning($assignmentid,
+                                                 'couldnotcopyprevioussubmission',
+                                                 $notice);
+        }
+
+        return $warnings;
+    }
+
+    /**
+     * Describes the return value for save_submission
+     *
+     * @return external_single_structure
+     * @since Moodle 2.6
+     */
+    public static function copy_previous_attempt_returns() {
+        return new external_multiple_structure(
+           new external_warnings()
+        );
+    }
 }
index 9ed302c..2d6e92d 100644 (file)
@@ -390,4 +390,16 @@ class assign_feedback_comments extends assign_feedback_plugin {
         return $this->view($grade) == '';
     }
 
+    /**
+     * Return a description of external params suitable for uploading an feedback comment from a webservice.
+     *
+     * @return external_description|null
+     */
+    public function get_external_parameters() {
+        $editorparams = array('text' => new external_value(PARAM_TEXT, 'The text for this feedback.'),
+                              'format' => new external_value(PARAM_INT, 'The format for this feedback'));
+        $editorstructure = new external_single_structure($editorparams);
+        return array('assignfeedbackcomments_editor' => $editorstructure);
+    }
+
 }
index 50c0e25..c537a43 100644 (file)
@@ -609,4 +609,14 @@ class assign_feedback_file extends assign_feedback_plugin {
     public function get_grading_actions() {
         return array('uploadzip'=>get_string('uploadzip', 'assignfeedback_file'));
     }
+
+    /**
+     * Return a description of external params suitable for uploading a feedback file from a webservice.
+     *
+     * @return external_description|null
+     */
+    public function get_external_parameters() {
+        return array('files_filemanager' => new external_value(PARAM_INT, 'The id of a draft area containing files for this feedback.'));
+    }
+
 }
index 5941b34..f4eb9c5 100644 (file)
@@ -389,7 +389,7 @@ class assign {
                 $nextpageparams['action'] = 'editsubmission';
             }
         } else if ($action == 'lock') {
-            $this->process_lock();
+            $this->process_lock_submission();
             $action = 'redirect';
             $nextpageparams['action'] = 'grading';
         } else if ($action == 'addattempt') {
@@ -401,7 +401,7 @@ class assign {
             $action = 'redirect';
             $nextpageparams['action'] = 'grading';
         } else if ($action == 'unlock') {
-            $this->process_unlock();
+            $this->process_unlock_submission();
             $action = 'redirect';
             $nextpageparams['action'] = 'grading';
         } else if ($action == 'setbatchmarkingworkflowstate') {
@@ -3314,9 +3314,9 @@ class assign {
 
             foreach ($userlist as $userid) {
                 if ($data->operation == 'lock') {
-                    $this->process_lock($userid);
+                    $this->process_lock_submission($userid);
                 } else if ($data->operation == 'unlock') {
-                    $this->process_unlock($userid);
+                    $this->process_unlock_submission($userid);
                 } else if ($data->operation == 'reverttodraft') {
                     $this->process_revert_to_draft($userid);
                 } else if ($data->operation == 'addattempt') {
@@ -4462,6 +4462,71 @@ class assign {
         }
     }
 
+    /**
+     * Submit a submission for grading.
+     *
+     * @return bool Return false if the submission was not submitted.
+     */
+    public function submit_for_grading() {
+        global $USER;
+
+        // Need submit permission to submit an assignment.
+        require_capability('mod/assign:submit', $this->context);
+
+        $instance = $this->get_instance();
+
+        if ($instance->teamsubmission) {
+            $submission = $this->get_group_submission($USER->id, 0, true);
+        } else {
+            $submission = $this->get_user_submission($USER->id, true);
+        }
+
+        if (!$this->submissions_open($USER->id)) {
+            return false;
+        }
+
+        if ($submission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
+            // Give each submission plugin a chance to process the submission.
+            $plugins = $this->get_submission_plugins();
+            foreach ($plugins as $plugin) {
+                if ($plugin->is_enabled() && $plugin->is_visible()) {
+                    $plugin->submit_for_grading($submission);
+                }
+            }
+
+            $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
+            $this->update_submission($submission, $USER->id, true, $instance->teamsubmission);
+            $completion = new completion_info($this->get_course());
+            if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
+                $completion->update_state($this->get_course_module(), COMPLETION_COMPLETE, $USER->id);
+            }
+
+            if (isset($data->submissionstatement)) {
+                $logmessage = get_string('submissionstatementacceptedlog',
+                                         'mod_assign',
+                                         fullname($USER));
+                $this->add_to_log('submission statement accepted', $logmessage);
+            }
+            $logdata = $this->add_to_log('submit for grading', $this->format_submission_for_log($submission), '', true);
+            $this->notify_graders($submission);
+            $this->notify_student_submission_receipt($submission);
+
+            // Trigger assessable_submitted event on submission.
+            $params = array(
+                'context' => context_module::instance($this->get_course_module()->id),
+                'objectid' => $submission->id,
+                'other' => array(
+                    'submission_editable' => false
+                )
+            );
+            $event = \mod_assign\event\assessable_submitted::create($params);
+            $event->set_legacy_logdata($logdata);
+            $event->trigger();
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Assignment submission is processed before grading.
      *
@@ -4472,8 +4537,6 @@ class assign {
     protected function process_submit_for_grading($mform) {
         global $USER, $CFG;
 
-        // Need submit permission to submit an assignment.
-        require_capability('mod/assign:submit', $this->context);
         require_once($CFG->dirroot . '/mod/assign/submissionconfirmform.php');
         require_sesskey();
 
@@ -4503,57 +4566,7 @@ class assign {
             if ($mform->get_data() == false) {
                 return false;
             }
-            if ($instance->teamsubmission) {
-                $submission = $this->get_group_submission($USER->id, 0, true);
-            } else {
-                $submission = $this->get_user_submission($USER->id, true);
-            }
-
-            if ($submission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
-                // Give each submission plugin a chance to process the submission.
-                $plugins = $this->get_submission_plugins();
-                foreach ($plugins as $plugin) {
-                    if ($plugin->is_enabled() && $plugin->is_visible()) {
-                        $plugin->submit_for_grading($submission);
-                    }
-                }
-
-                $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
-                $this->update_submission($submission, $USER->id, true, $instance->teamsubmission);
-                $completion = new completion_info($this->get_course());
-                if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
-                    $completion->update_state($this->get_course_module(), COMPLETION_COMPLETE, $USER->id);
-                }
-
-                if (isset($data->submissionstatement)) {
-                    $logmessage = get_string('submissionstatementacceptedlog',
-                                             'mod_assign',
-                                             fullname($USER));
-                    $addtolog = $this->add_to_log('submission statement accepted', $logmessage, '', true);
-                    $params = array(
-                        'context' => $this->context,
-                        'objectid' => $submission->id
-                    );
-                    $event = \mod_assign\event\statement_accepted::create($params);
-                    $event->set_legacy_logdata($addtolog);
-                    $event->trigger();
-                }
-                $logdata = $this->add_to_log('submit for grading', $this->format_submission_for_log($submission), '', true);
-                $this->notify_graders($submission);
-                $this->notify_student_submission_receipt($submission);
-
-                // Trigger assessable_submitted event on submission.
-                $params = array(
-                    'context' => context_module::instance($this->get_course_module()->id),
-                    'objectid' => $submission->id,
-                    'other' => array(
-                        'submission_editable' => false
-                    )
-                );
-                $event = \mod_assign\event\assessable_submitted::create($params);
-                $event->set_legacy_logdata($logdata);
-                $event->trigger();
-            }
+            return $this->submit_for_grading();
         }
         return true;
     }
@@ -4565,9 +4578,30 @@ class assign {
      * @param mixed $extensionduedate Either an integer date or null
      * @return boolean
      */
-    protected function save_user_extension($userid, $extensionduedate) {
+    public function save_user_extension($userid, $extensionduedate) {
         global $DB;
 
+        // Need submit permission to submit an assignment.
+        require_capability('mod/assign:grantextension', $this->context);
+
+        if (!is_enrolled($this->get_course_context(), $userid)) {
+            return false;
+        }
+        if (!has_capability('mod/assign:submit', $this->context, $userid)) {
+            return false;
+        }
+
+        if ($this->get_instance()->duedate && $extensionduedate) {
+            if ($this->get_instance()->duedate > $extensionduedate) {
+                return false;
+            }
+        }
+        if ($this->get_instance()->allowsubmissionsfromdate && $extensionduedate) {
+            if ($this->get_instance()->allowsubmissionsfromdate > $extensionduedate) {
+                return false;
+            }
+        }
+
         $flags = $this->get_user_flags($userid, true);
         $flags->extensionduedate = $extensionduedate;
 
@@ -4599,9 +4633,6 @@ class assign {
         // Include extension form.
         require_once($CFG->dirroot . '/mod/assign/extensionform.php');
 
-        // Need submit permission to submit an assignment.
-        require_capability('mod/assign:grantextension', $this->context);
-
         $batchusers = optional_param('selectedusers', '', PARAM_SEQUENCE);
         $userid = 0;
         if (!$batchusers) {
@@ -4834,11 +4865,12 @@ class assign {
      *
      * @return void
      */
-    protected function process_reveal_identities() {
-        global $DB, $CFG;
+    public function reveal_identities() {
+        global $DB;
 
         require_capability('mod/assign:revealidentities', $this->context);
-        if (!confirm_sesskey()) {
+
+        if ($this->get_instance()->revealidentities || empty($this->get_instance()->blindmarking)) {
             return false;
         }
 
@@ -4882,6 +4914,20 @@ class assign {
         $event->trigger();
     }
 
+    /**
+     * Reveal student identities to markers (and the gradebook).
+     *
+     * @return void
+     */
+    protected function process_reveal_identities() {
+
+        if (!confirm_sesskey()) {
+            return false;
+        }
+
+        return $this->reveal_identities();
+    }
+
 
     /**
      * Save grading options.
@@ -5009,15 +5055,30 @@ class assign {
     }
 
     /**
-     * Copy the current assignment submission from the last submitted attempt.
+     * Require a valid sess key and then call copy_previous_attempt.
      *
      * @param  array $notices Any error messages that should be shown
      *                        to the user at the top of the edit submission form.
      * @return bool
      */
     protected function process_copy_previous_attempt(&$notices) {
+        require_sesskey();
+
+        return copy_previous_attempt($notices);
+    }
+
+    /**
+     * Copy the current assignment submission from the last submitted attempt.
+     *
+     * @param  array $notices Any error messages that should be shown
+     *                        to the user at the top of the edit submission form.
+     * @return bool
+     */
+    public function copy_previous_attempt(&$notices) {
         global $USER, $CFG;
 
+        require_capability('mod/assign:submit', $this->context);
+
         $instance = $this->get_instance();
         if ($instance->teamsubmission) {
             $submission = $this->get_group_submission($USER->id, 0, true);
@@ -5128,121 +5189,137 @@ class assign {
     }
 
     /**
-     * Save assignment submission.
+     * Save assignment submission for the current user.
      *
-     * @param  moodleform $mform
+     * @param  stdClass $data
      * @param  array $notices Any error messages that should be shown
-     *                        to the user at the top of the edit submission form.
+     *                        to the user.
      * @return bool
      */
-    protected function process_save_submission(&$mform, &$notices) {
-        global $USER, $CFG;
-
-        // Include submission form.
-        require_once($CFG->dirroot . '/mod/assign/submission_form.php');
+    public function save_submission(stdClass $data, & $notices) {
+        global $CFG, $USER;
 
-        // Need submit permission to submit an assignment.
         require_capability('mod/assign:submit', $this->context);
-        require_sesskey();
-        if (!$this->submissions_open()) {
-            $notices[] = get_string('duedatereached', 'assign');
-            return false;
-        }
         $instance = $this->get_instance();
 
-        $data = new stdClass();
-        $mform = new mod_assign_submission_form(null, array($this, $data));
-        if ($mform->is_cancelled()) {
-            return true;
+        if ($instance->teamsubmission) {
+            $submission = $this->get_group_submission($USER->id, 0, true);
+        } else {
+            $submission = $this->get_user_submission($USER->id, true);
+        }
+        if ($instance->submissiondrafts) {
+            $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
+        } else {
+            $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
         }
-        if ($data = $mform->get_data()) {
-            if ($instance->teamsubmission) {
-                $submission = $this->get_group_submission($USER->id, 0, true);
-            } else {
-                $submission = $this->get_user_submission($USER->id, true);
-            }
-            if ($instance->submissiondrafts) {
-                $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
-            } else {
-                $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
-            }
 
-            $flags = $this->get_user_flags($USER->id, false);
+        $flags = $this->get_user_flags($USER->id, false);
 
-            // Get the flags to check if it is locked.
-            if ($flags && $flags->locked) {
-                print_error('submissionslocked', 'assign');
-                return true;
-            }
+        // Get the flags to check if it is locked.
+        if ($flags && $flags->locked) {
+            print_error('submissionslocked', 'assign');
+            return true;
+        }
 
-            $pluginerror = false;
-            foreach ($this->submissionplugins as $plugin) {
-                if ($plugin->is_enabled() && $plugin->is_visible()) {
-                    if (!$plugin->save($submission, $data)) {
-                        $notices[] = $plugin->get_error();
-                        $pluginerror = true;
-                    }
+        $pluginerror = false;
+        foreach ($this->submissionplugins as $plugin) {
+            if ($plugin->is_enabled() && $plugin->is_visible()) {
+                if (!$plugin->save($submission, $data)) {
+                    $notices[] = $plugin->get_error();
+                    $pluginerror = true;
                 }
             }
-            $allempty = $this->submission_empty($submission);
-            if ($pluginerror || $allempty) {
-                if ($allempty) {
-                    $notices[] = get_string('submissionempty', 'mod_assign');
-                }
-                return false;
+        }
+        $allempty = $this->submission_empty($submission);
+        if ($pluginerror || $allempty) {
+            if ($allempty) {
+                $notices[] = get_string('submissionempty', 'mod_assign');
             }
+            return false;
+        }
 
-            $this->update_submission($submission, $USER->id, true, $instance->teamsubmission);
-
-            // Logging.
-            if (isset($data->submissionstatement)) {
-                $logmessage = get_string('submissionstatementacceptedlog',
-                                         'mod_assign',
-                                         fullname($USER));
-                $addtolog = $this->add_to_log('submission statement accepted', $logmessage, '', true);
-                $params = array(
-                    'context' => $this->context,
-                    'objectid' => $submission->id
-                );
-                $event = \mod_assign\event\statement_accepted::create($params);
-                $event->set_legacy_logdata($addtolog);
-                $event->trigger();
-            }
+        $this->update_submission($submission, $USER->id, true, $instance->teamsubmission);
 
-            $addtolog = $this->add_to_log('submit', $this->format_submission_for_log($submission), '', true);
+        // Logging.
+        if (isset($data->submissionstatement)) {
+            $logmessage = get_string('submissionstatementacceptedlog',
+                                     'mod_assign',
+                                     fullname($USER));
+            $this->add_to_log('submission statement accepted', $logmessage);
+            $addtolog = $this->add_to_log('submission statement accepted', $logmessage, '', true);
             $params = array(
                 'context' => $this->context,
                 'objectid' => $submission->id
             );
-            $event = \mod_assign\event\submission_updated::create($params);
+            $event = \mod_assign\event\statement_accepted::create($params);
             $event->set_legacy_logdata($addtolog);
             $event->trigger();
+        }
+        $addtolog = $this->add_to_log('submit', $this->format_submission_for_log($submission), '', true);
+        $params = array(
+            'context' => $this->context,
+            'objectid' => $submission->id
+        );
+        $event = \mod_assign\event\submission_updated::create($params);
+        $event->set_legacy_logdata($addtolog);
+        $event->trigger();
 
-            $complete = COMPLETION_INCOMPLETE;
-            if ($submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
-                $complete = COMPLETION_COMPLETE;
-            }
-            $completion = new completion_info($this->get_course());
-            if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
-                $completion->update_state($this->get_course_module(), $complete, $USER->id);
-            }
+        $complete = COMPLETION_INCOMPLETE;
+        if ($submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
+            $complete = COMPLETION_COMPLETE;
+        }
+        $completion = new completion_info($this->get_course());
+        if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
+            $completion->update_state($this->get_course_module(), $complete, $USER->id);
+        }
 
-            if (!$instance->submissiondrafts) {
-                $this->notify_student_submission_receipt($submission);
-                $this->notify_graders($submission);
-                // Trigger assessable_submitted event on submission.
-                $params = array(
-                    'context' => context_module::instance($this->get_course_module()->id),
-                    'objectid' => $submission->id,
-                    'other' => array(
-                        'submission_editable' => true
-                    )
-                );
-                $event = \mod_assign\event\assessable_submitted::create($params);
-                $event->trigger();
-            }
+        if (!$instance->submissiondrafts) {
+            $this->notify_student_submission_receipt($submission);
+            $this->notify_graders($submission);
+            // Trigger assessable_submitted event on submission.
+            $params = array(
+                'context' => context_module::instance($this->get_course_module()->id),
+                'objectid' => $submission->id,
+                'other' => array(
+                    'submission_editable' => true
+                )
+            );
+            $event = \mod_assign\event\assessable_submitted::create($params);
+            $event->trigger();
+        }
+        return true;
+    }
+
+    /**
+     * Save assignment submission.
+     *
+     * @param  moodleform $mform
+     * @param  array $notices Any error messages that should be shown
+     *                        to the user at the top of the edit submission form.
+     * @return bool
+     */
+    protected function process_save_submission(&$mform, &$notices) {
+        global $CFG;
+
+        // Include submission form.
+        require_once($CFG->dirroot . '/mod/assign/submission_form.php');
+
+        // Need submit permission to submit an assignment.
+        require_sesskey();
+        if (!$this->submissions_open()) {
+            $notices[] = get_string('duedatereached', 'assign');
+            return false;
+        }
+        $instance = $this->get_instance();
+
+        $data = new stdClass();
+        $mform = new mod_assign_submission_form(null, array($this, $data));
+        if ($mform->is_cancelled()) {
             return true;
         }
+        if ($data = $mform->get_data()) {
+            return $this->save_submission($data, $notices);
+        }
         return false;
     }
 
@@ -5677,21 +5754,15 @@ class assign {
 
     /**
      * Revert to draft.
-     * Uses url parameter userid
      *
      * @param int $userid
-     * @return void
+     * @return boolean
      */
-    protected function process_revert_to_draft($userid = 0) {
+    public function revert_to_draft($userid) {
         global $DB, $USER;
 
         // Need grade permission.
         require_capability('mod/assign:grade', $this->context);
-        require_sesskey();
-
-        if (!$userid) {
-            $userid = required_param('userid', PARAM_INT);
-        }
 
         if ($this->get_instance()->teamsubmission) {
             $submission = $this->get_group_submission($userid, 0, false);
@@ -5700,7 +5771,7 @@ class assign {
         }
 
         if (!$submission) {
-            return;
+            return false;
         }
         $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
         $this->update_submission($submission, $userid, true, $this->get_instance()->teamsubmission);
@@ -5739,39 +5810,51 @@ class assign {
         $event = \mod_assign\event\submission_status_updated::create($params);
         $event->set_legacy_logdata($addtolog);
         $event->trigger();
+        return true;
     }
 
     /**
-     * Lock the process.
-     * Uses url parameter userid
+     * Revert to draft.
+     * Uses url parameter userid if userid not supplied as a parameter.
      *
      * @param int $userid
-     * @return void
+     * @return boolean
      */
-    protected function process_lock($userid = 0) {
-        global $USER, $DB;
-
-        // Need grade permission.
-        require_capability('mod/assign:grade', $this->context);
+    protected function process_revert_to_draft($userid = 0) {
         require_sesskey();
 
         if (!$userid) {
             $userid = required_param('userid', PARAM_INT);
         }
 
+        return $this->revert_to_draft($userid);
+    }
+
+    /**
+     * Prevent student updates to this submission
+     *
+     * @param int $userid
+     * @return bool
+     */
+    public function lock_submission($userid) {
+        global $USER, $DB;
+        // Need grade permission.
+        require_capability('mod/assign:grade', $this->context);
+
         // Give each submission plugin a chance to process the locking.
         $plugins = $this->get_submission_plugins();
         $submission = $this->get_user_submission($userid, false);
-        foreach ($plugins as $plugin) {
-            if ($plugin->is_enabled() && $plugin->is_visible()) {
-                $plugin->lock($submission);
-            }
-        }
 
         $flags = $this->get_user_flags($userid, true);
         $flags->locked = 1;
         $this->update_user_flags($flags);
 
+        foreach ($plugins as $plugin) {
+            if ($plugin->is_enabled() && $plugin->is_visible()) {
+                $plugin->lock($submission, $flags);
+            }
+        }
+
         $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
 
         $logmessage = get_string('locksubmissionforstudent',
@@ -5786,6 +5869,7 @@ class assign {
         $event = \mod_assign\event\submission_locked::create($params);
         $event->set_legacy_logdata($addtolog);
         $event->trigger();
+        return true;
     }
 
 
@@ -5896,33 +5980,49 @@ class assign {
 
 
     /**
-     * Unlock the process.
+     * Prevent student updates to this submission.
+     * Uses url parameter userid.
      *
      * @param int $userid
      * @return void
      */
-    protected function process_unlock($userid = 0) {
-        global $USER, $DB;
+    protected function process_lock_submission($userid = 0) {
 
-        // Need grade permission.
-        require_capability('mod/assign:grade', $this->context);
         require_sesskey();
 
         if (!$userid) {
             $userid = required_param('userid', PARAM_INT);
         }
+
+        return $this->lock_submission($userid);
+    }
+
+    /**
+     * Unlock the student submission.
+     *
+     * @param int $userid
+     * @return bool
+     */
+    public function unlock_submission($userid) {
+        global $USER, $DB;
+
+        // Need grade permission.
+        require_capability('mod/assign:grade', $this->context);
+
         // Give each submission plugin a chance to process the unlocking.
         $plugins = $this->get_submission_plugins();
         $submission = $this->get_user_submission($userid, false);
+
+        $flags = $this->get_user_flags($userid, true);
+        $flags->locked = 0;
+        $this->update_user_flags($flags);
+
         foreach ($plugins as $plugin) {
             if ($plugin->is_enabled() && $plugin->is_visible()) {
-                $plugin->unlock($submission);
+                $plugin->unlock($submission, $flags);
             }
         }
 
-        $flags = $this->get_user_flags($userid, true);
-        $flags->locked = 0;
-        $this->update_user_flags($flags);
 
         $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
 
@@ -5938,6 +6038,25 @@ class assign {
         $event = \mod_assign\event\submission_unlocked::create($params);
         $event->set_legacy_logdata($addtolog);
         $event->trigger();
+        return true;
+    }
+
+    /**
+     * Unlock the student submission.
+     * Uses url parameter userid.
+     *
+     * @param int $userid
+     * @return bool
+     */
+    protected function process_unlock_submission($userid = 0) {
+
+        require_sesskey();
+
+        if (!$userid) {
+            $userid = required_param('userid', PARAM_INT);
+        }
+
+        return $this->unlock_submission($userid);
     }
 
     /**
@@ -6051,6 +6170,93 @@ class assign {
     }
 
 
+    /**
+     * Save grade update.
+     *
+     * @param int $userid
+     * @param  stdClass $data
+     * @param int $attemptnumber - -1 means latest attempt
+     * @return bool - was the grade saved
+     */
+    public function save_grade($userid, $data) {
+
+        // Need grade permission.
+        require_capability('mod/assign:grade', $this->context);
+
+        $instance = $this->get_instance();
+        $submission = null;
+        if ($instance->teamsubmission) {
+            $submission = $this->get_group_submission($userid, 0, false, $data->attemptnumber);
+        } else {
+            $submission = $this->get_user_submission($userid, false, $data->attemptnumber);
+        }
+        if ($instance->teamsubmission && $data->applytoall) {
+            $groupid = 0;
+            if ($this->get_submission_group($userid)) {
+                $group = $this->get_submission_group($userid);
+                if ($group) {
+                    $groupid = $group->id;
+                }
+            }
+            $members = $this->get_submission_group_members($groupid, true);
+            foreach ($members as $member) {
+                // User may exist in multple groups (which should put them in the default group).
+                $this->apply_grade_to_user($data, $member->id, $data->attemptnumber);
+                $this->process_outcomes($member->id, $data);
+            }
+        } else {
+            $this->apply_grade_to_user($data, $userid, $data->attemptnumber);
+
+            $this->process_outcomes($userid, $data);
+        }
+        $maxattemptsreached = !empty($submission) &&
+                              $submission->attemptnumber >= ($instance->maxattempts - 1) &&
+                              $instance->maxattempts != ASSIGN_UNLIMITED_ATTEMPTS;
+        $shouldreopen = false;
+        if ($instance->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS) {
+            // Check the gradetopass from the gradebook.
+            $gradinginfo = grade_get_grades($this->get_course()->id,
+                                            'mod',
+                                            'assign',
+                                            $instance->id,
+                                            $userid);
+
+            // What do we do if the grade has not been added to the gradebook (e.g. blind marking)?
+            $gradingitem = null;
+            $gradebookgrade = null;
+            if (isset($gradinginfo->items[0])) {
+                $gradingitem = $gradinginfo->items[0];
+                $gradebookgrade = $gradingitem->grades[$userid];
+            }
+
+            if ($gradebookgrade) {
+                // TODO: This code should call grade_grade->is_passed().
+                $shouldreopen = true;
+                if (is_null($gradebookgrade->grade)) {
+                    $shouldreopen = false;
+                }
+                if (empty($gradingitem->gradepass) || $gradingitem->gradepass == $gradingitem->grademin) {
+                    $shouldreopen = false;
+                }
+                if ($gradebookgrade->grade >= $gradingitem->gradepass) {
+                    $shouldreopen = false;
+                }
+            }
+        }
+        if ($instance->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL &&
+                !empty($data->addattempt)) {
+            $shouldreopen = true;
+        }
+        // Never reopen if we are editing a previous attempt.
+        if ($data->attemptnumber != -1) {
+            $shouldreopen = false;
+        }
+        if ($shouldreopen && !$maxattemptsreached) {
+            $this->add_attempt($userid);
+        }
+        return true;
+    }
+
     /**
      * Save grade.
      *
@@ -6062,8 +6268,6 @@ class assign {
         // Include grade form.
         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
 
-        // Need submit permission to submit an assignment.
-        require_capability('mod/assign:grade', $this->context);
         require_sesskey();
 
         $instance = $this->get_instance();
@@ -6102,80 +6306,10 @@ class assign {
                                            array('class'=>'gradeform'));
 
         if ($formdata = $mform->get_data()) {
-            $submission = null;
-            if ($instance->teamsubmission) {
-                $submission = $this->get_group_submission($userid, 0, false, $attemptnumber);
-            } else {
-                $submission = $this->get_user_submission($userid, false, $attemptnumber);
-            }
-            if ($instance->teamsubmission && $formdata->applytoall) {
-                $groupid = 0;
-                if ($this->get_submission_group($userid)) {
-                    $group = $this->get_submission_group($userid);
-                    if ($group) {
-                        $groupid = $group->id;
-                    }
-                }
-                $members = $this->get_submission_group_members($groupid, true);
-                foreach ($members as $member) {
-                    // User may exist in multple groups (which should put them in the default group).
-                    $this->apply_grade_to_user($formdata, $member->id, $attemptnumber);
-                    $this->process_outcomes($member->id, $formdata);
-                }
-            } else {
-                $this->apply_grade_to_user($formdata, $userid, $attemptnumber);
-
-                $this->process_outcomes($userid, $formdata);
-            }
-            $maxattemptsreached = !empty($submission) &&
-                                  $submission->attemptnumber >= ($instance->maxattempts - 1) &&
-                                  $instance->maxattempts != ASSIGN_UNLIMITED_ATTEMPTS;
-            $shouldreopen = false;
-            if ($instance->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS) {
-                // Check the gradetopass from the gradebook.
-                $gradinginfo = grade_get_grades($this->get_course()->id,
-                                                'mod',
-                                                'assign',
-                                                $instance->id,
-                                                $userid);
-
-                // What do we do if the grade has not been added to the gradebook (e.g. blind marking)?
-                $gradingitem = null;
-                $gradebookgrade = null;
-                if (isset($gradinginfo->items[0])) {
-                    $gradingitem = $gradinginfo->items[0];
-                    $gradebookgrade = $gradingitem->grades[$userid];
-                }
-
-                if ($gradebookgrade) {
-                    // TODO: This code should call grade_grade->is_passed().
-                    $shouldreopen = true;
-                    if (is_null($gradebookgrade->grade)) {
-                        $shouldreopen = false;
-                    }
-                    if (empty($gradingitem->gradepass) || $gradingitem->gradepass == $gradingitem->grademin) {
-                        $shouldreopen = false;
-                    }
-                    if ($gradebookgrade->grade >= $gradingitem->gradepass) {
-                        $shouldreopen = false;
-                    }
-                }
-            }
-            if ($instance->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL &&
-                    !empty($formdata->addattempt)) {
-                $shouldreopen = true;
-            }
-            // Never reopen if we are editing a previous attempt.
-            if ($attemptnumber != -1) {
-                $shouldreopen = false;
-            }
-            if ($shouldreopen && !$maxattemptsreached) {
-                $this->process_add_attempt($userid);
-            }
+            return $this->save_grade($userid, $formdata);
         } else {
             return false;
         }
-        return true;
     }
 
     /**
@@ -6288,15 +6422,26 @@ class assign {
     }
 
     /**
-     * Add a new attempt for a user.
+     * Check for a sess key and then call add_attempt.
      *
      * @param int $userid int The user to add the attempt for
      * @return bool - true if successful.
      */
     protected function process_add_attempt($userid) {
-        require_capability('mod/assign:grade', $this->context);
         require_sesskey();
 
+        return $this->add_attempt($userid);
+    }
+
+    /**
+     * Add a new attempt for a user.
+     *
+     * @param int $userid int The user to add the attempt for
+     * @return bool - true if successful.
+     */
+    protected function add_attempt($userid) {
+        require_capability('mod/assign:grade', $this->context);
+
         if ($this->get_instance()->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_NONE) {
             return false;
         }
index e9d72e4..f981aa3 100644 (file)
@@ -477,4 +477,13 @@ class assign_submission_file extends assign_submission_plugin {
         }
         return true;
     }
+
+    /**
+     * Return a description of external params suitable for uploading a file submission from a webservice.
+     *
+     * @return external_description|null
+     */
+    public function get_external_parameters() {
+        return array('files_filemanager' => new external_value(PARAM_INT, 'The id of a draft area containing files for this submission.'));
+    }
 }
index 6f44e4e..87fcc19 100644 (file)
@@ -495,6 +495,20 @@ class assign_submission_onlinetext extends assign_submission_plugin {
         }
         return true;
     }
+
+    /**
+     * Return a description of external params suitable for uploading an onlinetext submission from a webservice.
+     *
+     * @return external_description|null
+     */
+    public function get_external_parameters() {
+        $editorparams = array('text' => new external_value(PARAM_TEXT, 'The text for this submission.'),
+                              'format' => new external_value(PARAM_INT, 'The format for this submission'),
+                              'itemid' => new external_value(PARAM_INT, 'The draft area id for files attached to the submission'));
+        $editorstructure = new external_single_structure($editorparams);
+        return array('onlinetext_editor' => $editorstructure);
+    }
+
 }
 
 
index 5b48a38..120f9b7 100644 (file)
@@ -88,19 +88,21 @@ abstract class assign_submission_plugin extends assign_plugin {
     /*
      * Carry out any extra processing required when the work is locked.
      *
-     * @param stdClass $submission - assign_submission data
+     * @param stdClass|false $submission - assign_submission data if any
+     * @param stdClass $flags - User flags record
      * @return void
      */
-    public function lock(stdClass $submission) {
+    public function lock($submission, stdClass $flags) {
     }
 
     /**
      * Carry out any extra processing required when the work is unlocked.
      *
-     * @param stdClass $submission - assign_submission data
+     * @param stdClass $submission|false - assign_submission data if any
+     * @param stdClass $flags - User flags record
      * @return void
      */
-    public function unlock(stdClass $submission) {
+    public function unlock($submission, stdClass $flags) {
     }
 
     /**
index cae9553..9155540 100644 (file)
@@ -223,10 +223,6 @@ class mod_assign_base_testcase extends advanced_testcase {
  */
 class testable_assign extends assign {
 
-    public function testable_process_reveal_identities() {
-        return parent::process_reveal_identities();
-    }
-
     public function testable_show_intro() {
         return parent::show_intro();
     }
@@ -259,10 +255,6 @@ class testable_assign extends assign {
         return parent::process_add_attempt($userid);
     }
 
-    public function testable_process_lock($userid = 0) {
-        return parent::process_lock($userid);
-    }
-
     public function testable_process_save_quick_grades($postdata) {
         // Ugly hack to get something into the method.
         global $_POST;
@@ -270,18 +262,6 @@ class testable_assign extends assign {
         return parent::process_save_quick_grades();
     }
 
-    public function testable_process_unlock($userid = 0) {
-        return parent::process_unlock($userid);
-    }
-
-    public function testable_process_copy_previous_attempt(&$notices) {
-        return parent::process_copy_previous_attempt($notices);
-    }
-
-    public function testable_process_revert_to_draft($userid = 0) {
-        return parent::process_revert_to_draft($userid);
-    }
-
     public function testable_process_set_batch_marking_allocation($selectedusers, $markerid) {
         // Ugly hack to get something into the method.
         global $_POST;
index 88a4aa2..31a13d3 100644 (file)
@@ -41,7 +41,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
     /**
      * Test get_grades
      */
-    public function test_get_grades () {
+    public function test_get_grades() {
         global $DB, $USER;
 
         $this->resetAfterTest(true);
@@ -118,7 +118,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
     /**
      * Test get_assignments
      */
-    public function test_get_assignments () {
+    public function test_get_assignments() {
         global $DB, $USER;
 
         $this->resetAfterTest(true);
@@ -218,7 +218,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
     /**
      * Test get_submissions
      */
-    public function test_get_submissions () {
+    public function test_get_submissions() {
         global $DB, $USER;
 
         $this->resetAfterTest(true);
@@ -307,7 +307,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
     /**
      * Test get_user_flags
      */
-    public function test_get_user_flags () {
+    public function test_get_user_flags() {
         global $DB, $USER;
 
         $this->resetAfterTest(true);
@@ -378,7 +378,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
     /**
      * Test get_user_mappings
      */
-    public function test_get_user_mappings () {
+    public function test_get_user_mappings() {
         global $DB, $USER;
 
         $this->resetAfterTest(true);
@@ -436,4 +436,592 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals($student->id, $mapping['userid']);
     }
 
+    /**
+     * Test lock_submissions
+     */
+    public function test_lock_submissions() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['assignsubmission_onlinetext_enabled'] = 1;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $student2 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $this->getDataGenerator()->enrol_user($student2->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+
+        // Create a student1 with an online text submission.
+        // Simulate a submission.
+        $this->setUser($student1);
+        $submission = $assign->get_user_submission($student1->id, true);
+        $data = new stdClass();
+        $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
+                                         'text'=>'Submission text',
+                                         'format'=>FORMAT_MOODLE);
+        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
+        $plugin->save($submission, $data);
+
+        // Ready to test
+        $this->setUser($teacher);
+        $students = array($student1->id, $student2->id);
+        $result = mod_assign_external::lock_submissions($instance->id, $students);
+
+        // Check for 0 warnings.
+        $this->assertEquals(0, count($result));
+
+        $this->setUser($student2);
+        $submission = $assign->get_user_submission($student2->id, true);
+        $data = new stdClass();
+        $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
+                                         'text'=>'Submission text',
+                                         'format'=>FORMAT_MOODLE);
+        $notices = array();
+        $this->setExpectedException('moodle_exception');
+        $assign->save_submission($data, $notices);
+    }
+
+    /**
+     * Test unlock_submissions
+     */
+    public function test_unlock_submissions() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['assignsubmission_onlinetext_enabled'] = 1;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $student2 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $this->getDataGenerator()->enrol_user($student2->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+
+        // Create a student1 with an online text submission.
+        // Simulate a submission.
+        $this->setUser($student1);
+        $submission = $assign->get_user_submission($student1->id, true);
+        $data = new stdClass();
+        $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
+                                         'text'=>'Submission text',
+                                         'format'=>FORMAT_MOODLE);
+        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
+        $plugin->save($submission, $data);
+
+        // Ready to test
+        $this->setUser($teacher);
+        $students = array($student1->id, $student2->id);
+        $result = mod_assign_external::lock_submissions($instance->id, $students);
+
+        // Check for 0 warnings.
+        $this->assertEquals(0, count($result));
+
+        $result = mod_assign_external::unlock_submissions($instance->id, $students);
+
+        // Check for 0 warnings.
+        $this->assertEquals(0, count($result));
+
+        $this->setUser($student2);
+        $submission = $assign->get_user_submission($student2->id, true);
+        $data = new stdClass();
+        $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
+                                         'text'=>'Submission text',
+                                         'format'=>FORMAT_MOODLE);
+        $notices = array();
+        $assign->save_submission($data, $notices);
+    }
+
+    /**
+     * Test submit_for_grading
+     */
+    public function test_submit_for_grading() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        set_config('submissionreceipts', 0, 'assign');
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['assignsubmission_onlinetext_enabled'] = 1;
+        $params['submissiondrafts'] = 1;
+        $params['sendnotifications'] = 0;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+
+        // Create a student1 with an online text submission.
+        // Simulate a submission.
+        $this->setUser($student1);
+        $submission = $assign->get_user_submission($student1->id, true);
+        $data = new stdClass();
+        $data->onlinetext_editor = array('itemid'=>file_get_unused_draft_itemid(),
+                                         'text'=>'Submission text',
+                                         'format'=>FORMAT_MOODLE);
+        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
+        $plugin->save($submission, $data);
+
+        $result = mod_assign_external::submit_for_grading($instance->id);
+
+        // Check for 0 warnings.
+        $this->assertEquals(0, count($result));
+
+        $submission = $assign->get_user_submission($student1->id, false);
+
+        $this->assertEquals(ASSIGN_SUBMISSION_STATUS_SUBMITTED, $submission->status);
+    }
+
+    /**
+     * Test save_user_extensions
+     */
+    public function test_save_user_extensions() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+        $this->setUser($teacher);
+
+        $now = time();
+        $yesterday = $now - 24*60*60;
+        $tomorrow = $now + 24*60*60;
+        set_config('submissionreceipts', 0, 'assign');
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['submissiondrafts'] = 1;
+        $params['sendnotifications'] = 0;
+        $params['duedate'] = $yesterday;
+        $params['cutoffdate'] = $now - 10;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+
+        $this->setUser($student1);
+        $result = mod_assign_external::submit_for_grading($instance->id);
+
+        // Check for 0 warnings.
+        $this->assertEquals(1, count($result));
+
+        $this->setUser($teacher);
+        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($now, $tomorrow));
+        $this->assertEquals(1, count($result));
+
+        $this->setUser($teacher);
+        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($yesterday - 10));
+        $this->assertEquals(1, count($result));
+
+        $this->setUser($teacher);
+        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($tomorrow));
+        $this->assertEquals(0, count($result));
+
+        $this->setUser($student1);
+        $result = mod_assign_external::submit_for_grading($instance->id);
+        $this->assertEquals(0, count($result));
+
+        $this->setUser($student1);
+        $result = mod_assign_external::save_user_extensions($instance->id, array($student1->id), array($now, $tomorrow));
+
+    }
+
+    /**
+     * Test reveal_identities
+     */
+    public function test_reveal_identities() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+        $this->setUser($teacher);
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['submissiondrafts'] = 1;
+        $params['sendnotifications'] = 0;
+        $params['blindmarking'] = 1;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+
+        $this->setUser($student1);
+        $this->setExpectedException('required_capability_exception');
+        $result = mod_assign_external::reveal_identities($instance->id);
+        $this->assertEquals(1, count($result));
+        $this->assertEquals(true, $assign->is_blind_marking());
+
+        $this->setUser($teacher);
+        $result = mod_assign_external::reveal_identities($instance->id);
+        $this->assertEquals(0, count($result));
+        $this->assertEquals(false, $assign->is_blind_marking());
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['submissiondrafts'] = 1;
+        $params['sendnotifications'] = 0;
+        $params['blindmarking'] = 0;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+        $result = mod_assign_external::reveal_identities($instance->id);
+        $this->assertEquals(1, count($result));
+        $this->assertEquals(false, $assign->is_blind_marking());
+
+    }
+
+    /**
+     * Test revert_submissions_to_draft
+     */
+    public function test_revert_submissions_to_draft() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        set_config('submissionreceipts', 0, 'assign');
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['sendnotifications'] = 0;
+        $params['submissiondrafts'] = 1;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $student2 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $this->getDataGenerator()->enrol_user($student2->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+
+        // Create a student1 with an online text submission.
+        // Simulate a submission.
+        $this->setUser($student1);
+        $result = mod_assign_external::submit_for_grading($instance->id);
+        $this->assertEquals(0, count($result));
+
+        // Ready to test
+        $this->setUser($teacher);
+        $students = array($student1->id, $student2->id);
+        $result = mod_assign_external::revert_submissions_to_draft($instance->id, array($student1->id));
+
+        // Check for 0 warnings.
+        $this->assertEquals(0, count($result));
+
+    }
+
+    /**
+     * Test save_submission
+     */
+    public function test_save_submission() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+        $this->setUser($teacher);
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['assignsubmission_onlinetext_enabled'] = 1;
+        $params['assignsubmission_file_enabled'] = 1;
+        $params['assignsubmission_file_maxfiles'] = 5;
+        $params['assignsubmission_file_maxsizebytes'] = 1024*1024;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $student2 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $this->getDataGenerator()->enrol_user($student2->id,
+                                              $course->id,
+                                              $studentrole->id);
+        // Create a student1 with an online text submission.
+        // Simulate a submission.
+        $this->setUser($student1);
+
+        // Create a file in a draft area.
+        $draftidfile = file_get_unused_draft_itemid();
+
+        $usercontext = context_user::instance($student1->id);
+        $filerecord = array(
+            'contextid' => $usercontext->id,
+            'component' => 'user',
+            'filearea'  => 'draft',
+            'itemid'    => $draftidfile,
+            'filepath'  => '/',
+            'filename'  => 'testtext.txt',
+        );
+
+        $fs = get_file_storage();
+        $fs->create_file_from_string($filerecord, 'text contents');
+
+        // Create another file in a different draft area.
+        $draftidonlinetext = file_get_unused_draft_itemid();
+
+        $filerecord = array(
+            'contextid' => $usercontext->id,
+            'component' => 'user',
+            'filearea'  => 'draft',
+            'itemid'    => $draftidonlinetext,
+            'filepath'  => '/',
+            'filename'  => 'shouldbeanimage.txt',
+        );
+
+        $fs->create_file_from_string($filerecord, 'image contents (not really)');
+
+        // Now try a submission
+        $submissionpluginparams = array();
+        $submissionpluginparams['files_filemanager'] = $draftidfile;
+        $onlinetexteditorparams = array('text'=>'Yeeha!',
+                                        'format'=>1,
+                                        'itemid'=>$draftidonlinetext);
+        $submissionpluginparams['onlinetext_editor'] = $onlinetexteditorparams;
+        $result = mod_assign_external::save_submission($instance->id, $submissionpluginparams);
+
+        $this->assertEquals(0, count($result));
+
+    }
+
+    /**
+     * Test save_grade
+     */
+    public function test_save_grade() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+        $this->setUser($teacher);
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['assignfeedback_file_enabled'] = 1;
+        $params['assignfeedback_comments_enabled'] = 1;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $student2 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+        $this->getDataGenerator()->enrol_user($student2->id,
+                                              $course->id,
+                                              $studentrole->id);
+        // Simulate a grade.
+        $this->setUser($teacher);
+
+        // Create a file in a draft area.
+        $draftidfile = file_get_unused_draft_itemid();
+
+        $usercontext = context_user::instance($teacher->id);
+        $filerecord = array(
+            'contextid' => $usercontext->id,
+            'component' => 'user',
+            'filearea'  => 'draft',
+            'itemid'    => $draftidfile,
+            'filepath'  => '/',
+            'filename'  => 'testtext.txt',
+        );
+
+        $fs = get_file_storage();
+        $fs->create_file_from_string($filerecord, 'text contents');
+
+        // Now try a grade
+        $feedbackpluginparams = array();
+        $feedbackpluginparams['files_filemanager'] = $draftidfile;
+        $feedbackeditorparams = array('text'=>'Yeeha!',
+                                        'format'=>1);
+        $feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
+        $result = mod_assign_external::save_grade($instance->id, $student1->id, 50.0, -1, true, 'released', false, $feedbackpluginparams);
+
+        // No warnings.
+        $this->assertEquals(0, count($result));
+
+        $result = mod_assign_external::get_grades(array($instance->id));
+
+        $this->assertEquals($result['assignments'][0]['grades'][0]['grade'], '50.0');
+
+    }
+
+    /**
+     * Test copy_previous_attempt
+     */
+    public function test_copy_previous_attempt() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment and users.
+        $course = self::getDataGenerator()->create_course();
+
+        $teacher = self::getDataGenerator()->create_user();
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->getDataGenerator()->enrol_user($teacher->id,
+                                              $course->id,
+                                              $teacherrole->id);
+        $this->setUser($teacher);
+
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $params['assignsubmission_onlinetext_enabled'] = 1;
+        $params['assignsubmission_file_enabled'] = 0;
+        $params['assignfeedback_file_enabled'] = 0;
+        $params['attemptreopenmethod'] = 'manual';
+        $params['maxattempts'] = 5;
+        $instance = $generator->create_instance($params);
+        $cm = get_coursemodule_from_instance('assign', $instance->id);
+        $context = context_module::instance($cm->id);
+
+        $assign = new assign($context, $cm, $course);
+
+        $student1 = self::getDataGenerator()->create_user();
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->getDataGenerator()->enrol_user($student1->id,
+                                              $course->id,
+                                              $studentrole->id);
+        // Now try a submission
+        $this->setUser($student1);
+        $draftidonlinetext = file_get_unused_draft_itemid();
+        $submissionpluginparams = array();
+        $onlinetexteditorparams = array('text'=>'Yeeha!',
+                                        'format'=>1,
+                                        'itemid'=>$draftidonlinetext);
+        $submissionpluginparams['onlinetext_editor'] = $onlinetexteditorparams;
+        $submissionpluginparams['files_filemanager'] = file_get_unused_draft_itemid();
+        $result = mod_assign_external::save_submission($instance->id, $submissionpluginparams);
+
+        $this->setUser($teacher);
+        // Add a grade and reopen the attempt.
+        // Now try a grade
+        $feedbackpluginparams = array();
+        $feedbackpluginparams['files_filemanager'] = file_get_unused_draft_itemid();
+        $feedbackeditorparams = array('text'=>'Yeeha!',
+                                        'format'=>1);
+        $feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
+        $result = mod_assign_external::save_grade($instance->id, $student1->id, 50.0, -1, true, 'released', false, $feedbackpluginparams);
+
+        $this->setUser($student1);
+        // Now copy the previous attempt.
+        $result = mod_assign_external::copy_previous_attempt($instance->id);
+        // No warnings.
+        $this->assertEquals(0, count($result));
+
+        $this->setUser($teacher);
+        $result = mod_assign_external::get_submissions(array($instance->id));
+
+        // Check we are now on the second attempt.
+        $this->assertEquals($result['assignments'][0]['submissions'][0]['attemptnumber'], 1);
+        // Check the plugins data is not empty.
+        $this->assertNotEmpty($result['assignments'][0]['submissions'][0]['plugins']);
+
+    }
 }
index 3ed48e8..48a4688 100644 (file)
@@ -83,26 +83,31 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
 
         // Test students cannot reveal identities.
         $nopermission = false;
+        $this->students[0]->ignoresesskey = true;
         $this->setUser($this->students[0]);
         $this->setExpectedException('required_capability_exception');
-        $assign->testable_process_reveal_identities();
+        $assign->reveal_identities();
+        $this->students[0]->ignoresesskey = false;
 
         // Test teachers cannot reveal identities.
         $nopermission = false;
+        $this->teachers[0]->ignoresesskey = true;
         $this->setUser($this->teachers[0]);
         $this->setExpectedException('required_capability_exception');
-        $assign->testable_process_reveal_identities();
+        $assign->reveal_identities();
+        $this->teachers[0]->ignoresesskey = false;
 
         // Test sesskey is required.
         $this->setUser($this->editingteachers[0]);
         $this->setExpectedException('moodle_exception');
-        $assign->testable_process_reveal_identities();
+        $assign->reveal_identities();
 
         // Test editingteacher can reveal identities if sesskey is ignored.
         $this->editingteachers[0]->ignoresesskey = true;
         $this->setUser($this->editingteachers[0]);
-        $assign->testable_process_reveal_identities();
+        $assign->reveal_identities();
         $this->assertEquals(false, $assign->is_blind_marking());
+        $this->editingteachers[0]->ignoresesskey = false;
 
         // Test student names are visible.
         $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
@@ -1016,7 +1021,7 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
         $assign = $this->create_instance();
         $sink = $this->redirectEvents();
 
-        $assign->testable_process_lock($this->students[0]->id);
+        $assign->lock_submission($this->students[0]->id);
 
         $events = $sink->get_events();
         $this->assertCount(1, $events);
@@ -1049,7 +1054,7 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
         $assign = $this->create_instance(array('blindmarking'=>1));
         $sink = $this->redirectEvents();
 
-        $assign->testable_process_reveal_identities();
+        $assign->reveal_identities();
 
         $events = $sink->get_events();
         $this->assertCount(1, $events);
@@ -1083,7 +1088,7 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
         $assign->testable_update_submission($submission, $this->students[0]->id, true, false);
 
         $sink = $this->redirectEvents();
-        $assign->testable_process_revert_to_draft($this->students[0]->id);
+        $assign->revert_to_draft($this->students[0]->id);
 
         $events = $sink->get_events();
         $this->assertCount(1, $events);
@@ -1191,7 +1196,7 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
 
         $sink = $this->redirectEvents();
         $notices = null;
-        $assign->testable_process_copy_previous_attempt($notices);
+        $assign->copy_previous_attempt($notices);
 
         $events = $sink->get_events();
         $this->assertCount(1, $events);
@@ -1221,7 +1226,7 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
         $assign = $this->create_instance();
         $sink = $this->redirectEvents();
 
-        $assign->testable_process_unlock($this->students[0]->id);
+        $assign->unlock_submission($this->students[0]->id);
 
         $events = $sink->get_events();
         $this->assertCount(1, $events);
index 1f513fe..44c2f89 100644 (file)
@@ -3,6 +3,11 @@ This files describes API changes in the assign code.
 === 2.6 ===
 * To see submission/grades of inactive users, user should have moodle/course:viewsuspendedusers capability.
 * count_* functions will return only active participants.
+* assign_submission_plugin->lock and unlock methods have an additional parameter for user flags. A user will not
+  always have a submission record when the submission is locked/unlocked.
+* Submission and feedback plugins can now participate in webservices. The plugin must implement get_external_parameters()
+  to describe the parameters it is expecting from the mod_assign_save_grade or mod_assign_save_submission functions. The
+  plugin will then handle the webservice via it's normal save() method with the extra data supplied in the $data argument.
 
 === 2.5 ===
 
index 7b06f0d..e2cb6d0 100644 (file)
@@ -25,7 +25,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 $module->component = 'mod_assign'; // Full name of the plugin (used for diagnostics).
-$module->version  = 2013070902;    // The current module version (Date: YYYYMMDDXX).
+$module->version  = 2013080800;    // The current module version (Date: YYYYMMDDXX).
 $module->requires = 2013050100;    // Requires this Moodle version.
 $module->cron     = 60;