MDL-51566 mod_choice: New WS mod_choice_delete_choice_responses
authorJuan Leyva <juanleyvadelgado@gmail.com>
Mon, 28 Sep 2015 09:46:53 +0000 (11:46 +0200)
committerJuan Leyva <juanleyvadelgado@gmail.com>
Wed, 7 Oct 2015 11:58:51 +0000 (13:58 +0200)
lib/db/services.php
mod/choice/classes/external.php
mod/choice/db/services.php
mod/choice/lib.php
mod/choice/tests/externallib_test.php
mod/choice/version.php
version.php

index 84b9be1..13786f2 100644 (file)
@@ -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',
             ),
index 3332e61..d5676e4 100644 (file)
@@ -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(),
+            )
+        );
+    }
+
 }
index c88cf98..8670d27 100644 (file)
@@ -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'
+    ),
 );
index b04ce60..43c3db6 100644 (file)
@@ -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.
  *
index 34bc06a..4902a56 100644 (file)
@@ -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));
+
+    }
 }
index 135d434..5516f6f 100644 (file)
@@ -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;
index 4732f16..0713a53 100644 (file)
@@ -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.