Merge branch 'MDL-73073-master_assign_timing_web_services' of https://github.com/andrewmadden/moodle

This commit is contained in:
Ilya Tregubov
2022-03-10 15:01:06 +06:00
12 changed files with 1093 additions and 202 deletions
+115
View File
@@ -0,0 +1,115 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_assign\external;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("$CFG->libdir/externallib.php");
require_once("$CFG->dirroot/mod/assign/locallib.php");
/**
* Extend the base external_api class with mod_assign utility methods.
*
* @package mod_assign
* @author Andrew Madden <[email protected]>
* @copyright 2021 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external_api 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
* @param string $detail - A description of the error
* @return array - Warning structure containing item, itemid, warningcode, message
*/
protected static function generate_warning(int $assignmentid, string $warningcode, string $detail): array {
$warningmessages = [
'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.',
'couldnotstartsubmission' => 'Could not start submission with time limit.',
'submissionnotopen' => 'This assignment is not open for submissions',
'timelimitnotenabled' => 'Time limit is not enabled for assignment.',
'opensubmissionexists' => 'Open assignment submission already exists.',
];
$message = $warningmessages[$warningcode];
if (empty($message)) {
$message = 'Unknown warning type.';
}
return [
'item' => s($detail),
'itemid' => $assignmentid,
'warningcode' => $warningcode,
'message' => $message,
];
}
/**
* Utility function for validating an assign.
*
* @param int $assignid assign instance id
* @return array array containing the assign, course, context and course module objects
* @since Moodle 3.2
*/
protected static function validate_assign(int $assignid): array {
global $DB;
// Request and permission validation.
$assign = $DB->get_record('assign', ['id' => $assignid], 'id', MUST_EXIST);
list($course, $cm) = get_course_and_cm_from_instance($assign, 'assign');
$context = \context_module::instance($cm->id);
// Please, note that is not required to check mod/assign:view because is done by validate_context->require_login.
self::validate_context($context);
$assign = new \assign($context, $cm, $course);
return [$assign, $course, $cm, $context];
}
/**
* Get a submission from an assignment for a user. Encapsulates checking whether it's a solo or team submission.
*
* @param \assign $assignment Assignment object.
* @param int|null $userid User id.
* @param int $groupid Group id.
* @param bool $create Whether a new submission should be created.
* @param int $attemptnumber Attempt number. Use -1 for last attempt.
* @return bool|\stdClass
*/
protected static function get_user_or_group_submission(\assign $assignment, int $userid = null,
int $groupid = 0, bool $create = false, int $attemptnumber = -1) {
if ($assignment->get_instance($userid)->teamsubmission) {
$submission = $assignment->get_group_submission($userid, $groupid, $create, $attemptnumber);
} else {
$submission = $assignment->get_user_submission($userid, $create, $attemptnumber);
}
return $submission;
}
}
+113
View File
@@ -0,0 +1,113 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_assign\external;
/**
* External function to notify Moodle that an assignment submission is starting.
*
* @package mod_assign
* @author Andrew Madden <[email protected]>
* @copyright 2021 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class start_submission extends external_api {
/**
* Describes the parameters for submission_start.
*
* @return \external_function_parameters
* @since Moodle 4.0
*/
public static function execute_parameters(): \external_function_parameters {
return new \external_function_parameters ([
'assignid' => new \external_value(PARAM_INT, 'Assignment instance id'),
]
);
}
/**
* Call to start an assignment submission.
*
* @param int $assignid Assignment ID.
* @return array
* @since Moodle 4.0
*/
public static function execute(int $assignid): array {
global $DB, $USER;
$result = $warnings = [];
$submission = null;
[
'assignid' => $assignid,
] = self::validate_parameters(self::execute_parameters(), [
'assignid' => $assignid,
]);
list($assignment, $course, $cm, $context) = self::validate_assign($assignid);
$assignment->update_effective_access($USER->id);
$latestsubmission = external_api::get_user_or_group_submission($assignment, $USER->id);
if (!$assignment->submissions_open($USER->id)) {
$warnings[] = self::generate_warning($assignid,
'submissionnotopen',
get_string('submissionnotopen', 'assign'));
}
if (!$assignment->is_time_limit_enabled()) {
$warnings[] = self::generate_warning($assignid,
'timelimitnotenabled',
get_string('timelimitnotenabled', 'assign'));
} else if ($assignment->is_attempt_in_progress()) {
$warnings[] = self::generate_warning($assignid,
'opensubmissionexists',
get_string('opensubmissionexists', 'assign'));
}
if (empty($warnings)) {
// If there is an open submission with no start time, use latest submission, otherwise create a new submission.
if (!empty($latestsubmission)
&& $latestsubmission->status !== ASSIGN_SUBMISSION_STATUS_SUBMITTED
&& empty($latestsubmission->timestarted)) {
$submission = $latestsubmission;
} else {
$submission = external_api::get_user_or_group_submission($assignment, $USER->id, 0, true);
}
// Set the start time of the submission.
$submission->timestarted = time();
$DB->update_record('assign_submission', $submission);
}
$result['submissionid'] = $submission ? $submission->id : 0;
$result['warnings'] = $warnings;
return $result;
}
/**
* Describes the submission_start return value.
*
* @return \external_single_structure
* @since Moodle 4.0
*/
public static function execute_returns(): \external_single_structure {
return new \external_single_structure([
'submissionid' => new \external_value(PARAM_INT, 'New submission ID.'),
'warnings' => new \external_warnings(),
]);
}
}
+8 -1
View File
@@ -240,5 +240,12 @@ $functions = array(
'capabilities' => 'mod/assign:view',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_assign_start_submission' => [
'classname' => 'mod_assign\external\start_submission',
'methodname' => 'execute',
'description' => 'Start a submission for user if assignment has a time limit.',
'type' => 'write',
'capabilities' => 'mod/assign:view',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
],
);
+56 -60
View File
@@ -34,39 +34,7 @@ require_once("$CFG->dirroot/mod/assign/locallib.php");
* @copyright 2012 Paul Charsley
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
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
* @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' => s($detail),
'itemid'=>$assignmentid,
'warningcode'=>$warningcode,
'message'=>$message);
}
class mod_assign_external extends \mod_assign\external\external_api {
/**
* Describes the parameters for get_grades
@@ -384,7 +352,11 @@ class mod_assign_external extends external_api {
'm.requiresubmissionstatement, '.
'm.preventsubmissionnotingroup, '.
'm.intro, '.
'm.introformat';
'm.introformat,' .
'm.activity,' .
'm.activityformat,' .
'm.timelimit,' .
'm.submissionattachments';
$coursearray = array();
foreach ($courses as $id => $course) {
$assignmentarray = array();
@@ -457,6 +429,8 @@ class mod_assign_external extends external_api {
'markingallocation' => $module->markingallocation,
'requiresubmissionstatement' => $module->requiresubmissionstatement,
'preventsubmissionnotingroup' => $module->preventsubmissionnotingroup,
'timelimit' => $module->timelimit,
'submissionattachments' => $module->submissionattachments,
'configs' => $configarray
);
@@ -468,9 +442,10 @@ class mod_assign_external extends external_api {
$options);
$assignment['introfiles'] = external_util::get_area_files($context->id, 'mod_assign', 'intro', false,
false);
$assignment['introattachments'] = external_util::get_area_files($context->id, 'mod_assign',
ASSIGN_INTROATTACHMENT_FILEAREA, 0);
if ($assign->should_provide_intro_attachments($USER->id)) {
$assignment['introattachments'] = external_util::get_area_files($context->id, 'mod_assign',
ASSIGN_INTROATTACHMENT_FILEAREA, 0);
}
}
if ($module->requiresubmissionstatement) {
@@ -497,6 +472,13 @@ class mod_assign_external extends external_api {
}
}
if ($module->activity && $assign->submissions_open($USER->id, true)) {
list($assignment['activity'], $assignment['activityformat']) = external_format_text($module->activity,
$module->activityformat, $context->id, 'mod_assign', ASSIGN_ACTIVITYATTACHMENT_FILEAREA);
$assignment['activityattachments'] = external_util::get_area_files($context->id, 'mod_assign',
ASSIGN_ACTIVITYATTACHMENT_FILEAREA, 0);
}
$assignmentarray[] = $assignment;
}
}
@@ -561,6 +543,12 @@ class mod_assign_external extends external_api {
'introformat' => new external_format_value('intro', VALUE_OPTIONAL),
'introfiles' => new external_files('Files in the introduction text', VALUE_OPTIONAL),
'introattachments' => new external_files('intro attachments files', VALUE_OPTIONAL),
'activity' => new external_value(PARAM_RAW, 'Description of activity', VALUE_OPTIONAL),
'activityformat' => new external_format_value('activity', VALUE_OPTIONAL),
'activityattachments' => new external_files('Files from activity field', VALUE_OPTIONAL),
'timelimit' => new external_value(PARAM_INT, 'Time limit to complete assigment', VALUE_OPTIONAL),
'submissionattachments' => new external_value(PARAM_INT,
'Flag to only show files during submission', VALUE_OPTIONAL),
), 'assignment information object');
}
@@ -759,7 +747,7 @@ class mod_assign_external extends external_api {
WHERE mxs.assignment = :assignid1 GROUP BY mxs.userid, mxs.groupid';
$sql = "SELECT mas.id, mas.assignment,mas.userid,".
"mas.timecreated,mas.timemodified,mas.status,mas.groupid,mas.attemptnumber ".
"mas.timecreated,mas.timemodified,mas.timestarted,mas.status,mas.groupid,mas.attemptnumber ".
"FROM {assign_submission} mas ".
"JOIN ( " . $submissionmaxattempt . " ) smx ON mas.userid = smx.userid ".
"AND mas.groupid = smx.groupid ".
@@ -788,6 +776,7 @@ class mod_assign_external extends external_api {
'userid' => $submissionrecord->userid,
'timecreated' => $submissionrecord->timecreated,
'timemodified' => $submissionrecord->timemodified,
'timestarted' => $submissionrecord->timestarted,
'status' => $submissionrecord->status,
'attemptnumber' => $submissionrecord->attemptnumber,
'groupid' => $submissionrecord->groupid,
@@ -872,6 +861,7 @@ class mod_assign_external extends external_api {
'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'),
'timestarted' => new external_value(PARAM_INT, 'submission start time', VALUE_OPTIONAL),
'status' => new external_value(PARAM_TEXT, 'submission status'),
'groupid' => new external_value(PARAM_INT, 'group id'),
'assignment' => new external_value(PARAM_INT, 'assignment id', VALUE_OPTIONAL),
@@ -2499,6 +2489,25 @@ class mod_assign_external extends external_api {
}
}
// Send back some assignment data as well.
$instance = $assign->get_instance();
$assignmentdata = [];
$attachments = [];
if ($assign->should_provide_intro_attachments($user->id)) {
$attachments['intro'] = external_util::get_area_files($context->id, 'mod_assign',
ASSIGN_INTROATTACHMENT_FILEAREA, 0);
}
if ($instance->activity && ($lastattempt || $assign->submissions_open($user->id, true))) {
list($assignmentdata['activity'], $assignmentdata['activityformat']) = external_format_text($instance->activity,
$instance->activityformat, $context->id, 'mod_assign', ASSIGN_ACTIVITYATTACHMENT_FILEAREA);
$attachments['activity'] = external_util::get_area_files($context->id, 'mod_assign',
ASSIGN_ACTIVITYATTACHMENT_FILEAREA, 0);
}
if (!empty($attachments)) {
$assignmentdata['attachments'] = $attachments;
}
$result['assignmentdata'] = $assignmentdata;
$result['warnings'] = $warnings;
return $result;
}
@@ -2544,6 +2553,7 @@ class mod_assign_external extends external_api {
'caneditowner' => new external_value(PARAM_BOOL, 'Whether the owner of the submission can edit it.'),
'cansubmit' => new external_value(PARAM_BOOL, 'Whether the user can submit.'),
'extensionduedate' => new external_value(PARAM_INT, 'Extension due date.'),
'timelimit' => new external_value(PARAM_INT, 'Time limit for submission.', VALUE_OPTIONAL),
'blindmarking' => new external_value(PARAM_BOOL, 'Whether blind marking is enabled.'),
'gradingstatus' => new external_value(PARAM_ALPHANUMEXT, 'Grading status.'),
'usergroups' => new external_multiple_structure(
@@ -2570,6 +2580,14 @@ class mod_assign_external extends external_api {
)
), 'List all the previous attempts did by the user.', VALUE_OPTIONAL
),
'assignmentdata' => new external_single_structure([
'attachments' => new external_single_structure([
'intro' => new external_files('Intro attachments files', VALUE_OPTIONAL),
'activity' => new external_files('Activity attachments files', VALUE_OPTIONAL),
], 'Intro and activity attachments', VALUE_OPTIONAL),
'activity' => new external_value(PARAM_RAW, 'Text of activity', VALUE_OPTIONAL),
'activityformat' => new external_format_value('activity', VALUE_OPTIONAL),
], 'Extra information about assignment', VALUE_OPTIONAL),
'warnings' => new external_warnings(),
)
);
@@ -2890,28 +2908,6 @@ class mod_assign_external extends external_api {
));
}
/**
* Utility function for validating an assign.
*
* @param int $assignid assign instance id
* @return array array containing the assign, course, context and course module objects
* @since Moodle 3.2
*/
protected static function validate_assign($assignid) {
global $DB;
// Request and permission validation.
$assign = $DB->get_record('assign', array('id' => $assignid), 'id', MUST_EXIST);
list($course, $cm) = get_course_and_cm_from_instance($assign, 'assign');
$context = context_module::instance($cm->id);
// Please, note that is not required to check mod/assign:view because is done by validate_context->require_login.
self::validate_context($context);
$assign = new assign($context, $cm, $course);
return array($assign, $course, $cm, $context);
}
/**
* Describes the parameters for view_assign.
*
+3
View File
@@ -410,6 +410,7 @@ $string['numberofsubmissionsneedgradinglabel'] = 'Needs grading: {$a}';
$string['numberofteams'] = 'Groups';
$string['offline'] = 'No online submissions required';
$string['open'] = 'Open';
$string['opensubmissionexists'] = 'Open assignment submission already exists.';
$string['outof'] = '{$a->current} out of {$a->total}';
$string['overdue'] = 'Assignment is overdue by: {$a}';
$string['override'] = 'Override';
@@ -535,6 +536,7 @@ $string['submissioneditable'] = 'Student can edit this submission';
$string['submissionlog'] = 'Student: {$a->fullname}, Status: {$a->status}';
$string['submissionnotcopiedinvalidstatus'] = 'The submission was not copied because it has been edited since it was reopened.';
$string['submissionnoteditable'] = 'Student cannot edit this submission';
$string['submissionnotopen'] = 'This assignment is not open for submissions';
$string['submissionnotready'] = 'This assignment is not ready to submit:';
$string['privacy:submissionpath'] = 'submission';
$string['submissionplugins'] = 'Submission plugins';
@@ -608,6 +610,7 @@ $string['teamsubmissiongroupingid_help'] = 'This is the grouping that the assign
$string['textinstructions'] = 'Assignment instructions';
$string['timelimit'] = 'Time limit';
$string['timelimit_help'] = 'If enabled, the time limit is stated on the assignment page and a countdown timer is displayed during the assignment.';
$string['timelimitnotenabled'] = 'Time limit is not enabled for assignment.';
$string['timelimitpassed'] = 'Time limit has been passed';
$string['timemodified'] = 'Last modified';
$string['timeremaining'] = 'Time remaining';
+62
View File
@@ -1908,6 +1908,33 @@ class assign {
return ($this->count_attachments() > 0);
}
/**
* Check if the intro attachments should be provided to the user.
*
* @param int $userid User id.
* @return bool
*/
public function should_provide_intro_attachments(int $userid): bool {
$instance = $this->get_instance($userid);
// Check if user has permission to view attachments regardless of assignment settings.
if (has_capability('moodle/course:manageactivities', $this->get_context())) {
return true;
}
// If assignment does not show intro, we never provide intro attachments.
if (!$this->show_intro()) {
return false;
}
// If intro attachments should only be shown when submission is started, check if there is an open submission.
if (!empty($instance->submissionattachments) && !$this->submissions_open($userid, true)) {
return false;
}
return true;
}
/**
* Return a grade in user-friendly form, whether it's a scale or not.
*
@@ -9686,6 +9713,41 @@ class assign {
return $submissionstatement;
}
/**
* Check if time limit for assignment enabled and set up.
*
* @param int|null $userid User ID. If null, use global user.
* @return bool
*/
public function is_time_limit_enabled(?int $userid = null): bool {
$instance = $this->get_instance($userid);
return get_config('assign', 'enabletimelimit') && !empty($instance->timelimit);
}
/**
* Check if an assignment submission is already started and not yet submitted.
*
* @param int|null $userid User ID. If null, use global user.
* @param int $groupid Group ID. If 0, use user id to determine group.
* @param int $attemptnumber Attempt number. If -1, check latest submission.
* @return bool
*/
public function is_attempt_in_progress(?int $userid = null, int $groupid = 0, int $attemptnumber = -1): bool {
if ($this->get_instance($userid)->teamsubmission) {
$submission = $this->get_group_submission($userid, $groupid, false, $attemptnumber);
} else {
$submission = $this->get_user_submission($userid, false, $attemptnumber);
}
// If time limit is enabled, we only assume it is in progress if there is a start time for submission.
$timedattemptstarted = true;
if ($this->is_time_limit_enabled($userid)) {
$timedattemptstarted = !empty($submission) && !empty($submission->timestarted);
}
return !empty($submission) && $submission->status !== ASSIGN_SUBMISSION_STATUS_SUBMITTED && $timedattemptstarted;
}
}
/**
+178
View File
@@ -0,0 +1,178 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_assign\external;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/assign/tests/externallib_advanced_testcase.php');
/**
* Test the start_submission external function.
*
* @package mod_assign
* @category test
* @covers \mod_assign\external\start_submission
* @author Andrew Madden <[email protected]>
* @copyright 2021 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class start_submission_test extends \mod_assign\externallib_advanced_testcase {
/** @var \stdClass $course New course created to hold the assignments */
protected $course = null;
/**
* Called before every test.
*/
protected function setUp(): void {
parent::setUp();
$this->resetAfterTest();
$this->course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
}
/**
* Test start_submission if assignment doesn't exist matching id.
*/
public function test_start_submission_with_invalid_assign_id() {
$this->expectException(\dml_exception::class);
start_submission::execute(123);
}
/**
* Test start_submission if user is not able to access activity or course.
*/
public function test_start_submission_when_user_has_no_capability_to_view_assignment() {
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$assign = $generator->create_instance(['course' => $this->course->id]);
$this->expectException(\require_login_exception::class);
start_submission::execute($assign->id);
}
/**
* Test start_submission if assignment cut off date has elapsed.
*/
public function test_start_submission_when_assignment_past_due_date() {
$fiveminago = time() - 300;
list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(
false, ['cutoffdate' => $fiveminago]);
$result = start_submission::execute($instance->id);
$filteredwarnings = array_filter($result['warnings'], function($warning) {
return $warning['warningcode'] === 'submissionnotopen';
});
$this->assertCount(1, $filteredwarnings);
$this->assertEquals(0, $result['submissionid']);
$warning = array_pop($filteredwarnings);
$this->assertEquals($instance->id, $warning['itemid']);
$this->assertEquals('This assignment is not open for submissions', $warning['item']);
}
/**
* Test start_submission if time limit is disabled.
*/
public function test_start_submission_when_time_limit_disabled() {
list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status();
$result = start_submission::execute($instance->id);
$filteredwarnings = array_filter($result['warnings'], function($warning) {
return $warning['warningcode'] === 'timelimitnotenabled';
});
$this->assertCount(1, $filteredwarnings);
$this->assertEquals(0, $result['submissionid']);
$warning = array_pop($filteredwarnings);
$this->assertEquals($instance->id, $warning['itemid']);
$this->assertEquals('Time limit is not enabled for assignment.', $warning['item']);
}
/**
* Test start_submission if time limit is not set for assignment.
*/
public function test_start_submission_when_time_limit_not_set() {
set_config('enabletimelimit', '1', 'assign');
list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status();
$result = start_submission::execute($instance->id);
$filteredwarnings = array_filter($result['warnings'], function($warning) {
return $warning['warningcode'] === 'timelimitnotenabled';
});
$this->assertCount(1, $filteredwarnings);
$this->assertEquals(0, $result['submissionid']);
$warning = array_pop($filteredwarnings);
$this->assertEquals($instance->id, $warning['itemid']);
$this->assertEquals('Time limit is not enabled for assignment.', $warning['item']);
}
/**
* Test start_submission if user already has open submission.
*/
public function test_start_submission_when_submission_already_open() {
global $DB;
set_config('enabletimelimit', '1', 'assign');
list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(
false, ['timelimit' => 300]);
$submission = $assign->get_user_submission($student1->id, true);
$submission->timestarted = time();
$DB->update_record('assign_submission', $submission);
$result = start_submission::execute($instance->id);
$filteredwarnings = array_filter($result['warnings'], function($warning) {
return $warning['warningcode'] === 'opensubmissionexists';
});
$this->assertCount(1, $filteredwarnings);
$this->assertEquals(0, $result['submissionid']);
$warning = array_pop($filteredwarnings);
$this->assertEquals($instance->id, $warning['itemid']);
$this->assertEquals('Open assignment submission already exists.', $warning['item']);
}
/**
* Test start_submission if user has already submitted with no additional attempts available.
*/
public function test_start_submission_with_no_attempts_available() {
global $DB;
set_config('enabletimelimit', '1', 'assign');
list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(
false, ['timelimit' => 300]);
$submission = $assign->get_user_submission($student1->id, true);
$submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
$DB->update_record('assign_submission', $submission);
$result = start_submission::execute($instance->id);
$filteredwarnings = array_filter($result['warnings'], function($warning) {
return $warning['warningcode'] === 'submissionnotopen';
});
$this->assertCount(1, $filteredwarnings);
$this->assertEquals(0, $result['submissionid']);
$warning = array_pop($filteredwarnings);
$this->assertEquals($instance->id, $warning['itemid']);
$this->assertEquals('This assignment is not open for submissions', $warning['item']);
}
/**
* Test start_submission if user has no open submissions.
*/
public function test_start_submission_with_new_submission() {
global $DB;
set_config('enabletimelimit', '1', 'assign');
list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(
false, ['timelimit' => 300]);
// Clear all current submissions.
$DB->delete_records('assign_submission', ['assignment' => $instance->id]);
$result = start_submission::execute($instance->id);
$this->assertCount(0, $result['warnings']);
$this->assertNotEmpty($result['submissionid']);
}
}
@@ -0,0 +1,172 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_assign;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
require_once($CFG->dirroot . '/mod/assign/externallib.php');
require_once(__DIR__ . '/fixtures/testable_assign.php');
/**
* Base class for unit tests for external functions in mod_assign.
*
* @package mod_assign
* @author Andrew Madden <[email protected]>
* @copyright 2021 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class externallib_advanced_testcase extends \externallib_advanced_testcase {
/**
* Create a submission for testing the get_submission_status function.
* @param bool $submitforgrading whether to submit for grading the submission
* @param array $params Optional params to use for creating assignment instance.
* @return array an array containing all the required data for testing
*/
protected function create_submission_for_testing_status(bool $submitforgrading = false, array $params = []): array {
global $DB;
// Create a course and assignment and users.
$course = self::getDataGenerator()->create_course(['groupmode' => SEPARATEGROUPS, 'groupmodeforce' => 1]);
$group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
$group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$params = array_merge([
'course' => $course->id,
'assignsubmission_file_maxfiles' => 1,
'assignsubmission_file_maxsizebytes' => 1024 * 1024,
'assignsubmission_onlinetext_enabled' => 1,
'assignsubmission_file_enabled' => 1,
'submissiondrafts' => 1,
'assignfeedback_file_enabled' => 1,
'assignfeedback_comments_enabled' => 1,
'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
'sendnotifications' => 0
], $params);
set_config('submissionreceipts', 0, 'assign');
$instance = $generator->create_instance($params);
$cm = get_coursemodule_from_instance('assign', $instance->id);
$context = \context_module::instance($cm->id);
$assign = new \mod_assign_testable_assign($context, $cm, $course);
$student1 = self::getDataGenerator()->create_user();
$student2 = self::getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', ['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', ['shortname' => 'teacher']);
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
$this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $student1->id]);
$this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $teacher->id]);
$this->getDataGenerator()->create_group_member(['groupid' => $group2->id, 'userid' => $student2->id]);
$this->getDataGenerator()->create_group_member(['groupid' => $group2->id, 'userid' => $teacher->id]);
$this->setUser($student1);
// Create a student1 with an online text submission.
// Simulate a submission.
$assign->get_user_submission($student1->id, true);
$data = new \stdClass();
$data->onlinetext_editor = [
'itemid' => file_get_unused_draft_itemid(),
'text' => 'Submission text with a <a href="@@PLUGINFILE@@/intro.txt">link</a>',
'format' => FORMAT_MOODLE,
];
$draftidfile = file_get_unused_draft_itemid();
$usercontext = \context_user::instance($student1->id);
$filerecord = [
'contextid' => $usercontext->id,
'component' => 'user',
'filearea' => 'draft',
'itemid' => $draftidfile,
'filepath' => '/',
'filename' => 't.txt',
];
$fs = get_file_storage();
$fs->create_file_from_string($filerecord, 'text contents');
$data->files_filemanager = $draftidfile;
$notices = [];
$assign->save_submission($data, $notices);
if ($submitforgrading) {
// Now, submit the draft for grading.
$notices = [];
$data = new \stdClass;
$data->userid = $student1->id;
$assign->submit_for_grading($data, $notices);
}
return [$assign, $instance, $student1, $student2, $teacher, $group1, $group2];
}
/**
* Create a course, assignment module instance, student and teacher and enrol them in
* the course.
*
* @param array $params parameters to be provided to the assignment module creation
* @return array containing the course, assignment module, student and teacher
*/
protected function create_assign_with_student_and_teacher(array $params = []): array {
global $DB;
$course = $this->getDataGenerator()->create_course();
$params = array_merge([
'course' => $course->id,
'name' => 'assignment',
'intro' => 'assignment intro text',
], $params);
// Create a course and assignment and users.
$assign = $this->getDataGenerator()->create_module('assign', $params);
$cm = get_coursemodule_from_instance('assign', $assign->id);
$context = \context_module::instance($cm->id);
$student = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
$teacher = $this->getDataGenerator()->create_user();
$teacherrole = $DB->get_record('role', ['shortname' => 'teacher']);
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
assign_capability('mod/assign:view', CAP_ALLOW, $teacherrole->id, $context->id, true);
assign_capability('mod/assign:viewgrades', CAP_ALLOW, $teacherrole->id, $context->id, true);
assign_capability('mod/assign:grade', CAP_ALLOW, $teacherrole->id, $context->id, true);
accesslib_clear_all_caches_for_unit_testing();
return [
'course' => $course,
'assign' => $assign,
'student' => $student,
'teacher' => $teacher,
];
}
}
+185 -140
View File
@@ -17,7 +17,6 @@
namespace mod_assign;
use core_user_external;
use externallib_advanced_testcase;
use mod_assign_external;
use mod_assign_testable_assign;
@@ -27,6 +26,7 @@ global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
require_once($CFG->dirroot . '/mod/assign/externallib.php');
require_once($CFG->dirroot . '/mod/assign/tests/externallib_advanced_testcase.php');
require_once(__DIR__ . '/fixtures/testable_assign.php');
/**
@@ -37,7 +37,7 @@ require_once(__DIR__ . '/fixtures/testable_assign.php');
* @copyright 2012 Paul Charsley
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class externallib_test extends externallib_advanced_testcase {
class externallib_test extends \mod_assign\externallib_advanced_testcase {
/**
* Test get_grades
@@ -184,7 +184,11 @@ class externallib_test extends externallib_advanced_testcase {
'intro' => 'the assignment intro text here <a href="@@PLUGINFILE@@/intro.txt">link</a>',
'introformat' => FORMAT_HTML,
'markingworkflow' => 1,
'markingallocation' => 1
'markingallocation' => 1,
'activityeditor' => [
'text' => 'Test activity',
'format' => 1,
],
));
// Add a file as assignment attachment.
@@ -244,6 +248,11 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertEquals(1, $assignment['markingworkflow']);
$this->assertEquals(1, $assignment['markingallocation']);
$this->assertEquals(0, $assignment['preventsubmissionnotingroup']);
$this->assertEquals(0, $assignment['timelimit']);
$this->assertEquals(0, $assignment['submissionattachments']);
$this->assertEquals('Test activity', $assignment['activity']);
$this->assertEquals(1, $assignment['activityformat']);
$this->assertEmpty($assignment['activityattachments']);
$this->assertCount(1, $assignment['introattachments']);
$this->assertEquals('introattachment.txt', $assignment['introattachments'][0]['filename']);
@@ -366,6 +375,80 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertArrayNotHasKey('submissionstatement', $assignmentret);
}
/**
* Test that get_assignments does not return intro attachments if submissionattachments enabled and there is no open submission.
*
* @covers \mod_assign_external::get_assignments
*/
public function test_get_assignments_when_submissionattachments_is_enabled() {
global $DB;
$this->resetAfterTest(true);
// Create data.
$course1 = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
// First set submissionattachments to empty.
$assign = self::getDataGenerator()->create_module('assign', array(
'course' => $course1->id,
'name' => 'Test assignment',
'intro' => 'The assignment intro text here',
'introformat' => FORMAT_HTML,
'submissionattachments' => 0,
));
$context = \context_module::instance($assign->cmid);
// Enrol user as student.
$this->getDataGenerator()->enrol_user($user->id, $course1->id, 'student');
// Add a file as assignment attachment.
$filerecord = array('component' => 'mod_assign', 'filearea' => ASSIGN_INTROATTACHMENT_FILEAREA,
'contextid' => $context->id, 'itemid' => 0,
'filename' => 'introattachment.txt', 'filepath' => '/');
$fs = get_file_storage();
$fs->create_file_from_string($filerecord, 'Test intro attachment file');
// We need to execute the return values cleaning process to simulate the web service server.
$result = mod_assign_external::get_assignments();
$result = \external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
$this->assertEquals(1, count($result['courses']));
$course = $result['courses'][0];
$this->assertEquals(1, count($course['assignments']));
$assignment = $course['assignments'][0];
$this->assertCount(1, $assignment['introattachments']);
$this->assertEquals('introattachment.txt', $assignment['introattachments'][0]['filename']);
// Now set submissionattachments to enabled. We will close assignment so assume intro attachments are not sent.
$DB->set_field('assign', 'submissionattachments', '1', ['id' => $assignment['id']]);
$DB->set_field('assign', 'cutoffdate', time() - 300, ['id' => $assignment['id']]);
// We need to execute the return values cleaning process to simulate the web service server.
$result = mod_assign_external::get_assignments();
$result = \external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
$this->assertEquals(1, count($result['courses']));
$course = $result['courses'][0];
$this->assertEquals(1, count($course['assignments']));
$assignment = $course['assignments'][0];
$this->assertArrayNotHasKey('introattachments', $assignment);
// Enrol a user as a teacher to override the setting.
$this->getDataGenerator()->enrol_user($user->id, $course1->id, 'editingteacher');
// We need to execute the return values cleaning process to simulate the web service server.
$result = mod_assign_external::get_assignments();
$result = \external_api::clean_returnvalue(mod_assign_external::get_assignments_returns(), $result);
$this->assertEquals(1, count($result['courses']));
$course = $result['courses'][0];
$this->assertEquals(1, count($course['assignments']));
$assignment = $course['assignments'][0];
$this->assertCount(1, $assignment['introattachments']);
$this->assertEquals('introattachment.txt', $assignment['introattachments'][0]['filename']);
}
/**
* Test get_submissions
*/
@@ -394,17 +477,20 @@ class externallib_test extends externallib_advanced_testcase {
$submission->userid = $student->id;
$submission->timecreated = time();
$submission->timemodified = $submission->timecreated;
$submission->timestarted = $submission->timecreated;;
$submission->status = 'draft';
$submission->attemptnumber = 0;
$submission->latest = 0;
$sid = $DB->insert_record('assign_submission', $submission);
// Second attempt.
$now = time();
$submission = new \stdClass();
$submission->assignment = $assign1->id;
$submission->userid = $student->id;
$submission->timecreated = time();
$submission->timemodified = $submission->timecreated;
$submission->timecreated = $now;
$submission->timemodified = $now;
$submission->timestarted = $now;
$submission->status = 'submitted';
$submission->attemptnumber = 1;
$submission->latest = 1;
@@ -449,6 +535,7 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertEquals($sid, $submission['id']);
$this->assertCount(1, $submission['plugins']);
$this->assertEquals('notgraded', $submission['gradingstatus']);
$this->assertEquals($now, $submission['timestarted']);
// Test locking the context.
set_config('contextlocking', 1);
@@ -1907,98 +1994,6 @@ class externallib_test extends externallib_advanced_testcase {
}
}
/**
* Create a submission for testing the get_submission_status function.
* @param boolean $submitforgrading whether to submit for grading the submission
* @return array an array containing all the required data for testing
*/
private function create_submission_for_testing_status($submitforgrading = false) {
global $DB;
// Create a course and assignment and users.
$course = self::getDataGenerator()->create_course(array('groupmode' => SEPARATEGROUPS, 'groupmodeforce' => 1));
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$params = array(
'course' => $course->id,
'assignsubmission_file_maxfiles' => 1,
'assignsubmission_file_maxsizebytes' => 1024 * 1024,
'assignsubmission_onlinetext_enabled' => 1,
'assignsubmission_file_enabled' => 1,
'submissiondrafts' => 1,
'assignfeedback_file_enabled' => 1,
'assignfeedback_comments_enabled' => 1,
'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
'sendnotifications' => 0
);
set_config('submissionreceipts', 0, 'assign');
$instance = $generator->create_instance($params);
$cm = get_coursemodule_from_instance('assign', $instance->id);
$context = \context_module::instance($cm->id);
$assign = new mod_assign_testable_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);
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $student1->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $teacher->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $student2->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $teacher->id));
$this->setUser($student1);
// Create a student1 with an online text submission.
// Simulate a submission.
$submission = $assign->get_user_submission($student1->id, true);
$data = new \stdClass();
$data->onlinetext_editor = array(
'itemid' => file_get_unused_draft_itemid(),
'text' => 'Submission text with a <a href="@@PLUGINFILE@@/intro.txt">link</a>',
'format' => FORMAT_MOODLE);
$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' => 't.txt',
);
$fs = get_file_storage();
$fs->create_file_from_string($filerecord, 'text contents');
$data->files_filemanager = $draftidfile;
$notices = array();
$assign->save_submission($data, $notices);
if ($submitforgrading) {
// Now, submit the draft for grading.
$notices = array();
$data = new \stdClass;
$data->userid = $student1->id;
$assign->submit_for_grading($data, $notices);
}
return array($assign, $instance, $student1, $student2, $teacher, $group1, $group2);
}
/**
* Test get_submission_status for a draft submission.
*/
@@ -2053,6 +2048,9 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertEquals($expectedformat, $submissionplugins['onlinetext']['editorfields'][0]['format']);
$this->assertEquals('/', $submissionplugins['file']['fileareas'][0]['files'][0]['filepath']);
$this->assertEquals('t.txt', $submissionplugins['file']['fileareas'][0]['files'][0]['filename']);
// Test assignment data.
$this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);
}
/**
@@ -2082,7 +2080,10 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertFalse($result['lastattempt']['blindmarking']);
$this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
$this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
$this->assertNull($result['lastattempt']['submission']['timestarted']);
// Test assignment data.
$this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);
}
/**
@@ -2153,6 +2154,9 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertEquals(1, $result['gradingsummary']['submissiondraftscount']); // We have a draft submission.
$this->assertEquals(0, $result['gradingsummary']['submissionssubmittedcount']); // We have only draft submissions.
$this->assertEquals(0, $result['gradingsummary']['submissionsneedgradingcount']); // We have only draft submissions.
// Test assignment data.
$this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);
}
/**
@@ -2247,6 +2251,8 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertEquals('/', $submissionplugins['file']['fileareas'][0]['files'][0]['filepath']);
$this->assertEquals('t.txt', $submissionplugins['file']['fileareas'][0]['files'][0]['filename']);
// Test assignment data.
$this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);
}
/**
@@ -2321,6 +2327,9 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertTrue(isset($result['feedback']));
$this->assertTrue(isset($result['feedback']['grade']));
$this->assertEquals($teacher->id, $result['feedback']['grade']['grader']);
// Test assignment data.
$this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);
}
/**
@@ -2380,6 +2389,85 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertFalse($result['lastattempt']['blindmarking']);
$this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
$this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
// Test assignment data.
$this->assertEquals(['attachments' => ['intro' => []]], $result['assignmentdata']);
}
/**
* Test get_submission_status with time limit for student.
*
* @covers \mod_assign_external::get_submission_status
*/
public function test_get_submission_status_with_time_limit_enabled() {
$this->resetAfterTest();
set_config('enabletimelimit', '1', 'assign');
// Add time limit of 5 minutes to assignment. To edit activity, activity editor must not be empty.
list($assign, $instance, $student1, $student2, $teacher, $g1, $g2) = $this->create_submission_for_testing_status(
true, [
'timelimit' => 300,
'activityeditor' => [
'text' => 'Test activity',
'format' => 1,
],
]
);
// Add an intro attachment.
$fs = get_file_storage();
$context = \context_module::instance($instance->cmid);
$filerecord = array(
'contextid' => $context->id,
'component' => 'mod_assign',
'filearea' => ASSIGN_INTROATTACHMENT_FILEAREA,
'filename' => 'Test intro file',
'itemid' => 0,
'filepath' => '/'
);
$fs->create_file_from_string($filerecord, 'Test assign file');
// Set optional param to indicate start time required.
$_GET['action'] = 'editsubmission';
$cm = get_coursemodule_from_instance('assign', $instance->id);
$context = \context_module::instance($cm->id);
(new \assign($context, $cm, $cm->course))->get_user_submission(0, true);
$result = mod_assign_external::get_submission_status($assign->get_instance()->id);
// We expect debugging because of the $PAGE object, this won't happen in a normal WS request.
$this->assertDebuggingCalled();
$result = \external_api::clean_returnvalue(mod_assign_external::get_submission_status_returns(), $result);
$this->assertCount(0, $result['warnings']);
$this->assertFalse(isset($result['gradingsummary']));
$this->assertFalse(isset($result['feedback']));
$this->assertFalse(isset($result['previousattempts']));
$this->assertTrue($result['lastattempt']['submissionsenabled']);
$this->assertFalse($result['lastattempt']['canedit']);
$this->assertFalse($result['lastattempt']['cansubmit']);
$this->assertFalse($result['lastattempt']['locked']);
$this->assertFalse($result['lastattempt']['graded']);
$this->assertEmpty($result['lastattempt']['extensionduedate']);
$this->assertFalse($result['lastattempt']['blindmarking']);
$this->assertCount(0, $result['lastattempt']['submissiongroupmemberswhoneedtosubmit']);
$this->assertEquals('notgraded', $result['lastattempt']['gradingstatus']);
$this->assertEquals(300, $result['lastattempt']['timelimit']);
$this->assertNotNull($result['lastattempt']['submission']['timestarted']);
$this->assertLessThanOrEqual(time(), $result['lastattempt']['submission']['timestarted']);
// Test assignment data.
$this->assertNotEmpty($result['assignmentdata']);
$this->assertEquals('Test activity', $result['assignmentdata']['activity']);
$this->assertEquals(1, $result['assignmentdata']['activityformat']);
$this->assertCount(2, $result['assignmentdata']['attachments']);
$introattachments = $result['assignmentdata']['attachments']['intro'];
$activityattachments = $result['assignmentdata']['attachments']['activity'];
$this->assertCount(1, $introattachments);
$intro = reset($introattachments);
$this->assertEquals('Test intro file', $intro['filename']);
$this->assertEmpty($activityattachments);
}
/**
@@ -2766,49 +2854,6 @@ class externallib_test extends externallib_advanced_testcase {
}
}
/**
* Create a a course, assignment module instance, student and teacher and enrol them in
* the course.
*
* @param array $params parameters to be provided to the assignment module creation
* @return array containing the course, assignment module, student and teacher
*/
private function create_assign_with_student_and_teacher($params = array()) {
global $DB;
$course = $this->getDataGenerator()->create_course();
$params = array_merge(array(
'course' => $course->id,
'name' => 'assignment',
'intro' => 'assignment intro text',
), $params);
// Create a course and assignment and users.
$assign = $this->getDataGenerator()->create_module('assign', $params);
$cm = get_coursemodule_from_instance('assign', $assign->id);
$context = \context_module::instance($cm->id);
$student = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
$teacher = $this->getDataGenerator()->create_user();
$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
assign_capability('mod/assign:view', CAP_ALLOW, $teacherrole->id, $context->id, true);
assign_capability('mod/assign:viewgrades', CAP_ALLOW, $teacherrole->id, $context->id, true);
assign_capability('mod/assign:grade', CAP_ALLOW, $teacherrole->id, $context->id, true);
accesslib_clear_all_caches_for_unit_testing();
return array(
'course' => $course,
'assign' => $assign,
'student' => $student,
'teacher' => $teacher
);
}
/**
* Test test_view_assign
*/
+3
View File
@@ -56,6 +56,9 @@ class mod_assign_generator extends testing_module_generator {
'maxattempts' => -1,
'markingworkflow' => 0,
'markingallocation' => 0,
'activityformat' => 0,
'timelimit' => 0,
'submissionattachments' => 0,
);
if (property_exists($record, 'teamsubmissiongroupingid')) {
+197
View File
@@ -4331,4 +4331,201 @@ Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
$assign->get_course_module()->id . '&amp;action=grading">' .
get_string('numberofsubmissionsneedgradinglabel', 'assign', 1) . '</a>', $summary);
}
/**
* Test that attachments should not be provided if \assign->show_intro returns false.
*
* @covers \assign::should_provide_intro_attachments
*/
public function test_should_provide_intro_attachments_with_show_intro_disabled() {
$this->resetAfterTest();
$futuredate = time() + 300;
list($assign, $instance, $student) = $this->create_submission([
'alwaysshowdescription' => '0',
'allowsubmissionsfromdate' => $futuredate,
]);
$this->assertFalse($assign->should_provide_intro_attachments($student->id));
}
/**
* Test that attachments should be provided if user has capability to manage activity.
*
* @covers \assign::should_provide_intro_attachments
*/
public function test_should_provide_intro_attachments_with_bypass_capability() {
$this->resetAfterTest();
list($assign, $instance, $student) = $this->create_submission([
'submissionattachments' => 1,
]);
// Provide teaching role to student1 so they are able to bypass time limit restrictions on viewing attachments.
$this->getDataGenerator()->enrol_user($student->id, $instance->course, 'editingteacher');
$this->assertTrue($assign->should_provide_intro_attachments($student->id));
}
/**
* Test that attachments should be provided if submissionattachments is disabled.
*
* @covers \assign::should_provide_intro_attachments
*/
public function test_should_provide_intro_attachments_with_submissionattachments_disabled() {
$this->resetAfterTest();
list($assign, $instance, $student) = $this->create_submission();
$this->assertTrue($assign->should_provide_intro_attachments($student->id));
}
/**
* Test that attachments should not be provided if submissionattachments is enabled with no open submission.
*
* @covers \assign::should_provide_intro_attachments
*/
public function test_should_provide_intro_attachments_with_submissionattachments_enabled_and_submissions_closed() {
$this->resetAfterTest();
// Set cut-off date to the past.
list($assign, $instance, $student) = $this->create_submission([
'timelimit' => '300',
'submissionattachments' => 1,
'cutoffdate' => time() - 300,
]);
$this->assertFalse($assign->should_provide_intro_attachments($student->id));
}
/**
* Test that attachments should be provided if submissionattachments is enabled with an open submission.
*
* @covers \assign::should_provide_intro_attachments
*/
public function test_should_provide_intro_attachments_submissionattachments_enabled_and_an_open_submission() {
$this->resetAfterTest();
set_config('enabletimelimit', '1', 'assign');
list($assign, $instance, $student) = $this->create_submission([
'timelimit' => '300',
'submissionattachments' => 1,
]);
// Open a submission.
$assign->get_user_submission($student->id, true);
$this->assertTrue($assign->should_provide_intro_attachments($student->id));
}
/**
* Test that a submission using a time limit is currently open.
*
* @covers \assign::is_attempt_in_progress
*/
public function test_is_attempt_in_progress_with_open_submission() {
global $DB;
$this->resetAfterTest();
set_config('enabletimelimit', '1', 'assign');
list($assign, $instance, $student) = $this->create_submission([
'timelimit' => '300',
]);
$submission = $assign->get_user_submission($student->id, true);
// Set a timestarted.
$submission->timestarted = time() - 300;
$DB->update_record('assign_submission', $submission);
$this->assertTrue($assign->is_attempt_in_progress());
}
/**
* Test that a submission using a time limit is started without a start time.
*
* @covers \assign::is_attempt_in_progress
*/
public function test_is_attempt_in_progress_with_open_submission_and_no_timestarted() {
$this->resetAfterTest();
set_config('enabletimelimit', '1', 'assign');
list($assign, $instance, $student) = $this->create_submission([
'timelimit' => '300',
]);
$assign->get_user_submission($student->id, true);
$this->assertFalse($assign->is_attempt_in_progress());
}
/**
* Test that a submission using a time limit is currently not open.
*
* @covers \assign::is_attempt_in_progress
*/
public function test_is_attempt_in_progress_with_no_open_submission() {
global $DB;
$this->resetAfterTest();
set_config('enabletimelimit', '1', 'assign');
list($assign, $instance, $student) = $this->create_submission([
'timelimit' => '300',
]);
// Clear all current submissions.
$DB->delete_records('assign_submission', ['assignment' => $instance->id]);
$this->assertFalse($assign->is_attempt_in_progress());
}
/**
* Create a submission for testing.
* @param array $params Optional params to use for creating assignment instance.
* @return array an array containing all the required data for testing
*/
protected function create_submission(array $params = []) {
global $DB;
// Create a course and assignment and users.
$course = self::getDataGenerator()->create_course(array('groupmode' => SEPARATEGROUPS, 'groupmodeforce' => 1));
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$params = array_merge(array(
'course' => $course->id,
'assignsubmission_file_maxfiles' => 1,
'assignsubmission_file_maxsizebytes' => 1024 * 1024,
'assignsubmission_onlinetext_enabled' => 1,
'assignsubmission_file_enabled' => 1,
'submissiondrafts' => 1,
'assignfeedback_file_enabled' => 1,
'assignfeedback_comments_enabled' => 1,
'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
'sendnotifications' => 0
), $params);
set_config('submissionreceipts', 0, 'assign');
$instance = $generator->create_instance($params);
$cm = get_coursemodule_from_instance('assign', $instance->id);
$context = \context_module::instance($cm->id);
$assign = new \mod_assign_testable_assign($context, $cm, $course);
$student = self::getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
$this->setUser($student);
// Create a student1 with an online text submission.
// Simulate a submission.
$submission = $assign->get_user_submission($student->id, true);
$data = new \stdClass();
$data->onlinetext_editor = array(
'itemid' => file_get_unused_draft_itemid(),
'text' => 'Submission text with a <a href="@@PLUGINFILE@@/intro.txt">link</a>',
'format' => FORMAT_MOODLE);
$draftidfile = file_get_unused_draft_itemid();
$usercontext = \context_user::instance($student->id);
$filerecord = array(
'contextid' => $usercontext->id,
'component' => 'user',
'filearea' => 'draft',
'itemid' => $draftidfile,
'filepath' => '/',
'filename' => 't.txt',
);
$fs = get_file_storage();
$fs->create_file_from_string($filerecord, 'text contents');
$data->files_filemanager = $draftidfile;
$notices = array();
$assign->save_submission($data, $notices);
return array($assign, $instance, $student);
}
}
+1 -1
View File
@@ -25,5 +25,5 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'mod_assign'; // Full name of the plugin (used for diagnostics).
$plugin->version = 2021110901; // The current module version (Date: YYYYMMDDXX).
$plugin->version = 2021110902; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2021052500; // Requires this Moodle version.