Skip to content

Commit

Permalink
MDL-74050 mod_assign: New WS remove_submission
Browse files Browse the repository at this point in the history
  • Loading branch information
durenadev committed Sep 24, 2024
1 parent f6141a6 commit b477046
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 2 deletions.
2 changes: 2 additions & 0 deletions mod/assign/classes/external/external_api.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ protected static function generate_warning(int $assignmentid, string $warningcod
'submissionnotopen' => 'This assignment is not open for submissions',
'timelimitnotenabled' => 'Time limit is not enabled for assignment.',
'opensubmissionexists' => 'Open assignment submission already exists.',
'couldnotremovesubmission' => 'Could not remove the submission for this user',
'submissionnotfoundtoremove' => 'There is no submission to remove',
];

$message = $warningmessages[$warningcode];
Expand Down
107 changes: 107 additions & 0 deletions mod/assign/classes/external/remove_submission.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?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;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;

/**
* External function to remove an assignment submission.
*
* @package mod_assign
* @category external
*
* @copyright 2024 Daniel Ureña <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.5
*/
class remove_submission extends external_api {
/**
* Describes the parameters for remove submission.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'userid' => new external_value(PARAM_INT, 'User id'),
'assignid' => new external_value(PARAM_INT, 'Assignment instance id'),
]);
}

/**
* Call to remove submission.
*
* @param int $userid User id to remove submission
* @param int $assignid The id of the assignment
* @return array
*/
public static function execute(int $userid, int $assignid): array {
// Initialize return variables.
$warnings = [];
$result = [];
$status = false;

[
'userid' => $userid,
'assignid' => $assignid
] = self::validate_parameters(self::execute_parameters(), [
'userid' => $userid,
'assignid' => $assignid,
]);

// Validate and get the assign.
[$assign, $course, $cm, $context] = self::validate_assign($assignid);

// Get submission.
$submission = $assign->get_user_submission($userid, false);
if (
!$submission ||
$submission->status == ASSIGN_SUBMISSION_STATUS_NEW ||
$submission->status == ASSIGN_SUBMISSION_STATUS_REOPENED
) {
// No submission to remove.
$warnings[] = self::generate_warning($assignid, 'submissionnotfoundtoremove', 'assign');
return [
'status' => $status,
'warnings' => $warnings,
];
}

if (!$status = $assign->remove_submission($userid)) {
$errors = $assign->get_error_messages();
foreach ($errors as $errormsg) {
$warnings[] = self::generate_warning($assignid, 'couldnotremovesubmission', $errormsg);
}
}
$result['status'] = $status;
$result['warnings'] = $warnings;
return $result;
}

/**
* Describes the remove submissions return value.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'status' => new external_value(PARAM_BOOL, 'True if the submission was successfully removed and false if was not.'),
'warnings' => new external_warnings(),
]);
}
}
7 changes: 7 additions & 0 deletions mod/assign/db/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,11 @@
'capabilities' => 'mod/assign:view',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
],
'mod_assign_remove_submission' => [
'classname' => 'mod_assign\external\remove_submission',
'description' => 'Remove submission.',
'type' => 'write',
'capabilities' => 'mod/assign:submit',
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
],
);
2 changes: 1 addition & 1 deletion mod/assign/locallib.php
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ protected function set_error_message(string $message) {
*
* @return array The array of error messages
*/
protected function get_error_messages(): array {
public function get_error_messages(): array {
return $this->errors;
}

Expand Down
226 changes: 226 additions & 0 deletions mod/assign/tests/external/remove_submission_test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
<?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;

use mod_assign_test_generator;
use mod_assign_external;

defined('MOODLE_INTERNAL') || die();

global $CFG;

require_once("$CFG->dirroot/mod/assign/tests/generator.php");
require_once("$CFG->dirroot/mod/assign/tests/fixtures/event_mod_assign_fixtures.php");
require_once("$CFG->dirroot/mod/assign/tests/externallib_advanced_testcase.php");

/**
* Test the remove submission external function.
*
* @package mod_assign
* @category test
*
* @copyright 2024 Daniel Ureña <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \mod_assign\external\remove_submission
*/
final class remove_submission_test extends \mod_assign\externallib_advanced_testcase {
// Use the generator helper.
use mod_assign_test_generator;

/**
* Prepare course with users, teacher and assign.
*
* @return array Containing course, student1, student2, teacher assign and instance data
*/
protected function prepare_course(): array {

$course = $this->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 \mod_assign_testable_assign($context, $cm, $course);

$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
$student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');

return [$course, $student1, $student2, $teacher, $assign, $instance];
}

/**
* Test remove submission by WS with invalid assign id.
* @covers ::execute
*/
public function test_remove_submission_with_invalid_assign_id(): void {
$this->resetAfterTest();
[$course, $student1, $student2, $teacher, $assign, $instance] = $this->prepare_course();
$this->expectException(\moodle_exception::class);
remove_submission::execute($student1->id, 123);
}

/**
* Test remove submission by WS with invalid user id.
* @covers ::execute
*/
public function test_remove_submission_with_invalid_user_id(): void {
$this->resetAfterTest();
[$course, $student1, $student2, $teacher, $assign, $instance] = $this->prepare_course();
$this->setUser($student1);
$result = remove_submission::execute(123, $assign->get_instance()->id);
$this->assertFalse($result['status']);
$this->assertEquals('submissionnotfoundtoremove', $result['warnings'][0]['warningcode']);
}

/**
* Test teacher can't remove student submissions by WS.
* @covers ::execute
*/
public function test_teacher_remove_submissions(): void {
$this->resetAfterTest();
[$course, $student1, $student2, $teacher, $assign, $instance] = $this->prepare_course();
$this->add_submission($student1, $assign);
$this->add_submission($student2, $assign);
$this->setUser($teacher);

$result = remove_submission::execute($student1->id, $assign->get_instance()->id);
$this->assertFalse($result['status']);
$this->assertEquals('couldnotremovesubmission', $result['warnings'][0]['warningcode']);

$result = remove_submission::execute($student2->id, $assign->get_instance()->id);
$this->assertFalse($result['status']);
$this->assertEquals('couldnotremovesubmission', $result['warnings'][0]['warningcode']);
}

/**
* Test teacher can remove student submissions by WS if they have added capability.
* @covers ::execute
*/
public function test_teacher_editothersubmission_remove_submissions(): void {
global $DB;
$this->resetAfterTest();
[$course, $student1, $student2, $teacher, $assign, $instance] = $this->prepare_course();
$this->add_submission($student1, $assign);
$this->add_submission($student2, $assign);

$capability = 'mod/assign:editothersubmission';
$roleteacher = $DB->get_record('role', ['shortname' => 'teacher']);
assign_capability($capability, CAP_ALLOW, $roleteacher->id, $assign->get_context()->id);
role_assign($roleteacher->id, $teacher->id, $assign->get_context()->id);
accesslib_clear_all_caches_for_unit_testing();

$this->setUser($teacher);

$result = remove_submission::execute($student1->id, $assign->get_instance()->id);
$this->assertTrue($result['status']);
$this->assertEmpty($result['warnings']);

$result = remove_submission::execute($student2->id, $assign->get_instance()->id);
$this->assertTrue($result['status']);
$this->assertEmpty($result['warnings']);
}

/**
* Test user can't remove their own non-existent submission.
* @covers ::execute
*/
public function test_remove_own_notexists_submission(): void {
$this->resetAfterTest();
[$course, $student1, $student2, $teacher, $assign, $instance] = $this->prepare_course();

// Remove own submission when user has no submission to remove.
$this->setUser($student1);
$result = remove_submission::execute($student1->id, $assign->get_instance()->id);
$this->assertFalse($result['status']);
$this->assertEquals('submissionnotfoundtoremove', $result['warnings'][0]['warningcode']);
}

/**
* Test user can remove their own existing submission.
* @covers ::execute
*/
public function test_remove_own_submission(): void {
global $DB;
$this->resetAfterTest();
[$course, $student1, $student2, $teacher, $assign, $instance] = $this->prepare_course();

// Remove own submission.
$this->add_submission($student2, $assign);
$this->setUser($student2);

$result = remove_submission::execute($student2->id, $assign->get_instance()->id);
$this->assertTrue($result['status']);
$this->assertEmpty($result['warnings']);

// Make sure submission was removed.
$submission = $assign->get_user_submission($student2->id, 0);
$submissionquery = $DB->get_record('assign_submission', ['id' => $submission->id]);
$this->assertEquals(ASSIGN_SUBMISSION_STATUS_NEW, $submissionquery->status);

// Try to remove after removed.
$result = remove_submission::execute($student2->id, $assign->get_instance()->id);
$this->assertFalse($result['status']);
$this->assertEquals('submissionnotfoundtoremove', $result['warnings'][0]['warningcode']);
}

/**
* Test user can remove their own reopened submission.
* @covers ::execute
*/
public function test_remove_own_submission_reopened(): void {
global $DB, $USER;
$this->resetAfterTest();
[$course, $student1, $student2, $teacher, $assign, $instance] = $this->prepare_course();

// Create submission and reopen.
$this->add_submission($student2, $assign);
// Grade and reopen.
$this->setUser($teacher);
$feedbackpluginparams = [];
$feedbackpluginparams['files_filemanager'] = file_get_unused_draft_itemid();
$feedbackeditorparams = ['text' => 'Yeeha!', 'format' => 1];
$feedbackpluginparams['assignfeedbackcomments_editor'] = $feedbackeditorparams;
mod_assign_external::save_grade(
$instance->id,
$student1->id,
50.0,
-1,
false,
'released',
false,
$feedbackpluginparams
);
$USER->ignoresesskey = true;
$assign->testable_process_add_attempt($student1->id);

// Create submission.
$this->add_submission($student2, $assign);

// Remove own submission.
$this->setUser($student2);
$result = remove_submission::execute($student2->id, $assign->get_instance()->id);
$this->assertTrue($result['status']);
$this->assertEmpty($result['warnings']);

// Make sure submission was removed.
$submission = $assign->get_user_submission($student2->id, 0);
$submissionquery = $DB->get_record('assign_submission', ['id' => $submission->id]);
$this->assertEquals(ASSIGN_SUBMISSION_STATUS_NEW, $submissionquery->status);
}
}
Loading

0 comments on commit b477046

Please sign in to comment.