From 05a6445a14ce182fa69df056faf247e83e838751 Mon Sep 17 00:00:00 2001 From: Damyon Wiese Date: Mon, 10 Dec 2012 14:47:31 +0800 Subject: [PATCH 1/4] MDL-37148 Assignment: Lots more webservices 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. --- mod/assign/assignmentplugin.php | 10 +- mod/assign/db/services.php | 65 ++ mod/assign/externallib.php | 635 ++++++++++++++++- mod/assign/feedback/comments/locallib.php | 12 + mod/assign/feedback/file/locallib.php | 10 + mod/assign/locallib.php | 665 +++++++++++------- mod/assign/submission/file/locallib.php | 9 + mod/assign/submission/onlinetext/locallib.php | 14 + mod/assign/submissionplugin.php | 10 +- mod/assign/tests/base_test.php | 20 - mod/assign/tests/externallib_test.php | 598 +++++++++++++++- mod/assign/tests/locallib_test.php | 23 +- mod/assign/upgrade.txt | 5 + mod/assign/version.php | 2 +- 14 files changed, 1776 insertions(+), 302 deletions(-) diff --git a/mod/assign/assignmentplugin.php b/mod/assign/assignmentplugin.php index cda69f19dd5..ea6a36af9e8 100644 --- a/mod/assign/assignmentplugin.php +++ b/mod/assign/assignmentplugin.php @@ -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; + } } diff --git a/mod/assign/db/services.php b/mod/assign/db/services.php index 1fe8cd2c9c4..b2d12fcab5c 100644 --- a/mod/assign/db/services.php +++ b/mod/assign/db/services.php @@ -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' ) + ); diff --git a/mod/assign/externallib.php b/mod/assign/externallib.php index 2b03f0737e0..09db0b8f76b 100644 --- a/mod/assign/externallib.php +++ b/mod/assign/externallib.php @@ -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() + ); + } } diff --git a/mod/assign/feedback/comments/locallib.php b/mod/assign/feedback/comments/locallib.php index 9ed302ccf83..2d6e92d4069 100644 --- a/mod/assign/feedback/comments/locallib.php +++ b/mod/assign/feedback/comments/locallib.php @@ -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); + } + } diff --git a/mod/assign/feedback/file/locallib.php b/mod/assign/feedback/file/locallib.php index 50c0e25ab4e..c537a438185 100644 --- a/mod/assign/feedback/file/locallib.php +++ b/mod/assign/feedback/file/locallib.php @@ -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.')); + } + } diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index 5941b34ff69..f4eb9c5e592 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -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); @@ -5127,6 +5188,108 @@ class assign { return $allempty; } + /** + * Save assignment submission for the current user. + * + * @param stdClass $data + * @param array $notices Any error messages that should be shown + * to the user. + * @return bool + */ + public function save_submission(stdClass $data, & $notices) { + global $CFG, $USER; + + 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 ($instance->submissiondrafts) { + $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT; + } else { + $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED; + } + + $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; + } + + $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; + } + + $this->update_submission($submission, $USER->id, true, $instance->teamsubmission); + + // 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\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); + } + + 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. * @@ -5136,13 +5299,12 @@ class assign { * @return bool */ protected function process_save_submission(&$mform, &$notices) { - global $USER, $CFG; + global $CFG; // Include submission form. require_once($CFG->dirroot . '/mod/assign/submission_form.php'); // 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'); @@ -5156,92 +5318,7 @@ class assign { return true; } 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); - - // 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; - } - } - } - $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(); - } - - $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); - } - - 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; + 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,34 +5980,50 @@ 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); - foreach ($plugins as $plugin) { - if ($plugin->is_enabled() && $plugin->is_visible()) { - $plugin->unlock($submission); - } - } $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, $flags); + } + } + + $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST); $logmessage = get_string('unlocksubmissionforstudent', @@ -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; } diff --git a/mod/assign/submission/file/locallib.php b/mod/assign/submission/file/locallib.php index e9d72e48b79..f981aa3d30e 100644 --- a/mod/assign/submission/file/locallib.php +++ b/mod/assign/submission/file/locallib.php @@ -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.')); + } } diff --git a/mod/assign/submission/onlinetext/locallib.php b/mod/assign/submission/onlinetext/locallib.php index 6f44e4eea41..87fcc1938ec 100644 --- a/mod/assign/submission/onlinetext/locallib.php +++ b/mod/assign/submission/onlinetext/locallib.php @@ -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); + } + } diff --git a/mod/assign/submissionplugin.php b/mod/assign/submissionplugin.php index 5b48a38f5b9..120f9b73f63 100644 --- a/mod/assign/submissionplugin.php +++ b/mod/assign/submissionplugin.php @@ -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) { } /** diff --git a/mod/assign/tests/base_test.php b/mod/assign/tests/base_test.php index cae9553c00d..91555408419 100644 --- a/mod/assign/tests/base_test.php +++ b/mod/assign/tests/base_test.php @@ -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; diff --git a/mod/assign/tests/externallib_test.php b/mod/assign/tests/externallib_test.php index 88a4aa2a665..31a13d3c4ab 100644 --- a/mod/assign/tests/externallib_test.php +++ b/mod/assign/tests/externallib_test.php @@ -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']); + + } } diff --git a/mod/assign/tests/locallib_test.php b/mod/assign/tests/locallib_test.php index 3ed48e8b6aa..48a4688322f 100644 --- a/mod/assign/tests/locallib_test.php +++ b/mod/assign/tests/locallib_test.php @@ -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); diff --git a/mod/assign/upgrade.txt b/mod/assign/upgrade.txt index 1f513fe7b3b..44c2f892be6 100644 --- a/mod/assign/upgrade.txt +++ b/mod/assign/upgrade.txt @@ -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 === diff --git a/mod/assign/version.php b/mod/assign/version.php index 7b06f0d62b4..e2cb6d00d1e 100644 --- a/mod/assign/version.php +++ b/mod/assign/version.php @@ -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; From 539af60201b5d9a3231d551e6b10783616314699 Mon Sep 17 00:00:00 2001 From: Damyon Wiese Date: Fri, 13 Sep 2013 11:50:00 +0800 Subject: [PATCH 2/4] MDL-37148 assign: codechecker cleanups --- mod/assign/externallib.php | 9 ++++++- mod/assign/feedback/file/locallib.php | 7 +++++- mod/assign/lib.php | 12 ++++----- mod/assign/locallib.php | 12 ++++----- mod/assign/submission/file/locallib.php | 7 +++++- mod/assign/tests/externallib_test.php | 33 +++++++++++++++++-------- 6 files changed, 55 insertions(+), 25 deletions(-) diff --git a/mod/assign/externallib.php b/mod/assign/externallib.php index 09db0b8f76b..18531bdba5e 100644 --- a/mod/assign/externallib.php +++ b/mod/assign/externallib.php @@ -1528,7 +1528,14 @@ class mod_assign_external extends external_api { * @return null * @since Moodle 2.6 */ - public static function save_grade($assignmentid, $userid, $grade, $attemptnumber, $addattempt, $workflowstate, $applytoall, $plugindata) { + public static function save_grade($assignmentid, + $userid, + $grade, + $attemptnumber, + $addattempt, + $workflowstate, + $applytoall, + $plugindata) { global $CFG, $USER; require_once("$CFG->dirroot/mod/assign/locallib.php"); diff --git a/mod/assign/feedback/file/locallib.php b/mod/assign/feedback/file/locallib.php index c537a438185..b4db0ae1c28 100644 --- a/mod/assign/feedback/file/locallib.php +++ b/mod/assign/feedback/file/locallib.php @@ -616,7 +616,12 @@ class assign_feedback_file extends assign_feedback_plugin { * @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.')); + return array( + 'files_filemanager' => new external_value( + PARAM_INT, + 'The id of a draft area containing files for this feedback.' + ) + ); } } diff --git a/mod/assign/lib.php b/mod/assign/lib.php index 49a13f10260..65e5cc254a4 100644 --- a/mod/assign/lib.php +++ b/mod/assign/lib.php @@ -280,11 +280,11 @@ function assign_get_coursemodule_info($coursemodule) { * @param stdClass $currentcontext Current context of block */ function assign_page_type_list($pagetype, $parentcontext, $currentcontext) { - $module_pagetype = array( + $modulepagetype = array( 'mod-assign-*' => get_string('page-mod-assign-x', 'assign'), 'mod-assign-view' => get_string('page-mod-assign-view', 'assign'), ); - return $module_pagetype; + return $modulepagetype; } /** @@ -656,10 +656,10 @@ function assign_get_recent_mod_activity(&$activities, } $groupmode = groups_get_activity_groupmode($cm, $course); - $cm_context = context_module::instance($cm->id); - $grader = has_capability('moodle/grade:viewall', $cm_context); - $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context); - $viewfullnames = has_capability('moodle/site:viewfullnames', $cm_context); + $cmcontext = context_module::instance($cm->id); + $grader = has_capability('moodle/grade:viewall', $cmcontext); + $accessallgroups = has_capability('moodle/site:accessallgroups', $cmcontext); + $viewfullnames = has_capability('moodle/site:viewfullnames', $cmcontext); if (is_null($modinfo->get_groups())) { // Load all my groups and cache it in modinfo. diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index f4eb9c5e592..30bd6dcf043 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -2003,10 +2003,10 @@ class assign { $submitted = get_string('submissionstatus_', 'assign'); } } - $grading_info = grade_get_grades($course->id, 'mod', 'assign', $cm->instance, $USER->id); - if (isset($grading_info->items[0]->grades[$USER->id]) && - !$grading_info->items[0]->grades[$USER->id]->hidden ) { - $grade = $grading_info->items[0]->grades[$USER->id]->str_grade; + $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $cm->instance, $USER->id); + if (isset($gradinginfo->items[0]->grades[$USER->id]) && + !$gradinginfo->items[0]->grades[$USER->id]->hidden ) { + $grade = $gradinginfo->items[0]->grades[$USER->id]->str_grade; } else { $grade = '-'; } @@ -3651,7 +3651,8 @@ class assign { // Only show the grade if it is not hidden in gradebook. if (!empty($gradebookgrade->grade) && ($cangrade || !$gradebookgrade->hidden)) { if ($controller = $gradingmanager->get_active_controller()) { - $controller->set_grade_range(make_grades_menu($this->get_instance()->grade), $this->get_instance()->grade > 0); + $menu = make_grades_menu($this->get_instance()->grade); + $controller->set_grade_range($menu, $this->get_instance()->grade > 0); $gradefordisplay = $controller->render_grade($PAGE, $grade->id, $gradingitem, @@ -6023,7 +6024,6 @@ class assign { } } - $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST); $logmessage = get_string('unlocksubmissionforstudent', diff --git a/mod/assign/submission/file/locallib.php b/mod/assign/submission/file/locallib.php index f981aa3d30e..7b8facb37e2 100644 --- a/mod/assign/submission/file/locallib.php +++ b/mod/assign/submission/file/locallib.php @@ -484,6 +484,11 @@ class assign_submission_file extends assign_submission_plugin { * @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.')); + return array( + 'files_filemanager' => new external_value( + PARAM_INT, + 'The id of a draft area containing files for this submission.' + ) + ); } } diff --git a/mod/assign/tests/externallib_test.php b/mod/assign/tests/externallib_test.php index 31a13d3c4ab..aeb0cb3eb7f 100644 --- a/mod/assign/tests/externallib_test.php +++ b/mod/assign/tests/externallib_test.php @@ -481,7 +481,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase { $plugin = $assign->get_submission_plugin_by_type('onlinetext'); $plugin->save($submission, $data); - // Ready to test + // Ready to test. $this->setUser($teacher); $students = array($student1->id, $student2->id); $result = mod_assign_external::lock_submissions($instance->id, $students); @@ -545,7 +545,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase { $plugin = $assign->get_submission_plugin_by_type('onlinetext'); $plugin->save($submission, $data); - // Ready to test + // Ready to test. $this->setUser($teacher); $students = array($student1->id, $student2->id); $result = mod_assign_external::lock_submissions($instance->id, $students); @@ -786,7 +786,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase { $result = mod_assign_external::submit_for_grading($instance->id); $this->assertEquals(0, count($result)); - // Ready to test + // 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)); @@ -868,7 +868,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase { $fs->create_file_from_string($filerecord, 'image contents (not really)'); - // Now try a submission + // Now try a submission. $submissionpluginparams = array(); $submissionpluginparams['files_filemanager'] = $draftidfile; $onlinetexteditorparams = array('text'=>'Yeeha!', @@ -936,13 +936,20 @@ class mod_assign_external_testcase extends externallib_advanced_testcase { $fs = get_file_storage(); $fs->create_file_from_string($filerecord, 'text contents'); - // Now try a grade + // 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); + $result = mod_assign_external::save_grade($instance->id, + $student1->id, + 50.0, + -1, + true, + 'released', + false, + $feedbackpluginparams); // No warnings. $this->assertEquals(0, count($result)); @@ -950,7 +957,6 @@ class mod_assign_external_testcase extends externallib_advanced_testcase { $result = mod_assign_external::get_grades(array($instance->id)); $this->assertEquals($result['assignments'][0]['grades'][0]['grade'], '50.0'); - } /** @@ -988,7 +994,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase { $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id); - // Now try a submission + // Now try a submission. $this->setUser($student1); $draftidonlinetext = file_get_unused_draft_itemid(); $submissionpluginparams = array(); @@ -1001,13 +1007,20 @@ class mod_assign_external_testcase extends externallib_advanced_testcase { $this->setUser($teacher); // Add a grade and reopen the attempt. - // Now try a grade + // 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); + $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. From 1561a37cabff25cd8a8dcc1803c93a6a950b7889 Mon Sep 17 00:00:00 2001 From: Damyon Wiese Date: Fri, 13 Sep 2013 12:41:51 +0800 Subject: [PATCH 3/4] MDL-37148 assign: Fix all phpdocs warnings from moodlecheck (except tests) --- mod/assign/externallib.php | 25 +++++++++++++------ ...ssignfeedback_comments_subplugin.class.php | 8 ++++-- mod/assign/feedback/comments/settings.php | 2 +- ...re_assignfeedback_file_subplugin.class.php | 4 ++- mod/assign/feedback/file/locallib.php | 2 +- mod/assign/feedback/file/settings.php | 2 +- mod/assign/gradingtable.php | 6 +++-- mod/assign/lib.php | 10 +++++--- mod/assign/locallib.php | 17 ++++++++----- mod/assign/mod_form.php | 11 ++++++++ mod/assign/renderable.php | 23 +++++++++-------- ..._assignsubmission_file_subplugin.class.php | 4 ++- mod/assign/submission/file/locallib.php | 1 + ...nsubmission_onlinetext_subplugin.class.php | 4 ++- mod/assign/submissionplugin.php | 6 +++-- 15 files changed, 86 insertions(+), 39 deletions(-) diff --git a/mod/assign/externallib.php b/mod/assign/externallib.php index 18531bdba5e..4a679d9f1a4 100644 --- a/mod/assign/externallib.php +++ b/mod/assign/externallib.php @@ -29,6 +29,8 @@ require_once("$CFG->libdir/externallib.php"); /** * Assign functions + * @copyright 2012 Paul Charsley + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class mod_assign_external extends external_api { @@ -36,7 +38,7 @@ 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 $warningcode - The key for the warning message * @param string $detail - A description of the error * @return array - Warning structure containing item, itemid, warningcode, message */ @@ -85,7 +87,7 @@ class mod_assign_external extends external_api { /** * Returns grade information from assign_grades for the requested assignment ids - * @param array of ints $assignmentids + * @param int[] $assignmentids * @param int $since only return records with timemodified >= since * @return array of grade records for each requested assignment * @since Moodle 2.4 @@ -507,7 +509,7 @@ class mod_assign_external extends external_api { /** * Returns submissions for the requested assignment ids * - * @param array of ints $assignmentids + * @param int[] $assignmentids * @param string $status only return submissions with this status * @param int $since only return submissions with timemodified >= since * @param int $before only return submissions with timemodified <= before @@ -752,7 +754,7 @@ class mod_assign_external extends external_api { /** * Returns user flag information from assign_user_flags for the requested assignment ids - * @param array of ints $assignmentids + * @param int[] $assignmentids * @return array of user flag records for each requested assignment * @since Moodle 2.6 */ @@ -906,7 +908,7 @@ class mod_assign_external extends external_api { /** * Returns user mapping information from assign_user_mapping for the requested assignment ids - * @param array of ints $assignmentids + * @param int[] $assignmentids * @return array of user mapping records for each requested assignment * @since Moodle 2.6 */ @@ -1438,10 +1440,11 @@ class mod_assign_external extends external_api { } /** - * Save a student submission for a single assignment. + * Save a student submission for a single assignment * * @param int $assignmentid The id of the assignment - * @return array of warnings to indicate any errors. + * @param array $plugindata - The submitted data for plugins + * @return array of warnings to indicate any errors * @since Moodle 2.6 */ public static function save_submission($assignmentid, $plugindata) { @@ -1525,6 +1528,13 @@ class mod_assign_external extends external_api { * Save a student grade for a single assignment. * * @param int $assignmentid The id of the assignment + * @param int $userid The id of the user + * @param float $grade The grade + * @param int $attemptnumber The attempt number + * @param bool $addattempt Allow another attempt + * @param string $workflowstate New workflow state + * @param bool $applytoall Apply the grade to all members of the group + * @param array $plugindata Custom data used by plugins * @return null * @since Moodle 2.6 */ @@ -1593,6 +1603,7 @@ class mod_assign_external extends external_api { /** * Copy a students previous attempt to a new attempt. * + * @param int $assignmentid * @return array of warnings to indicate any errors. * @since Moodle 2.6 */ diff --git a/mod/assign/feedback/comments/backup/moodle2/restore_assignfeedback_comments_subplugin.class.php b/mod/assign/feedback/comments/backup/moodle2/restore_assignfeedback_comments_subplugin.class.php index 1efd24e79a8..9b95aed42d6 100644 --- a/mod/assign/feedback/comments/backup/moodle2/restore_assignfeedback_comments_subplugin.class.php +++ b/mod/assign/feedback/comments/backup/moodle2/restore_assignfeedback_comments_subplugin.class.php @@ -15,7 +15,9 @@ // along with Moodle. If not, see . /** - * Restore subplugin class that provides the necessary information needed to restore + * Restore subplugin class. + * + * Provides the necessary information needed to restore * one assign_submission subplugin. * * @package assignfeedback_comments @@ -26,7 +28,9 @@ defined('MOODLE_INTERNAL') || die(); /** - * Restore subplugin class that provides the necessary information needed to restore + * Restore subplugin class. + * + * Provides the necessary information needed to restore * one assignfeedback subplugin. * * @package assignfeedback_comments diff --git a/mod/assign/feedback/comments/settings.php b/mod/assign/feedback/comments/settings.php index f85cd5ad42e..f8d45ae927b 100644 --- a/mod/assign/feedback/comments/settings.php +++ b/mod/assign/feedback/comments/settings.php @@ -17,7 +17,7 @@ /** * This file defines the admin settings for this plugin * - * @package assignsubmission_comments + * @package assignfeedback_comments * @copyright 2012 NetSpot {@link http://www.netspot.com.au} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/mod/assign/feedback/file/backup/moodle2/restore_assignfeedback_file_subplugin.class.php b/mod/assign/feedback/file/backup/moodle2/restore_assignfeedback_file_subplugin.class.php index df258d3cccb..a408afd548a 100644 --- a/mod/assign/feedback/file/backup/moodle2/restore_assignfeedback_file_subplugin.class.php +++ b/mod/assign/feedback/file/backup/moodle2/restore_assignfeedback_file_subplugin.class.php @@ -24,7 +24,9 @@ defined('MOODLE_INTERNAL') || die(); /** - * Restore subplugin class that provides the necessary information needed + * Restore subplugin class. + * + * Provides the necessary information needed * to restore one assign_feedback subplugin. * * @package assignfeedback_file diff --git a/mod/assign/feedback/file/locallib.php b/mod/assign/feedback/file/locallib.php index b4db0ae1c28..7b64583ac8e 100644 --- a/mod/assign/feedback/file/locallib.php +++ b/mod/assign/feedback/file/locallib.php @@ -36,7 +36,7 @@ define('ASSIGNFEEDBACK_FILE_MAXFILEUNZIPTIME', 120); /** * Library class for file feedback plugin extending feedback plugin base class. * - * @package asignfeedback_file + * @package assignfeedback_file * @copyright 2012 NetSpot {@link http://www.netspot.com.au} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/mod/assign/feedback/file/settings.php b/mod/assign/feedback/file/settings.php index 0719b0bbe70..ead9c885c87 100644 --- a/mod/assign/feedback/file/settings.php +++ b/mod/assign/feedback/file/settings.php @@ -17,7 +17,7 @@ /** * This file defines the admin settings for this plugin * - * @package assignsubmission_onlinetext + * @package assignfeedback_file * @copyright 2012 NetSpot {@link http://www.netspot.com.au} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/mod/assign/gradingtable.php b/mod/assign/gradingtable.php index 4b795fe20e7..d7c7e276cb8 100644 --- a/mod/assign/gradingtable.php +++ b/mod/assign/gradingtable.php @@ -71,6 +71,7 @@ class assign_grading_table extends table_sql implements renderable { * @param string $filter The current filter * @param int $rowoffset For showing a subsequent page of results * @param bool $quickgrading Is this table wrapped in a quickgrading form? + * @param string $downloadfilename */ public function __construct(assign $assignment, $perpage, @@ -435,6 +436,7 @@ class assign_grading_table extends table_sql implements renderable { /** * Add a column with an ID that uniquely identifies this user in this assignment. * + * @param stdClass $row * @return string */ public function col_recordid(stdClass $row) { @@ -621,8 +623,8 @@ class assign_grading_table extends table_sql implements renderable { * Use a static cache to try and reduce DB calls. * * @param int $userid The user id for this submission - * @param int $groupid The groupid (returned) - * @param mixed $submission The stdClass submission or false (returned) + * @param int $group The groupid (returned) + * @param stdClass|false $submission The stdClass submission or false (returned) * @param int $attemptnumber Return a specific attempt number (-1 for latest) */ protected function get_group_and_submission($userid, &$group, &$submission, $attemptnumber) { diff --git a/mod/assign/lib.php b/mod/assign/lib.php index 65e5cc254a4..99bd42569a8 100644 --- a/mod/assign/lib.php +++ b/mod/assign/lib.php @@ -16,6 +16,7 @@ /** * This file contains the moodle hooks for the assign module. + * * It delegates most functions to the assignment class. * * @package mod_assign @@ -59,8 +60,9 @@ function assign_delete_instance($id) { * This function is used by the reset_course_userdata function in moodlelib. * This function will remove all assignment submissions and feedbacks in the database * and clean up any related data. - * @param $data the data submitted from the reset course. - * @return array status array + * + * @param stdClass $data the data submitted from the reset course. + * @return array */ function assign_reset_userdata($data) { global $CFG, $DB; @@ -109,7 +111,7 @@ function assign_reset_gradebook($courseid, $type='') { /** * Implementation of the function for printing the form elements that control * whether the course reset functionality affects the assignment. - * @param $mform form passed by reference + * @param moodleform $mform form passed by reference */ function assign_reset_course_form_definition(&$mform) { $mform->addElement('header', 'assignheader', get_string('modulenameplural', 'assign')); @@ -131,7 +133,7 @@ function assign_reset_course_form_defaults($course) { * * This is done by calling the update_instance() method of the assignment type class * @param stdClass $data - * @param $form + * @param stdClass $form - unused * @return object */ function assign_update_instance(stdClass $data, $form) { diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index 30bd6dcf043..5ef2ccdea0c 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -674,7 +674,7 @@ class assign { * Actual implementation of the reset course functionality, delete all the * assignment submissions for course $data->courseid. * - * @param $data the data submitted from the reset course. + * @param stdClass $data the data submitted from the reset course. * @return array status array */ public function reset_userdata($data) { @@ -2142,6 +2142,7 @@ class assign { /** * Rewrite plugin file urls so they resolve correctly in an exported zip. * + * @param string $text - The replacement text * @param stdClass $user - The user record * @param assign_plugin $plugin - The assignment plugin */ @@ -2231,6 +2232,7 @@ class assign { /** * Display a continue page. * + * @param string $message - The message to display * @return string */ protected function view_savegrading_result($message) { @@ -3237,6 +3239,7 @@ class assign { /** * Allows the plugin to show a batch grading operation page. * + * @param moodleform $mform * @return none */ protected function view_plugin_grading_batch_operation($mform) { @@ -3453,7 +3456,7 @@ class assign { /** * Ask the user to confirm they want to submit their work for grading. * - * @param $mform moodleform - null unless form validation has failed + * @param moodleform $mform - null unless form validation has failed * @return string */ protected function check_submit_for_grading($mform) { @@ -4242,7 +4245,6 @@ class assign { * @param string $modulename * @param stdClass $coursemodule * @param string $assignmentname - * @param stdClass $info */ protected static function format_notification_message_html($messagetype, $info, @@ -4282,6 +4284,8 @@ class assign { * @param stdClass $course * @param string $modulename * @param string $assignmentname + * @param bool $blindmarking + * @param int $uniqueidforuser * @return void */ public static function send_assignment_notification($userfrom, @@ -4531,7 +4535,7 @@ class assign { /** * Assignment submission is processed before grading. * - * @param $mform If validation failed when submitting this form - this is the moodleform. + * @param moodleform|null $mform If validation failed when submitting this form - this is the moodleform. * It can be null. * @return bool Return false if the validation fails. This affects which page is displayed next. */ @@ -5364,6 +5368,7 @@ class assign { * This is specific to the assignment, marker and student. * * @param int $userid - The student userid + * @param stdClass|false $grade - The grade record * @param bool $gradingdisabled * @return mixed gradingform_instance|null $gradinginstance */ @@ -6064,7 +6069,7 @@ class assign { * * @param stdClass $formdata - the data from the form * @param int $userid - the user to apply the grade to - * @param int attemptnumber - The attempt number to apply the grade to. + * @param int $attemptnumber - The attempt number to apply the grade to. * @return void */ protected function apply_grade_to_user($formdata, $userid, $attemptnumber) { @@ -6175,7 +6180,6 @@ class assign { * * @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) { @@ -6602,6 +6606,7 @@ class assign { /** * Lookup this user id and return the unique id for this assignment. * + * @param int $assignid The assignment id * @param int $userid The userid to lookup * @return int The unique id */ diff --git a/mod/assign/mod_form.php b/mod/assign/mod_form.php index edfe6ebe1e7..1cd183ca0a0 100644 --- a/mod/assign/mod_form.php +++ b/mod/assign/mod_form.php @@ -265,6 +265,11 @@ class mod_assign_mod_form extends moodleform_mod { $assignment->plugin_data_preprocessing($defaultvalues); } + /** + * Add any custom completion rules to the form. + * + * @return array Contains the names of the added form elements + */ public function add_completion_rules() { $mform =& $this->_form; @@ -272,6 +277,12 @@ class mod_assign_mod_form extends moodleform_mod { return array('completionsubmit'); } + /** + * Determines if completion is enabled for this module. + * + * @param array $data + * @return bool + */ public function completion_rule_enabled($data) { return !empty($data['completionsubmit']); } diff --git a/mod/assign/renderable.php b/mod/assign/renderable.php index 5e40398be74..6b033237676 100644 --- a/mod/assign/renderable.php +++ b/mod/assign/renderable.php @@ -42,6 +42,7 @@ class assign_submit_for_grading_page implements renderable { * Constructor * @param string $notifications - Any mesages to display * @param int $coursemoduleid + * @param moodleform $confirmform */ public function __construct($notifications, $coursemoduleid, $confirmform) { $this->notifications = $notifications; @@ -69,6 +70,7 @@ class assign_gradingmessage implements renderable { * Constructor * @param string $heading This is the heading to display * @param string $message This is the message to display + * @param int $coursemoduleid */ public function __construct($heading, $message, $coursemoduleid) { $this->heading = $heading; @@ -404,6 +406,7 @@ class assign_submission_status implements renderable { * @param int $extensionduedate - Any extension to the due date granted for this user * @param context $context - Any extension to the due date granted for this user * @param bool $blindmarking - Should we hide student identities from graders? + * @param string $gradingcontrollerpreview * @param string $attemptreopenmethod - The method of reopening student attempts. * @param int $maxattempts - How many attempts can a student make? */ @@ -493,14 +496,14 @@ class assign_attempt_history implements renderable { /** * Constructor * - * @param $submissions - * @param $grades - * @param $submissionplugins - * @param $feedbackplugins - * @param $coursemoduleid - * @param $returnaction - * @param $returnparams - * @param $cangrade + * @param array $submissions + * @param array $grades + * @param array $submissionplugins + * @param array $feedbackplugins + * @param int $coursemoduleid + * @param string $returnaction + * @param array $returnparams + * @param bool $cangrade */ public function __construct($submissions, $grades, @@ -648,8 +651,8 @@ class assign_course_index_summary implements renderable { /** * constructor * - * @param $usesections boolean - True if this course format uses sections - * @param $courseformatname string - The id of this course format + * @param boolean $usesections - True if this course format uses sections + * @param string $courseformatname - The id of this course format */ public function __construct($usesections, $courseformatname) { $this->usesections = $usesections; diff --git a/mod/assign/submission/file/backup/moodle2/restore_assignsubmission_file_subplugin.class.php b/mod/assign/submission/file/backup/moodle2/restore_assignsubmission_file_subplugin.class.php index 8f575e538a3..2447270a7c5 100644 --- a/mod/assign/submission/file/backup/moodle2/restore_assignsubmission_file_subplugin.class.php +++ b/mod/assign/submission/file/backup/moodle2/restore_assignsubmission_file_subplugin.class.php @@ -23,7 +23,9 @@ */ /** - * restore subplugin class that provides the necessary information + * Restore subplugin class. + * + * Provides the necessary information * needed to restore one assign_submission subplugin. * * @package assignsubmission_file diff --git a/mod/assign/submission/file/locallib.php b/mod/assign/submission/file/locallib.php index 7b8facb37e2..a2c2c8be819 100644 --- a/mod/assign/submission/file/locallib.php +++ b/mod/assign/submission/file/locallib.php @@ -249,6 +249,7 @@ class assign_submission_file extends assign_submission_plugin { * Produce a list of files suitable for export that represent this feedback or submission * * @param stdClass $submission The submission + * @param stdClass $user The user record - unused * @return array - return an array of files indexed by filename */ public function get_files(stdClass $submission, stdClass $user) { diff --git a/mod/assign/submission/onlinetext/backup/moodle2/restore_assignsubmission_onlinetext_subplugin.class.php b/mod/assign/submission/onlinetext/backup/moodle2/restore_assignsubmission_onlinetext_subplugin.class.php index bfa234289c6..ac975860f55 100644 --- a/mod/assign/submission/onlinetext/backup/moodle2/restore_assignsubmission_onlinetext_subplugin.class.php +++ b/mod/assign/submission/onlinetext/backup/moodle2/restore_assignsubmission_onlinetext_subplugin.class.php @@ -23,7 +23,9 @@ */ /** - * restore subplugin class that provides the necessary information needed to restore + * Restore subplugin class. + * + * Provides the necessary information needed to restore * one assign_submission subplugin. * * @package assignsubmission_onlinetext diff --git a/mod/assign/submissionplugin.php b/mod/assign/submissionplugin.php index 120f9b73f63..70388cb40f9 100644 --- a/mod/assign/submissionplugin.php +++ b/mod/assign/submissionplugin.php @@ -79,13 +79,15 @@ abstract class assign_submission_plugin extends assign_plugin { /** * Copy the plugin specific submission data to a new submission record. * + * @param stdClass $oldsubmission - Old submission record + * @param stdClass $submission - New submission record * @return bool */ public function copy_submission( stdClass $oldsubmission, stdClass $submission) { return true; } - /* + /** * Carry out any extra processing required when the work is locked. * * @param stdClass|false $submission - assign_submission data if any @@ -98,7 +100,7 @@ abstract class assign_submission_plugin extends assign_plugin { /** * Carry out any extra processing required when the work is unlocked. * - * @param stdClass $submission|false - assign_submission data if any + * @param stdClass|false $submission - assign_submission data if any * @param stdClass $flags - User flags record * @return void */ From 91327d96ca02d945d439a9169974f5576e41c03a Mon Sep 17 00:00:00 2001 From: Damyon Wiese Date: Mon, 16 Sep 2013 09:17:42 +0800 Subject: [PATCH 4/4] MDL-37148 assign: Fix missing parameter for submit_for_grading webservice. It is required to specify if the user has accepted the submission statement to make sure it goes in the log/event. --- mod/assign/externallib.php | 15 ++++++++++----- mod/assign/locallib.php | 10 +++++++--- mod/assign/tests/externallib_test.php | 14 ++++++++++---- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/mod/assign/externallib.php b/mod/assign/externallib.php index 4a679d9f1a4..8aa0a3537ad 100644 --- a/mod/assign/externallib.php +++ b/mod/assign/externallib.php @@ -1223,14 +1223,15 @@ class mod_assign_external extends external_api { } /** - * Describes the parameters for unlock_submissions + * Describes the parameters for submit_for_grading * @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') + 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'), + 'acceptsubmissionstatement' => new external_value(PARAM_BOOL, 'Accept the assignment submission statement') ) ); } @@ -1242,12 +1243,13 @@ class mod_assign_external extends external_api { * @return array of warnings to indicate any errors. * @since Moodle 2.6 */ - public static function submit_for_grading($assignmentid) { + public static function submit_for_grading($assignmentid, $acceptsubmissionstatement) { global $CFG, $USER; require_once("$CFG->dirroot/mod/assign/locallib.php"); $params = self::validate_parameters(self::submit_for_grading_parameters(), - array('assignmentid' => $assignmentid)); + array('assignmentid' => $assignmentid, + 'acceptsubmissionstatement' => $acceptsubmissionstatement)); $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST); $context = context_module::instance($cm->id); @@ -1255,7 +1257,10 @@ class mod_assign_external extends external_api { $assignment = new assign($context, $cm, null); $warnings = array(); - if (!$assignment->submit_for_grading()) { + $data = new stdClass(); + $data->submissionstatement = $acceptsubmissionstatement; + + if (!$assignment->submit_for_grading($data)) { $detail = 'User id: ' . $USER->id . ', Assignment id: ' . $assignmentid; $warnings[] = self::generate_warning($assignmentid, 'couldnotsubmitforgrading', diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index 5ef2ccdea0c..3cbccafdeff 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -4472,7 +4472,7 @@ class assign { * * @return bool Return false if the submission was not submitted. */ - public function submit_for_grading() { + public function submit_for_grading($data) { global $USER; // Need submit permission to submit an assignment. @@ -4490,6 +4490,10 @@ class assign { return false; } + if ($instance->requiresubmissionstatement && !$data->submissionstatement) { + return false; + } + if ($submission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) { // Give each submission plugin a chance to process the submission. $plugins = $this->get_submission_plugins(); @@ -4506,7 +4510,7 @@ class assign { $completion->update_state($this->get_course_module(), COMPLETION_COMPLETE, $USER->id); } - if (isset($data->submissionstatement)) { + if (!empty($data->submissionstatement)) { $logmessage = get_string('submissionstatementacceptedlog', 'mod_assign', fullname($USER)); @@ -4571,7 +4575,7 @@ class assign { if ($mform->get_data() == false) { return false; } - return $this->submit_for_grading(); + return $this->submit_for_grading($data); } return true; } diff --git a/mod/assign/tests/externallib_test.php b/mod/assign/tests/externallib_test.php index aeb0cb3eb7f..eebc7505692 100644 --- a/mod/assign/tests/externallib_test.php +++ b/mod/assign/tests/externallib_test.php @@ -584,6 +584,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase { $params['assignsubmission_onlinetext_enabled'] = 1; $params['submissiondrafts'] = 1; $params['sendnotifications'] = 0; + $params['requiresubmissionstatement'] = 1; $instance = $generator->create_instance($params); $cm = get_coursemodule_from_instance('assign', $instance->id); $context = context_module::instance($cm->id); @@ -607,7 +608,12 @@ class mod_assign_external_testcase extends externallib_advanced_testcase { $plugin = $assign->get_submission_plugin_by_type('onlinetext'); $plugin->save($submission, $data); - $result = mod_assign_external::submit_for_grading($instance->id); + $result = mod_assign_external::submit_for_grading($instance->id, false); + + // Should be 1 fail because the submission statement was not aceptted. + $this->assertEquals(1, count($result)); + + $result = mod_assign_external::submit_for_grading($instance->id, true); // Check for 0 warnings. $this->assertEquals(0, count($result)); @@ -657,7 +663,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase { $studentrole->id); $this->setUser($student1); - $result = mod_assign_external::submit_for_grading($instance->id); + $result = mod_assign_external::submit_for_grading($instance->id, true); // Check for 0 warnings. $this->assertEquals(1, count($result)); @@ -675,7 +681,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase { $this->assertEquals(0, count($result)); $this->setUser($student1); - $result = mod_assign_external::submit_for_grading($instance->id); + $result = mod_assign_external::submit_for_grading($instance->id, true); $this->assertEquals(0, count($result)); $this->setUser($student1); @@ -783,7 +789,7 @@ class mod_assign_external_testcase extends externallib_advanced_testcase { // Create a student1 with an online text submission. // Simulate a submission. $this->setUser($student1); - $result = mod_assign_external::submit_for_grading($instance->id); + $result = mod_assign_external::submit_for_grading($instance->id, true); $this->assertEquals(0, count($result)); // Ready to test.