Bladeren bron

Answer-storage change

Gregory Huxley 2 weken geleden
bovenliggende
commit
e6a7da1536

+ 2 - 0
TK2.php

@@ -1,5 +1,7 @@
 <?php
 
+require_once __DIR__ . '/TkAnswerStore.php';
+
 class TK2 {
     
     private $data = [];

+ 98 - 0
TkAnswerStore.php

@@ -0,0 +1,98 @@
+<?php
+/**
+ * Server-side storage for the expected answer of the question a user is
+ * currently looking at.
+ *
+ * Before: the generators put the correct answer in a hidden `checkerData`
+ * field and the checker trusted whatever came back, so a student could edit
+ * it. Now the payload stays on the server and the form only carries an opaque
+ * single-use token (still posted as `checkerData`, so form markup is unchanged).
+ *
+ * Schema (add to timekeeper_sql.php in e107's format, or run once by hand with
+ * the e107_ prefix on the table name):
+ *
+ *   CREATE TABLE tkPendingAnswer (
+ *     username varchar(100) NOT NULL,
+ *     token char(32) NOT NULL,
+ *     payload text NOT NULL,
+ *     created int(10) unsigned NOT NULL,
+ *     PRIMARY KEY (username)
+ *   ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ *
+ * One pending row per user, matching the existing one-open-question model.
+ */
+class TkAnswerStore
+{
+    const TABLE = 'tkPendingAnswer';
+    const TTL = 7200; // seconds a question stays answerable
+
+    /**
+     * Store the expected-answer payload for $user and return the token to
+     * embed in the form's hidden `checkerData` field.
+     *
+     * @param string $user
+     * @param array  $payload e.g. ['answer1' => 42, 'question' => '6 x 7'] or ['word' => 'because']
+     * @return string
+     */
+    public static function issue($user, array $payload)
+    {
+        $db = e107::getDb();
+        $u = $db->escape($user);
+
+        // Replace this user's pending row and sweep expired ones.
+        $db->delete(self::TABLE, "username = '{$u}' OR created < " . (time() - self::TTL));
+
+        $token = bin2hex(random_bytes(16));
+        $db->insert(self::TABLE, [
+            'username' => $user,
+            'token'    => $token,
+            'payload'  => json_encode($payload),
+            'created'  => time(),
+        ]);
+
+        return $token;
+    }
+
+    /**
+     * Validate the posted token and return the payload, consuming it so the
+     * same question cannot be scored twice. Returns null if the token is
+     * missing, wrong (e.g. stale browser tab), expired, or already used.
+     *
+     * @param string      $user
+     * @param string|null $token value of POST `checkerData`
+     * @return array|null
+     */
+   public static function consume($user, $token)
+{
+    if (!is_string($token) || $token === '') {
+        error_log("TK consume: no token posted (user=$user)");
+        return null;
+    }
+
+    $db = e107::getDb();
+    $u = $db->escape($user);
+
+    $row = $db->retrieve(self::TABLE, 'token, payload, created', "username = '{$u}'");
+    if (!is_array($row)) {
+        error_log("TK consume: no pending row for user=$user");
+        return null;
+    }
+    if (!hash_equals((string) $row['token'], $token)) {
+        error_log("TK consume: token mismatch user=$user posted=" . substr($token, 0, 40));
+        return null;
+    }
+
+    $deleted = $db->delete(self::TABLE, "username = '{$u}' AND token = '" . $db->escape($row['token']) . "'");
+    error_log("TK consume: delete returned " . var_export($deleted, true));
+    // Temporarily ignore the delete result while diagnosing:
+    // if (!$deleted) { return null; }
+
+    if (time() - (int) $row['created'] > self::TTL) {
+        error_log("TK consume: expired");
+        return null;
+    }
+
+    $payload = json_decode($row['payload'], true);
+    return is_array($payload) ? $payload : null;
+}
+}

+ 111 - 113
answerChecker2.php

@@ -1,42 +1,42 @@
 <?php
 
-/* 
+/*
  * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
  * Click nbfs://nbhost/SystemFileSystem/Templates/Scripting/EmptyPHP.php to edit this template
  */
 
-class answerChecker2{
-    
+class answerChecker2 {
+
     private $user;
     private $messages = [
-        'numbers'=>['Find the missing number. {QUESTION}',"Congratulations '{USER_ANSWER}' was correct for Find the missing number '{ANSWER}'","Sorry but '{USER_ANSWER}' was incorrect it should have been '{ANSWER}' for 'Find the missing number in {QUESTION}'"],
-        'addition'=>['Solve the following equation. {QUESTION}.',"Congratulations '{USER_ANSWER}' was correct for Solve the following equation. '{ANSWER}'","Sorry but '{USER_ANSWER}' was incorrect it should have been '{ANSWER}' for 'Solve the following equation in {QUESTION}'"],
-        'subtraction'=>['Solve the following equation. {QUESTION}.',"Congratulations '{USER_ANSWER}' was correct for Solve the following equation. '{ANSWER}'","Sorry but '{USER_ANSWER}' was incorrect it should have been '{ANSWER}' for 'Solve the following equation in {QUESTION}'"],
-        'multiplication'=>['Solve the following equation. {QUESTION}.',"Congratulations '{USER_ANSWER}' was correct for Solve the following equation. '{ANSWER}'","Sorry but '{USER_ANSWER}' was incorrect it should have been '{ANSWER}' for 'Solve the following equation in {QUESTION}'"],
-        'division'=>['Solve the following equation. {QUESTION}.',"Congratulations '{USER_ANSWER}' was correct for Solve the following equation. '{ANSWER}'","Sorry but '{USER_ANSWER}' was incorrect it should have been '{ANSWER}' for 'Solve the following equation in {QUESTION}'"],
-        'rounding'=>[],
-        'worded_add'=>[],
-        'worded_sub'=>[],
-        'worded_mult'=>[],
-        'worded_devi'=>[],
-        'anagrams'=>['Unjumble the letters to find the word. {QUESTION}.',"Congratulations '{USER_ANSWER}' was correct for Unjumble the letters '{ANSWER}'","Sorry but '{USER_ANSWER}' was incorrect it should have been '{ANSWER}' for 'Unjumble the letters in {QUESTION}'"],
-        'generalKnowledge'=>[],
-        'elements'=>['Fill in the missing element information. {QUESTION}',"Congratulations '{USER_ANSWER}' was correct for Fill in the missing element information '{ANSWER}'","Sorry but '{USER_ANSWER}' was incorrect it should have been '{ANSWER}' for 'Fill in the missing element information in {QUESTION}'"],
-        'continents'=>[],
-        'countries'=>[],
-        'flags'=>[],
-        'capitalCities'=>[],
-        'artists'=>[],
-        'malaySpelling'=>[],
-        'nameBodyParts'=>[],
-        'nameBodyOrgans'=>[],
-        'basicFirstAid'=>[]
+        'numbers' => ['Find the missing number. {QUESTION}', "Congratulations '{USER_ANSWER}' was correct for Find the missing number '{ANSWER}'", "Sorry but '{USER_ANSWER}' was incorrect it should have been '{ANSWER}' for 'Find the missing number in {QUESTION}'"],
+        'addition' => ['Solve the following equation. {QUESTION}.', "Congratulations '{USER_ANSWER}' was correct for Solve the following equation. '{ANSWER}'", "Sorry but '{USER_ANSWER}' was incorrect it should have been '{ANSWER}' for 'Solve the following equation in {QUESTION}'"],
+        'subtraction' => ['Solve the following equation. {QUESTION}.', "Congratulations '{USER_ANSWER}' was correct for Solve the following equation. '{ANSWER}'", "Sorry but '{USER_ANSWER}' was incorrect it should have been '{ANSWER}' for 'Solve the following equation in {QUESTION}'"],
+        'multiplication' => ['Solve the following equation. {QUESTION}.', "Congratulations '{USER_ANSWER}' was correct for Solve the following equation. '{ANSWER}'", "Sorry but '{USER_ANSWER}' was incorrect it should have been '{ANSWER}' for 'Solve the following equation in {QUESTION}'"],
+        'division' => ['Solve the following equation. {QUESTION}.', "Congratulations '{USER_ANSWER}' was correct for Solve the following equation. '{ANSWER}'", "Sorry but '{USER_ANSWER}' was incorrect it should have been '{ANSWER}' for 'Solve the following equation in {QUESTION}'"],
+        'rounding' => [],
+        'worded_add' => [],
+        'worded_sub' => [],
+        'worded_mult' => [],
+        'worded_devi' => [],
+        'anagrams' => ['Unjumble the letters to find the word. {QUESTION}.', "Congratulations '{USER_ANSWER}' was correct for Unjumble the letters '{ANSWER}'", "Sorry but '{USER_ANSWER}' was incorrect it should have been '{ANSWER}' for 'Unjumble the letters in {QUESTION}'"],
+        'generalKnowledge' => [],
+        'elements' => ['Fill in the missing element information. {QUESTION}', "Congratulations '{USER_ANSWER}' was correct for Fill in the missing element information '{ANSWER}'", "Sorry but '{USER_ANSWER}' was incorrect it should have been '{ANSWER}' for 'Fill in the missing element information in {QUESTION}'"],
+        'continents' => [],
+        'countries' => [],
+        'flags' => [],
+        'capitalCities' => [],
+        'artists' => [],
+        'malaySpelling' => [],
+        'nameBodyParts' => [],
+        'nameBodyOrgans' => [],
+        'basicFirstAid' => []
     ];
 
     /**
      * Get the right answer checker for the subject
      */
-    public function checkQuestionSubject($subject,$user) {
+    public function checkQuestionSubject($subject, $user) {
         $this->user = $user;
         switch ($subject) {
             case 'numbers':
@@ -51,7 +51,7 @@ class answerChecker2{
             case 'worded_mult':
             case 'worded_devi':
             case 'spelling':
-                $this->checkSpelling();//Custom checker
+                $this->checkSpelling(); //Custom checker
                 break;
             case 'anagrams':
                 $this->checkAnagrams();
@@ -69,153 +69,143 @@ class answerChecker2{
             case 'nameBodyParts':
             case 'nameBodyOrgans':
             case 'basicFirstAid':
-                
+
             default:
                 print_a('An error has been detected in the answer checker. Please report this and refresh the page.');
                 break;
         }
     }
-    
+
     /**
      * Check the answer for anything basic
      */
     private function checkGeneric($subject) {
-        
-        $usersAnswer = filter_input(INPUT_POST,'a1');
-        $answer = json_decode(base64_decode(filter_input(INPUT_POST,'checkerData')),true);
-        $lastAnswer = e107::getDb()->retrieve('tkReport', 'userAnswer', 'username="' . $this->user . '" ORDER BY id DESC LIMIT 1');
-        
-        if(is_array($answer['question'])){
-            $answer['question'] = implode('', $answer['question']);
+
+        $usersAnswer = (string) filter_input(INPUT_POST, 'a1');
+        $answer = $this->loadPending();
+        if ($answer === null) {
+            return;
         }
-        
-        if($lastAnswer==$usersAnswer){
-            return;//print_a('huh');//silent fail for cheats
+
+        if (is_array($answer['question'])) {
+            $answer['question'] = implode('', $answer['question']);
         }
-        
-        if($usersAnswer==$answer['answer1']){
-            $text = str_replace(['{ANSWER}','{USER_ANSWER}'], [$answer['answer1'],trim($usersAnswer)], $this->messages[$subject][1]) . "<br/>";
+
+        if (trim($usersAnswer) === trim((string) $answer['answer1'])) {
+            $text = str_replace(['{ANSWER}', '{USER_ANSWER}'], [$answer['answer1'], trim($usersAnswer)], $this->messages[$subject][1]) . "<br/>";
             $this->updateScore(true);
-        }else{
-            $text = str_replace(['{ANSWER}','{USER_ANSWER}','{QUESTION}'], [$answer['answer1'],trim($usersAnswer),$answer['question']], $this->messages[$subject][2]) . "<br/>";
+        } else {
+            $text = str_replace(['{ANSWER}', '{USER_ANSWER}', '{QUESTION}'], [$answer['answer1'], trim($usersAnswer), $answer['question']], $this->messages[$subject][2]) . "<br/>";
             $this->updateScore(false);
         }
-        
-        $this->addToReport($answer['answer1'], $usersAnswer, $subject, str_replace('{ANSWER}', $answer['answer1'], str_replace('{QUESTION}',$answer['question'],$this->messages[$subject][0])));
-        e107::getRender()->tablerender(ucfirst($subject).' answer check', $text);
-        
+
+        $this->addToReport($answer['answer1'], $usersAnswer, $subject, str_replace('{ANSWER}', $answer['answer1'], str_replace('{QUESTION}', $answer['question'], $this->messages[$subject][0])));
+        e107::getRender()->tablerender(ucfirst($subject) . ' answer check', $text);
     }
-    
+
     /**
      * Check the answer for anything basic
      */
     private function checkAnagrams() {
-        
-        $usersAnswer = filter_input(INPUT_POST,'a1');
-        $answer = json_decode(base64_decode(filter_input(INPUT_POST,'checkerData')),true);
-        $lastAnswer = e107::getDb()->retrieve('tkReport', 'userAnswer', 'username="' . $this->user . '" ORDER BY id DESC LIMIT 1');
-        
-        if(is_array($answer['question'])){
-            $answer['question'] = implode('', $answer['question']);
+
+        $usersAnswer = (string) filter_input(INPUT_POST, 'a1');
+        $answer = $this->loadPending();
+        if ($answer === null) {
+            return;
         }
-        
-        if($lastAnswer==$usersAnswer){
-            return;//print_a('huh');//silent fail for cheats
+
+        if (is_array($answer['question'])) {
+            $answer['question'] = implode('', $answer['question']);
         }
-        
+
         $words = e107::getDb()->retrieve('tk_wordList', 'word');
-        
-        if($usersAnswer==$answer['answer1']||$this->findInList($words,$usersAnswer)){
-            $text = str_replace(['{ANSWER}','{USER_ANSWER}'], [$answer['answer1'],trim($usersAnswer)], $this->messages['anagrams'][1]) . "<br/>";
+
+        if (trim($usersAnswer) === trim((string) $answer['answer1']) || $this->findInList($words, $usersAnswer)) {
+            $text = str_replace(['{ANSWER}', '{USER_ANSWER}'], [$answer['answer1'], trim($usersAnswer)], $this->messages['anagrams'][1]) . "<br/>";
             $this->updateScore(true);
-        }else{
-            $text = str_replace(['{ANSWER}','{USER_ANSWER}','{QUESTION}'], [$answer['answer1'],trim($usersAnswer),$answer['question']], $this->messages['anagrams'][2]) . "<br/>";
+        } else {
+            $text = str_replace(['{ANSWER}', '{USER_ANSWER}', '{QUESTION}'], [$answer['answer1'], trim($usersAnswer), $answer['question']], $this->messages['anagrams'][2]) . "<br/>";
             $this->updateScore(false);
         }
-        
-        $this->addToReport($answer['answer1'], $usersAnswer, 'anagrams', str_replace('{ANSWER}', $answer['answer1'], str_replace('{QUESTION}',$answer['question'],$this->messages['anagrams'][0])));
-        e107::getRender()->tablerender(ucfirst('anagrams').' answer check', $text);
-        
+
+        $this->addToReport($answer['answer1'], $usersAnswer, 'anagrams', str_replace('{ANSWER}', $answer['answer1'], str_replace('{QUESTION}', $answer['question'], $this->messages['anagrams'][0])));
+        e107::getRender()->tablerender(ucfirst('anagrams') . ' answer check', $text);
     }
-    
-    private function findInList($list,$needle) {
+
+    private function findInList($list, $needle) {
         foreach ($list as $listItem) {
-            if($listItem==$needle){
+            if ($listItem == $needle) {
                 return true;
             }
         }
         return false;
     }
-    
+
     /**
      * Check the answer for spelling
      */
     private function checkSpelling() {
-        
-        $usersAnswer = filter_input(INPUT_POST,'a1');//log for diagnostics =======================================================
-        $answer = json_decode(base64_decode(filter_input(INPUT_POST,'checkerData')),true);
-        $lastAnswer = e107::getDb()->retrieve('tkReport', 'userAnswer', 'username="' . $this->user . '" ORDER BY id DESC LIMIT 1');
-        
-        if($lastAnswer==$usersAnswer){
-            return;//silent fail for cheats
+
+        $usersAnswer = (string) filter_input(INPUT_POST, 'a1');
+        $answer = $this->loadPending();
+        if ($answer === null) {
+            return;
         }
-        
-        if($usersAnswer==$answer['word']){
+
+        if (trim($usersAnswer) === trim((string) $answer['word'])) {
             $text = "Congratulations '" . trim($usersAnswer) . "' was correct for 'spell the word " . $answer['word'] . "'<br/>";
             $this->updateScore(true);
             //file_put_contents('debug_Spelling_data.txt','<->'.$answer['word'].'<->'.$usersAnswer.'<->True'."\n",FILE_APPEND);//=================================
-        }else{
+        } else {
             $text = "Sorry but '" . trim($usersAnswer) . "' was incorrect it should have been '" . trim($answer['word']) . "' for 'spell the word " . $answer['word'] . "'<br/>";
             $this->updateScore(false);
             //file_put_contents('debug_Spelling_data.txt','<->'.$answer['word'].'<->'.$usersAnswer.'<->False'."\n",FILE_APPEND);//=================================
         }
-        
+
         $this->addToReport($answer['word'], $usersAnswer, 'spelling', "Spell the word " . $answer['word']);
         e107::getRender()->tablerender('Spelling answer check', $text);
     }
-    
+
     /**
      * Add or minus the score    
      */
     private function updateScore($scorePoint) {
         $currentScore = e107::getDb()->retrieve('timekeeperScore', 'timekeeper_lifetimecorrect, timekeeper_lifetimequestions, timekeeper_minutes', 'timekeeper_username="' . $this->user . '"');
 
-        if($scorePoint){
+        if ($scorePoint) {
             $currentScore['timekeeper_lifetimecorrect']++;
             $currentScore['timekeeper_minutes'] = $currentScore['timekeeper_minutes'] + $this->scaleTimeAddition();
-        }else{
+        } else {
             $currentScore['timekeeper_minutes']--;
         }
         $currentScore['timekeeper_lifetimequestions']++;
-        
-        if($currentScore['timekeeper_lifetimequestions']==1){
+
+        if ($currentScore['timekeeper_lifetimequestions'] == 1) {
             $currentScore['timekeeper_lastquestiondatestamp'] = time();
             $currentScore['timekeeper_yearlevel'] = 0;
             $currentScore['timekeeper_username'] = $this->user;
             e107::getDb()->insert('timekeeperScore', $currentScore);
-        }else{
+        } else {
             $currentScore['WHERE'] = 'timekeeper_username="' . $this->user . '"';
-            e107::getDb()->update('timekeeperScore',$currentScore);
+            e107::getDb()->update('timekeeperScore', $currentScore);
         }
     }
-    
+
     /**
      * Return how many minutes play time to award scaled by difficulty
      */
-    private function scaleTimeAddition() { 
+    private function scaleTimeAddition() {
 //TODO look at this and fix it.
-        $difficulty = $this->getDifficulty($this->user);//replace with db call
+        $difficulty = $this->getDifficulty($this->user); //replace with db call
         $maxDifficulty = 5000000; // 5 mil is max here
-
         //$returnMinutes = 10 - ($difficulty / $maxDifficulty * 100) / 10;
-        $returnMinutes = (int)$difficulty / 10;
+        $returnMinutes = (int) $difficulty / 10;
 
         //print_a(['diffi'=>$difficulty,'fprint'=> round($difficulty),'retmins'=>round($returnMinutes)]);
-        
+
         $returnMinutes = round($returnMinutes);
-        
-        if($returnMinutes<=1)
-        {
+
+        if ($returnMinutes <= 1) {
             $returnMinutes = 1;
         }
         return round($returnMinutes);
@@ -231,24 +221,23 @@ class answerChecker2{
             FROM e107.`e107_tkReport` WHERE username = '" . $user . "'
             AND answereddatestamp >= unix_timestamp(CURRENT_DATE() - INTERVAL 1 MONTH)
             AND answereddatestamp <= unix_timestamp(CURRENT_DATE() + INTERVAL 1 DAY)");
-            while ($row = e107::getDb()->fetch()) {
-                $returnArray = array_merge($returnArray,$row);
-            }
-        return $returnArray['percent'];//($returnArray['totalQuestionCount']*$returnArray['percent']);
-        
+        while ($row = e107::getDb()->fetch()) {
+            $returnArray = array_merge($returnArray, $row);
+        }
+        return $returnArray['percent']; //($returnArray['totalQuestionCount']*$returnArray['percent']);
     }
 
     /**
      * Add the last answered question to the report DB table.
      */
-    private function addToReport($answer,$usersAnswer,$subject,$questionString) {
-       
-        if($answer==$usersAnswer) {
+    private function addToReport($answer, $usersAnswer, $subject, $questionString) {
+
+        if ($answer == $usersAnswer) {
             $correct = 1;
-        }else{
+        } else {
             $correct = 0;
         }
-        
+
         $insert = [
             'username' => $this->user,
             'question' => $questionString,
@@ -258,18 +247,27 @@ class answerChecker2{
             'subject' => $subject,
             'correct' => $correct
         ];
-        
+
         e107::getDb()->insert('tkReport', $insert);
     }
-    
+
     /**
      * ShortCode engine
      */
-    private function shortCode($keys,$message) {
+    private function shortCode($keys, $message) {
         $returnMessage = $message;
         foreach ($keys as $key => $value) {
             $returnMessage = $str;
         }
     }
     
+    private function loadPending(): ?array {
+    $token = filter_input(INPUT_POST, 'checkerData');
+    $answer = TkAnswerStore::consume($this->user, is_string($token) ? $token : null);
+    if ($answer === null) {
+        e107::getRender()->tablerender('Answer check',
+            'That question has expired or was already answered. Please answer the new question.');
+    }
+    return $answer;
+}
 }

BIN
audio/6f129a2e6e103ea342a085875931830b.mp3


BIN
audio/8cbad96aced40b3838dd9f07f6ef5772.mp3


BIN
audio/9eb0d040ef57f4a06759cf307b657918.mp3


BIN
audio/efb5803993a803fe4414fa2ca001a84f.mp3


+ 1 - 1
questionGenerators/english/anagrams.php

@@ -62,7 +62,7 @@ class anagrams
         
         $fString .= '</br></br>';
         $fString .= $frm->text('a1', '', 80, ['placeholder' => 'Write answer here!', 'autocomplete' => "off", 'spellcheck' => "false"]) . "\n";
-        $fString .= $frm->hidden('checkerData', base64_encode(json_encode($data)), ['id' => 'checkerData']);
+        $fString .= $frm->hidden('checkerData', TkAnswerStore::issue($this->user, $data), ['id' => 'checkerData']);
         $fString .= $frm->hidden('subject', 'anagrams', ['id' => 'subject']);
         $fString .= '</br>' . "\n";
         $fString .= e107::getForm()->button('Answer', 'Check answer') . "\n";

+ 1 - 1
questionGenerators/english/spelling2.php

@@ -72,7 +72,7 @@ class spelling2 {
 
         $fString .= '</br></br>';
         $fString .= $frm->text('a1', '', 80, ['placeholder' => 'Write answer here!', 'autocomplete' => "off", 'spellcheck' => "false"]) . "\n";
-        $fString .= $frm->hidden('checkerData', base64_encode(json_encode($data)), ['id' => 'checkerData']);
+        $fString .= $frm->hidden('checkerData', TkAnswerStore::issue($this->user, $data), ['id' => 'checkerData']);
         $fString .= $frm->hidden('subject', 'spelling', ['id' => 'subject']);
         $fString .= '</br>' . "\n";
         $fString .= e107::getForm()->button('Answer', 'Check answer') . "\n";

+ 1 - 1
questionGenerators/math/numbers2.php

@@ -110,7 +110,7 @@ class numbers2 {
         
         $fString .= '</br></br>';
         $fString .= $frm->text('a1', '', 80, ['placeholder' => 'Write answer here!', 'autocomplete' => "off", 'spellcheck' => "false"]) . "\n";
-        $fString .= $frm->hidden('checkerData', base64_encode(json_encode($question)), ['id' => 'checkerData']);
+        $fString .= $frm->hidden('checkerData', TkAnswerStore::issue($this->user, $question), ['id' => 'checkerData']);
         $fString .= $frm->hidden('subject', 'numbers', ['id' => 'subject']);
         $fString .= '</br>' . "\n";
         $fString .= e107::getForm()->button('Answer', 'Check answer') . "\n";

+ 1 - 1
questionGenerators/math/numeracy2.php

@@ -183,7 +183,7 @@ class numeracy2 {
         
         $fString .= '</br></br>';
         $fString .= $frm->text('a1', '', 80, ['placeholder' => 'Write answer here!', 'autocomplete' => "off", 'spellcheck' => "false"]) . "\n";
-        $fString .= $frm->hidden('checkerData', base64_encode(json_encode($question)), ['id' => 'checkerData']);
+        $fString .= $frm->hidden('checkerData', TkAnswerStore::issue($this->user, $question), ['id' => 'checkerData']);
         $fString .= $frm->hidden('subject', $this->subSubject, ['id' => 'subject']);
         $fString .= '</br>' . "\n";
         $fString .= e107::getForm()->button('Answer', 'Check answer') . "\n";

+ 1 - 1
questionGenerators/science/elements2.php

@@ -138,7 +138,7 @@ class elements2 {
         
         $fString .= '</br></br>';
         $fString .= $frm->text('a1', '', 80, ['placeholder' => 'Write answer here!', 'autocomplete' => "off", 'spellcheck' => "false"]) . "\n";
-        $fString .= $frm->hidden('checkerData', base64_encode(json_encode($question)), ['id' => 'checkerData']);
+        $fString .= $frm->hidden('checkerData', TkAnswerStore::issue($this->user, $question), ['id' => 'checkerData']);
         $fString .= $frm->hidden('subject', 'elements', ['id' => 'elements']);
         $fString .= '</br>' . "\n";
         $fString .= e107::getForm()->button('Answer', 'Check answer') . "\n";

+ 1 - 1
timekeeperClientServices/elliotdebug.dat

@@ -1 +1 @@
--23-
+-24-

+ 1 - 1
timekeeperClientServices/silasdebug.dat

@@ -1 +1 @@
--25-
+-22-

+ 9 - 1
timekeeper_sql.php

@@ -68,4 +68,12 @@ CREATE TABLE `tkLastQuestion` (
   `id` INT NOT NULL AUTO_INCREMENT,
   `user` VARCHAR(100) NULL,
   `LastQuestionHtml` LONGTEXT NULL,
-  PRIMARY KEY (`id`));
+  PRIMARY KEY (`id`));
+  
+  CREATE TABLE tkPendingAnswer (
+  username varchar(100) NOT NULL,
+  token char(32) NOT NULL,
+  payload text NOT NULL,
+  created int(10) unsigned NOT NULL,
+  PRIMARY KEY (username)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;