From 4b93c3e342d4c7fc46c7939f3e15a93a8b12e0e9 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Mon, 28 Sep 2015 11:46:53 +0200 Subject: [PATCH] MDL-51566 mod_choice: New WS mod_choice_delete_choice_responses --- lib/db/services.php | 1 + mod/choice/classes/external.php | 112 +++++++++++++++++++++ mod/choice/db/services.php | 8 ++ mod/choice/lib.php | 14 +++ mod/choice/tests/externallib_test.php | 135 ++++++++++++++++++++++++++ mod/choice/version.php | 2 +- version.php | 2 +- 7 files changed, 272 insertions(+), 2 deletions(-) diff --git a/lib/db/services.php b/lib/db/services.php index 84b9be16f3b..13786f27ca2 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -1237,6 +1237,7 @@ $services = array( 'mod_choice_submit_choice_response', 'mod_choice_view_choice', 'mod_choice_get_choices_by_courses', + 'mod_choice_delete_choice_responses', 'mod_imscp_view_imscp', 'mod_imscp_get_imscps_by_courses', ), diff --git a/mod/choice/classes/external.php b/mod/choice/classes/external.php index 3332e618459..d5676e40ddb 100644 --- a/mod/choice/classes/external.php +++ b/mod/choice/classes/external.php @@ -584,4 +584,116 @@ class mod_choice_external extends external_api { ); } + /** + * Describes the parameters for delete_choice_responses. + * + * @return external_external_function_parameters + * @since Moodle 3.0 + */ + public static function delete_choice_responses_parameters() { + return new external_function_parameters ( + array( + 'choiceid' => new external_value(PARAM_INT, 'choice instance id'), + 'responses' => new external_multiple_structure( + new external_value(PARAM_INT, 'response id'), + 'Array of response ids, empty for deleting all the user responses', + VALUE_DEFAULT, + array() + ), + ) + ); + } + + /** + * Delete the given submitted responses in a choice + * + * @param int $choiceid the choice instance id + * @param array $responses the response ids, empty for deleting all the user responses + * @return array status information and warnings + * @throws moodle_exception + * @since Moodle 3.0 + */ + public static function delete_choice_responses($choiceid, $responses = array()) { + + $status = false; + $warnings = array(); + $params = self::validate_parameters(self::delete_choice_responses_parameters(), + array( + 'choiceid' => $choiceid, + 'responses' => $responses + )); + + if (!$choice = choice_get_choice($params['choiceid'])) { + throw new moodle_exception("invalidcoursemodule", "error"); + } + list($course, $cm) = get_course_and_cm_from_instance($choice, 'choice'); + + $context = context_module::instance($cm->id); + self::validate_context($context); + + require_capability('mod/choice:choose', $context); + + // If we have the capability, delete all the passed responses. + if (has_capability('mod/choice:deleteresponses', $context)) { + if (empty($params['responses'])) { + // Get all the responses for the choice. + $params['responses'] = array_keys(choice_get_all_responses($choice)); + } + $status = choice_delete_responses($params['responses'], $choice, $cm, $course); + } else if ($choice->allowupdate) { + // Check if we can delate our own responses. + $timenow = time(); + if ($choice->timeclose != 0) { + if ($timenow > $choice->timeclose) { + throw new moodle_exception("expired", "choice", '', userdate($choice->timeclose)); + } + } + // Delete only our responses. + $myresponses = array_keys(choice_get_my_response($choice)); + + if (empty($params['responses'])) { + $todelete = $myresponses; + } else { + $todelete = array(); + foreach ($params['responses'] as $response) { + if (!in_array($response, $myresponses)) { + $warnings[] = array( + 'item' => 'response', + 'itemid' => $response, + 'warningcode' => 'nopermissions', + 'message' => 'No permission to delete this response' + ); + } else { + $todelete[] = $response; + } + } + } + + $status = choice_delete_responses($todelete, $choice, $cm, $course); + } else { + // The user requires the capability to delete responses. + throw new required_capability_exception($context, 'mod/choice:deleteresponses', 'nopermissions', ''); + } + + return array( + 'status' => $status, + 'warnings' => $warnings + ); + } + + /** + * Describes the delete_choice_responses return value. + * + * @return external_multiple_structure + * @since Moodle 3.0 + */ + public static function delete_choice_responses_returns() { + return new external_single_structure( + array( + 'status' => new external_value(PARAM_BOOL, 'status, true if everything went right'), + 'warnings' => new external_warnings(), + ) + ); + } + } diff --git a/mod/choice/db/services.php b/mod/choice/db/services.php index c88cf98aa28..8670d271f13 100644 --- a/mod/choice/db/services.php +++ b/mod/choice/db/services.php @@ -68,4 +68,12 @@ $functions = array( 'type' => 'read', 'capabilities' => '' ), + + 'mod_choice_delete_choice_responses' => array( + 'classname' => 'mod_choice_external', + 'methodname' => 'delete_choice_responses', + 'description' => 'Delete the given submitted responses in a choice', + 'type' => 'write', + 'capabilities' => 'mod/choice:choose' + ), ); diff --git a/mod/choice/lib.php b/mod/choice/lib.php index b04ce600982..43c3db6f419 100644 --- a/mod/choice/lib.php +++ b/mod/choice/lib.php @@ -935,6 +935,20 @@ function choice_get_my_response($choice) { return $DB->get_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $USER->id)); } + +/** + * Get all the responses on a given choice. + * + * @param stdClass $choice Choice record + * @return array of choice answers records + * @since Moodle 3.0 + */ +function choice_get_all_responses($choice) { + global $DB; + return $DB->get_records('choice_answers', array('choiceid' => $choice->id)); +} + + /** * Return true if we are allowd to view the choice results. * diff --git a/mod/choice/tests/externallib_test.php b/mod/choice/tests/externallib_test.php index 34bc06ab122..4902a563ebd 100644 --- a/mod/choice/tests/externallib_test.php +++ b/mod/choice/tests/externallib_test.php @@ -415,4 +415,139 @@ class mod_choice_externallib_testcase extends externallib_advanced_testcase { $choices = external_api::clean_returnvalue(mod_choice_external::get_choices_by_courses_returns(), $choices); $this->assertFalse(isset($choices['choices'][0]['timeopen'])); } + + /** + * Test delete_choice_responses + */ + public function test_delete_choice_responses() { + global $DB; + + $this->resetAfterTest(true); + + $course = self::getDataGenerator()->create_course(); + $params = new stdClass(); + $params->course = $course->id; + $params->option = array('fried rice', 'spring rolls', 'sweet and sour pork', 'satay beef', 'gyouza'); + $params->name = 'First Choice Activity'; + $params->showresults = CHOICE_SHOWRESULTS_ALWAYS; + $params->allowmultiple = 1; + $params->showunanswered = 1; + $choice = self::getDataGenerator()->create_module('choice', $params); + $cm = get_coursemodule_from_id('choice', $choice->cmid); + + $choiceinstance = choice_get_choice($cm->instance); + $options = array_keys($choiceinstance->option); + + $student = $this->getDataGenerator()->create_user(); + $studentrole = $DB->get_record('role', array('shortname' => 'student')); + + // Enroll student in Course1. + self::getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id); + + $this->setUser($student); + $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2])); + $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results); + + $myresponses = array_keys(choice_get_my_response($choice)); + + // Try to delete responses when allow update is false. + try { + mod_choice_external::delete_choice_responses($choice->id, array($myresponses[0], $myresponses[0])); + $this->fail('Exception expected due to missing permissions.'); + } catch (required_capability_exception $e) { + $this->assertEquals('nopermissions', $e->errorcode); + } + + // Set allow update to true, and a passed time close. + $DB->set_field('choice', 'allowupdate', 1, array('id' => $choice->id)); + $DB->set_field('choice', 'timeclose', time() - DAYSECS, array('id' => $choice->id)); + try { + mod_choice_external::delete_choice_responses($choice->id, array($myresponses[0], $myresponses[1])); + $this->fail('Exception expected due to expired choice.'); + } catch (moodle_exception $e) { + $this->assertEquals('expired', $e->errorcode); + } + + // Reset time close. We should be able now to delete all the responses. + $DB->set_field('choice', 'timeclose', 0, array('id' => $choice->id)); + $results = mod_choice_external::delete_choice_responses($choice->id, array($myresponses[0], $myresponses[1])); + $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results); + + $this->assertTrue($results['status']); + $this->assertCount(0, $results['warnings']); + // Now, in the DB 0 responses. + $this->assertCount(0, choice_get_my_response($choice)); + + // Submit again the responses. + $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2])); + $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results); + + $myresponses = array_keys(choice_get_my_response($choice)); + // Delete only one response. + $results = mod_choice_external::delete_choice_responses($choice->id, array($myresponses[0])); + $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results); + $this->assertTrue($results['status']); + $this->assertCount(0, $results['warnings']); + // Now, in the DB 1 response still. + $this->assertCount(1, choice_get_my_response($choice)); + + // Delete the remaining response, passing 2 invalid responses ids. + $results = mod_choice_external::delete_choice_responses($choice->id, array($myresponses[1], $myresponses[0] + 2, + $myresponses[0] + 3)); + $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results); + $this->assertTrue($results['status']); + // 2 warnings, 2 invalid responses. + $this->assertCount(2, $results['warnings']); + // Now, in the DB 0 responses. + $this->assertCount(0, choice_get_my_response($choice)); + + // Now, as an admin we must be able to delete all the responses under any condition. + // Submit again the responses. + $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2])); + $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results); + $studentresponses = array_keys(choice_get_my_response($choice)); + + $this->setAdminUser(); + $DB->set_field('choice', 'allowupdate', 0, array('id' => $choice->id)); + $DB->set_field('choice', 'timeclose', time() - DAYSECS, array('id' => $choice->id)); + + $results = mod_choice_external::delete_choice_responses($choice->id, array($studentresponses[0], $studentresponses[1])); + $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results); + + $this->assertTrue($results['status']); + $this->assertCount(0, $results['warnings']); + + // Submit again the responses. + $DB->set_field('choice', 'timeclose', 0, array('id' => $choice->id)); + $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2])); + $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results); + // With other user account too, so we can test all the responses are deleted. + choice_user_submit_response( array($options[1], $options[2]), $choice, $student->id, $course, $cm); + + // Test deleting all (not passing the answers ids), event not only mine. + $results = mod_choice_external::delete_choice_responses($choice->id); + $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results); + + $this->assertTrue($results['status']); + $this->assertCount(0, $results['warnings']); + $this->assertCount(0, choice_get_all_responses($choice)); + + // Now, in the DB 0 responses. + $this->setUser($student); + + // Submit again respones. + $DB->set_field('choice', 'allowupdate', 1, array('id' => $choice->id)); + $DB->set_field('choice', 'timeclose', 0, array('id' => $choice->id)); + $results = mod_choice_external::submit_choice_response($choice->id, array($options[1], $options[2])); + $results = external_api::clean_returnvalue(mod_choice_external::submit_choice_response_returns(), $results); + + // Delete all responses. + $results = mod_choice_external::delete_choice_responses($choice->id); + $results = external_api::clean_returnvalue(mod_choice_external::delete_choice_responses_returns(), $results); + + $this->assertTrue($results['status']); + $this->assertCount(0, $results['warnings']); + $this->assertCount(0, choice_get_my_response($choice)); + + } } diff --git a/mod/choice/version.php b/mod/choice/version.php index 135d434835a..5516f6feb77 100644 --- a/mod/choice/version.php +++ b/mod/choice/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2015051102; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2015051103; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2015050500; // Requires this Moodle version $plugin->component = 'mod_choice'; // Full name of the plugin (used for diagnostics) $plugin->cron = 0; diff --git a/version.php b/version.php index 4732f168d81..0713a533f00 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2015100200.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2015100200.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes.