MDL-52852 mod_quiz: New WS mod_quiz_save_attempt
authorJuan Leyva <juanleyvadelgado@gmail.com>
Wed, 20 Jan 2016 06:57:39 +0000 (07:57 +0100)
committerJuan Leyva <juanleyvadelgado@gmail.com>
Fri, 11 Mar 2016 15:16:02 +0000 (16:16 +0100)
mod/quiz/classes/external.php
mod/quiz/db/services.php
mod/quiz/tests/external_test.php
mod/quiz/version.php

index 9087bc4..e2dd09f 100644 (file)
@@ -1073,4 +1073,89 @@ class mod_quiz_external extends external_api {
         );
     }
 
+    /**
+     * Describes the parameters for save_attempt.
+     *
+     * @return external_external_function_parameters
+     * @since Moodle 3.1
+     */
+    public static function save_attempt_parameters() {
+        return new external_function_parameters (
+            array(
+                'attemptid' => new external_value(PARAM_INT, 'attempt id'),
+                'data' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'name' => new external_value(PARAM_RAW, 'data name'),
+                            'value' => new external_value(PARAM_RAW, 'data value'),
+                        )
+                    ), 'the data to be saved'
+                ),
+                'preflightdata' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'),
+                            'value' => new external_value(PARAM_RAW, 'data value'),
+                        )
+                    ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array()
+                )
+            )
+        );
+    }
+
+    /**
+     * Processes save requests during the quiz. This function is intended for the quiz auto-save feature.
+     *
+     * @param int $attemptid attempt id
+     * @param array $data the data to be saved
+     * @param  array $preflightdata preflight required data (like passwords)
+     * @return array of warnings and execution result
+     * @since Moodle 3.1
+     */
+    public static function save_attempt($attemptid, $data, $preflightdata = array()) {
+        global $DB;
+
+        $warnings = array();
+
+        $params = array(
+            'attemptid' => $attemptid,
+            'data' => $data,
+            'preflightdata' => $preflightdata,
+        );
+        $params = self::validate_parameters(self::save_attempt_parameters(), $params);
+
+        // Add a page, required by validate_attempt.
+        list($attemptobj, $messages) = self::validate_attempt($params);
+
+        $transaction = $DB->start_delegated_transaction();
+        // Create the $_POST object required by the question engine.
+        $_POST = array();
+        foreach ($data as $element) {
+            $_POST[$element['name']] = $element['value'];
+        }
+        $timenow = time();
+        $attemptobj->process_auto_save($timenow);
+        $transaction->allow_commit();
+
+        $result = array();
+        $result['status'] = true;
+        $result['warnings'] = $warnings;
+        return $result;
+    }
+
+    /**
+     * Describes the save_attempt return value.
+     *
+     * @return external_single_structure
+     * @since Moodle 3.1
+     */
+    public static function save_attempt_returns() {
+        return new external_single_structure(
+            array(
+                'status' => new external_value(PARAM_BOOL, 'status: true if success'),
+                'warnings' => new external_warnings(),
+            )
+        );
+    }
+
 }
index 9f3e84f..24b3e38 100644 (file)
@@ -100,4 +100,14 @@ $functions = array(
         'capabilities'  => 'mod/quiz:attempt',
         'services'      => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
     ),
+
+    'mod_quiz_save_attempt' => array(
+        'classname'     => 'mod_quiz_external',
+        'methodname'    => 'save_attempt',
+        'description'   => 'Processes save requests during the quiz.
+                            This function is intended for the quiz auto-save feature.',
+        'type'          => 'write',
+        'capabilities'  => 'mod/quiz:attempt',
+        'services'      => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
+    ),
 );
index f2c42c8..6fab602 100644 (file)
@@ -139,7 +139,7 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
                 // Finish the attempt.
                 $attemptobj->process_finish(time(), false);
             }
-            return array($quiz, $context, $quizobj, $attempt, $attemptobj);
+            return array($quiz, $context, $quizobj, $attempt, $attemptobj, $quba);
         } else {
             return array($quiz, $context, $quizobj);
         }
@@ -950,4 +950,64 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
 
     }
 
+    /**
+     * Test save_attempt
+     */
+    public function test_save_attempt() {
+
+        // Create a new quiz with one attempt started.
+        list($quiz, $context, $quizobj, $attempt, $attemptobj, $quba) = $this->create_quiz_with_questions(true);
+
+        // Response for slot 1.
+        $prefix = $quba->get_field_prefix(1);
+        $data = array(
+            array('name' => 'slots', 'value' => 1),
+            array('name' => $prefix . ':sequencecheck',
+                    'value' => $attemptobj->get_question_attempt(1)->get_sequence_check_count()),
+            array('name' => $prefix . 'answer', 'value' => 1),
+        );
+
+        $this->setUser($this->student);
+
+        $result = mod_quiz_external::save_attempt($attempt->id, $data);
+        $result = external_api::clean_returnvalue(mod_quiz_external::save_attempt_returns(), $result);
+        $this->assertTrue($result['status']);
+
+        // Now, get the summary.
+        $result = mod_quiz_external::get_attempt_summary($attempt->id);
+        $result = external_api::clean_returnvalue(mod_quiz_external::get_attempt_summary_returns(), $result);
+
+        // Check it's marked as completed only the first one.
+        $this->assertEquals('complete', $result['questions'][0]['state']);
+        $this->assertEquals('todo', $result['questions'][1]['state']);
+        $this->assertEquals(1, $result['questions'][0]['number']);
+        $this->assertEquals(2, $result['questions'][1]['number']);
+        $this->assertFalse($result['questions'][0]['flagged']);
+        $this->assertFalse($result['questions'][1]['flagged']);
+        $this->assertEmpty($result['questions'][0]['mark']);
+        $this->assertEmpty($result['questions'][1]['mark']);
+
+        // Now, second slot.
+        $prefix = $quba->get_field_prefix(2);
+        $data = array(
+            array('name' => 'slots', 'value' => 2),
+            array('name' => $prefix . ':sequencecheck',
+                    'value' => $attemptobj->get_question_attempt(1)->get_sequence_check_count()),
+            array('name' => $prefix . 'answer', 'value' => 1),
+        );
+
+        $result = mod_quiz_external::save_attempt($attempt->id, $data);
+        $result = external_api::clean_returnvalue(mod_quiz_external::save_attempt_returns(), $result);
+        $this->assertTrue($result['status']);
+
+        // Now, get the summary.
+        $result = mod_quiz_external::get_attempt_summary($attempt->id);
+        $result = external_api::clean_returnvalue(mod_quiz_external::get_attempt_summary_returns(), $result);
+
+        // Check it's marked as completed only the first one.
+        $this->assertEquals('complete', $result['questions'][0]['state']);
+        $this->assertEquals('complete', $result['questions'][1]['state']);
+
+    }
+
 }
index c2b32ff..e332f17 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2015111609;
+$plugin->version   = 2015111610;
 $plugin->requires  = 2015111000;
 $plugin->component = 'mod_quiz';
 $plugin->cron      = 60;