.., 'count' => ..] (it returned * int 1 for new users and the code then indexed it like an array) * - the question type is chosen without float arguments to rand() * - the stored `question` is now a readable string ("element 12, missing * symbol") instead of a nested array, which the checker imploded into * "Array" in its messages; the element data the form needs is stored * separately (server-side, via TkAnswerStore) * - periodicTable.json and the template are loaded by absolute path * - user escaped in SQL; `e107.` database name replaced by MPREFIX * - previous tkLastQuestion row removed before inserting the new one */ class elements2 { private $user; /** Human wording for each element property, used in the checker's messages. */ private $labels = [ 'symbol' => 'symbol', 'phase' => 'phase', 'name' => 'name', 'number' => 'atomic number', 'atomic_mass' => 'atomic mass', ]; /** * @param string $user User to supply the question for. */ public function __construct($user) { $this->user = $user; } public function getElements() { $difficulty = $this->difficulty(); // 0..4, widening as accuracy improves (one more type per 20%). $maxType = min(4, (int) floor($difficulty['percent'] / 20)); $question = $this->elementSwitch(mt_rand(0, $maxType), $difficulty); if ($question === null) { e107::getRender()->tablerender('Elements', 'The periodic table data could not be loaded.'); return; } $questionHtml = $this->htmlify($question); $db = e107::getDb(); $db->delete('tkLastQuestion', "user = '" . $db->escape($this->user) . "'"); $db->insert('tkLastQuestion', ['user' => $this->user, 'LastQuestionHtml' => base64_encode($questionHtml)]); e107::getRender()->tablerender('Elements', $questionHtml); } private function elementSwitch($switchValue, $difficulty) { switch ($switchValue) { case 0: return $this->elementValues('symbol', $difficulty); case 1: return $this->elementValues('phase', $difficulty); case 2: return $this->elementValues('name', $difficulty); case 3: return $this->elementValues('number', $difficulty); default: return $this->elementValues('atomic_mass', $difficulty); } } /** * Difficulty based on the last month of answers. * * @return array ['percent' => float 0-100, 'count' => int] */ private function difficulty() { $db = e107::getDb(); $stats = $db->retrieve( "SELECT (SUM(correct)/COUNT(correct)*100) AS percent, COUNT(correct) AS count FROM `" . MPREFIX . "tkReport` WHERE answereddatestamp >= UNIX_TIMESTAMP(NOW() - INTERVAL 1 MONTH) AND username = '" . $db->escape($this->user) . "' AND subject = 'elements'" ); if (!is_array($stats) || (int) $stats['count'] === 0) { return ['percent' => 0, 'count' => 0]; } return ['percent' => (float) $stats['percent'], 'count' => (int) $stats['count']]; } private function loadElements() { $path = dirname(__DIR__, 2) . '/resources/periodicTable.json'; $json = is_file($path) ? json_decode(file_get_contents($path), true) : null; return (is_array($json) && !empty($json['elements'])) ? $json['elements'] : null; } private function elementValues($sign, $difficulty) { $count = (int) $difficulty['count']; if ($count >= 300) { $incrementalNumber = mt_rand(1, 100); } elseif ($count >= 200) { $incrementalNumber = mt_rand(1, 50); } elseif ($count >= 100) { $incrementalNumber = mt_rand(1, 20); } else { $incrementalNumber = mt_rand(1, 10); } $elements = $this->loadElements(); if ($elements === null) { return null; } $selectedElement = mt_rand(0, min((int) ($incrementalNumber * 1.18), count($elements) - 1)); $element = $elements[$selectedElement]; return [ 'answer1' => $element[$sign], 'question' => 'element ' . $element['number'] . ', missing ' . $this->labels[$sign], 'type' => 'elements', 'element' => $element, 'sign' => $sign, ]; } private function htmlify($question) { // Expected answer stays on the server; the form only carries a token. $token = TkAnswerStore::issue($this->user, $question); $frm = e107::getForm(); $tp = e107::getParser(); $sc = e107::getScBatch('timekeeper', TRUE); $fString = $frm->open('questionForm', 'post', '', ['autocomplete' => 'off', 'class' => 'formclass']); $fString .= $frm->label('

Elements

') . "
\n"; $fString .= $frm->label('Periodic Table of Elements. Fill in the missing information.') . "\n"; $fString .= '
'; $elementData = $question['element']; $elementData[$question['sign']] = '???'; $sc->setVars($elementData); $fString .= $tp->parseTemplate(file_get_contents(dirname(__DIR__, 2) . '/templates/elementTable.html'), true, $sc); $fString .= '

'; $fString .= $frm->text('a1', '', 80, ['placeholder' => 'Write answer here!', 'autocomplete' => "off", 'spellcheck' => "false"]) . "\n"; $fString .= $frm->hidden('checkerData', $token, ['id' => 'checkerData']); $fString .= $frm->hidden('subject', 'elements', ['id' => 'subject']); $fString .= '
' . "\n"; $fString .= $frm->button('Answer', 'Check answer') . "\n"; $fString .= $frm->close(); $fString .= '

'; return $fString; } }