|
@@ -1,117 +1,332 @@
|
|
|
<?php
|
|
<?php
|
|
|
|
|
|
|
|
-/*
|
|
|
|
|
- * To change this license header, choose License Headers in Project Properties.
|
|
|
|
|
- * To change this template file, choose Tools | Templates
|
|
|
|
|
- * and open the template in the editor.
|
|
|
|
|
- */
|
|
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Description of numbers
|
|
|
|
|
|
|
+ * General knowledge ("generalKnowledge"): a multiple choice question from the
|
|
|
|
|
+ * tkGeneralKnowledge table.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Replaces the placeholder, which was a copy of the "find the missing number"
|
|
|
|
|
+ * generator under another class name: it never read tkGeneralKnowledge and
|
|
|
|
|
+ * still used the old client-side checkerData, so every answer would have
|
|
|
|
|
+ * been refused.
|
|
|
*
|
|
*
|
|
|
- * @author gregory
|
|
|
|
|
|
|
+ * - Works on the table as imported from general_knowledge_10000_mcq.csv, and
|
|
|
|
|
+ * on the cleaned table (general_knowledge/tkGeneralKnowledge.sql). That file
|
|
|
|
|
+ * holds 1,018 questions, each copied about ten times with
|
|
|
|
|
+ * "(Choose the correct answer.)" added to the question (and the question
|
|
|
|
|
+ * mark dropped). So the question text is cleaned when it is read, and rows
|
|
|
|
|
+ * with the same cleaned question count as one.
|
|
|
|
|
+ * - The columns are found by name, ignoring case, spaces and underscores:
|
|
|
|
|
+ * Question, Option A..D (option_a, optionA ...), Correct Answer
|
|
|
|
|
+ * (correct_answer ...), Correct Option, Category. The correct answer is
|
|
|
|
|
+ * taken from the answer text, or the letter, or both (a row where they
|
|
|
|
|
+ * disagree is never asked).
|
|
|
|
|
+ * - The options are shuffled for each question.
|
|
|
|
|
+ * - A question isn't repeated within the student's last 200 answers.
|
|
|
|
|
+ * - A category is picked first, with big categories counted for less than
|
|
|
|
|
+ * their size (square root), so Geography (three quarters of the file) is
|
|
|
|
|
+ * not nearly everything.
|
|
|
|
|
+ * - The Difficulty column is not used: in the file it just repeats Easy,
|
|
|
|
|
+ * Medium, Hard by row.
|
|
|
|
|
+ *
|
|
|
|
|
+ * The right answer and the options as shown are stored server-side
|
|
|
|
|
+ * (TkAnswerStore). The student ticks a letter; the checker
|
|
|
|
|
+ * (answerChecker2::checkGeneralKnowledge) turns it back into the option text.
|
|
|
*/
|
|
*/
|
|
|
class generalKnowledge {
|
|
class generalKnowledge {
|
|
|
|
|
|
|
|
|
|
+ const SUBJECT = 'generalKnowledge';
|
|
|
|
|
+ const TABLE = 'tkGeneralKnowledge';
|
|
|
|
|
+ const RECENT = 200; // questions not repeated within this many of a student's answers
|
|
|
|
|
+ const SAMPLE = 40; // random rows looked at to find a question
|
|
|
|
|
+
|
|
|
private $user;
|
|
private $user;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Setup new numbers engine
|
|
|
|
|
- *
|
|
|
|
|
- * @param bool $test Enable debug mode.
|
|
|
|
|
|
|
+ * @param string $user User to supply the question for.
|
|
|
*/
|
|
*/
|
|
|
public function __construct($user) {
|
|
public function __construct($user) {
|
|
|
-
|
|
|
|
|
$this->user = $user;
|
|
$this->user = $user;
|
|
|
-
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public function getgeneralKnowledge() {
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Make a question, show it, and remember it as the student's open question.
|
|
|
|
|
+ */
|
|
|
|
|
+ public function getGeneralKnowledge() {
|
|
|
|
|
+ $problem = $this->getProblem();
|
|
|
|
|
+
|
|
|
|
|
+ if ($problem === null) {
|
|
|
|
|
+ e107::getRender()->tablerender('General knowledge', 'There are no general knowledge questions available yet. Please refresh the page.');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $questionHtml = $this->htmlify($problem);
|
|
|
|
|
+
|
|
|
|
|
+ $db = e107::getDb();
|
|
|
|
|
+ $db->delete('tkLastQuestion', "user = '" . $db->escape($this->user) . "'");
|
|
|
|
|
+ $db->insert('tkLastQuestion', ['user' => $this->user, 'LastQuestionHtml' => base64_encode($questionHtml)]);
|
|
|
|
|
|
|
|
- $difficulty = $this->difficulty();
|
|
|
|
|
- $question = $this->getSequence($difficulty);
|
|
|
|
|
- $questionHtml = $this->htmlify($question);
|
|
|
|
|
-
|
|
|
|
|
- e107::getDb()->insert('tkLastQuestion', ['user'=>$this->user,'LastQuestionHtml'=>base64_encode($questionHtml)]);
|
|
|
|
|
-
|
|
|
|
|
- e107::getRender()->tablerender('General Knowledge', $questionHtml);
|
|
|
|
|
|
|
+ e107::getRender()->tablerender('General knowledge', $questionHtml);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Return the difficulty based on previously answered questions.
|
|
|
|
|
|
|
+ * Choose a question without showing it.
|
|
|
*
|
|
*
|
|
|
- * @return int Difficulty as percent.
|
|
|
|
|
|
|
+ * @return array|null ['answer1' => correct option text, 'question' => string, 'type' => 'generalKnowledge',
|
|
|
|
|
+ * 'options' => the four options in the order shown, 'category' => string],
|
|
|
|
|
+ * or null when no question can be asked.
|
|
|
|
|
+ */
|
|
|
|
|
+ public function getProblem() {
|
|
|
|
|
+ $columns = $this->columns();
|
|
|
|
|
+ if ($columns === null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $recent = $this->recentQuestions();
|
|
|
|
|
+ $category = $this->pickCategory($columns);
|
|
|
|
|
+
|
|
|
|
|
+ $problem = $this->pickQuestion($columns, $category, $recent);
|
|
|
|
|
+ if ($problem === null && $category !== null) {
|
|
|
|
|
+ $problem = $this->pickQuestion($columns, null, $recent); // that category had nothing new
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($problem === null) {
|
|
|
|
|
+ $problem = $this->pickQuestion($columns, null, []); // everything was asked recently: repeat
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $problem;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The real column names, found by name: ['question', 'a', 'b', 'c', 'd', and 'answer' and/or 'letter',
|
|
|
|
|
+ * optionally 'category'], or null when the table isn't usable.
|
|
|
|
|
+ */
|
|
|
|
|
+ private function columns() {
|
|
|
|
|
+ $db = e107::getDb();
|
|
|
|
|
+ $db->gen("SHOW COLUMNS FROM `" . MPREFIX . self::TABLE . "`");
|
|
|
|
|
+
|
|
|
|
|
+ $found = [];
|
|
|
|
|
+ while ($row = $db->fetch()) {
|
|
|
|
|
+ $found[strtolower(preg_replace('/[^a-z0-9]/i', '', $row['Field']))] = $row['Field'];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $aliases = [
|
|
|
|
|
+ 'question' => ['question', 'questiontext'],
|
|
|
|
|
+ 'a' => ['optiona', 'choicea', 'answera', 'a'],
|
|
|
|
|
+ 'b' => ['optionb', 'choiceb', 'answerb', 'b'],
|
|
|
|
|
+ 'c' => ['optionc', 'choicec', 'answerc', 'c'],
|
|
|
|
|
+ 'd' => ['optiond', 'choiced', 'answerd', 'd'],
|
|
|
|
|
+ 'answer' => ['correctanswer', 'answertext', 'rightanswer'],
|
|
|
|
|
+ 'letter' => ['correctoption', 'correctletter', 'correctchoice'],
|
|
|
|
|
+ 'category' => ['category', 'topic', 'subject'],
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ $map = [];
|
|
|
|
|
+ foreach ($aliases as $name => $names) {
|
|
|
|
|
+ foreach ($names as $alias) {
|
|
|
|
|
+ if (isset($found[$alias])) {
|
|
|
|
|
+ $map[$name] = $found[$alias];
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ foreach (['question', 'a', 'b', 'c', 'd'] as $required) {
|
|
|
|
|
+ if (!isset($map[$required])) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!isset($map['answer']) && !isset($map['letter'])) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $map;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Keys of the questions in the student's last answers (cleaned text, lower case).
|
|
|
|
|
+ * The report stores the question as "General knowledge: <question>".
|
|
|
|
|
+ */
|
|
|
|
|
+ private function recentQuestions() {
|
|
|
|
|
+ $db = e107::getDb();
|
|
|
|
|
+ $recent = [];
|
|
|
|
|
+ $db->gen("SELECT `question` FROM `" . MPREFIX . "tkReport`
|
|
|
|
|
+ WHERE `username` = '" . $db->escape($this->user) . "' AND `subject` = '" . self::SUBJECT . "'
|
|
|
|
|
+ ORDER BY `id` DESC LIMIT " . (int) self::RECENT);
|
|
|
|
|
+ while ($row = $db->fetch()) {
|
|
|
|
|
+ $recent[$this->key(preg_replace('/^General knowledge:\s*/', '', (string) $row['question']))] = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return $recent;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * A category chosen at random, each weighted by the square root of how many
|
|
|
|
|
+ * rows it has. Null when there is no category column.
|
|
|
|
|
+ */
|
|
|
|
|
+ private function pickCategory(array $columns) {
|
|
|
|
|
+ if (!isset($columns['category'])) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $db = e107::getDb();
|
|
|
|
|
+ $field = '`' . $columns['category'] . '`';
|
|
|
|
|
+ $db->gen("SELECT " . $field . " AS category, COUNT(*) AS n FROM `" . MPREFIX . self::TABLE . "` GROUP BY " . $field);
|
|
|
|
|
+
|
|
|
|
|
+ $weights = [];
|
|
|
|
|
+ $total = 0.0;
|
|
|
|
|
+ while ($row = $db->fetch()) {
|
|
|
|
|
+ $weight = sqrt((int) $row['n']);
|
|
|
|
|
+ if ($weight > 0) {
|
|
|
|
|
+ $weights[$row['category']] = $weight;
|
|
|
|
|
+ $total += $weight;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($total <= 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $point = (mt_rand() / mt_getrandmax()) * $total;
|
|
|
|
|
+ foreach ($weights as $category => $weight) {
|
|
|
|
|
+ $point -= $weight;
|
|
|
|
|
+ if ($point <= 0) {
|
|
|
|
|
+ return (string) $category;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return (string) key($weights);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The first usable question, not asked recently, among SAMPLE random rows.
|
|
|
*/
|
|
*/
|
|
|
- private function difficulty() {
|
|
|
|
|
-
|
|
|
|
|
- $stats = e107::getDb()->retrieve("SELECT (SUM(correct)/COUNT(correct)*100) as percent,COUNT(correct) as count
|
|
|
|
|
- FROM e107.`e107_tkReport` WHERE
|
|
|
|
|
- answereddatestamp >= unix_timestamp((NOW()) - INTERVAL 1 MONTH)
|
|
|
|
|
- AND username = '$this->user' AND subject = 'generalKnowledge';");
|
|
|
|
|
-
|
|
|
|
|
- if ($stats["count"]==0) {
|
|
|
|
|
- return 1;
|
|
|
|
|
- } else {
|
|
|
|
|
- return $stats;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
|
|
+ private function pickQuestion(array $columns, $category, array $recent) {
|
|
|
|
|
+ $db = e107::getDb();
|
|
|
|
|
+
|
|
|
|
|
+ $where = '';
|
|
|
|
|
+ if ($category !== null && isset($columns['category'])) {
|
|
|
|
|
+ $where = " WHERE `" . $columns['category'] . "` = '" . $db->escape($category) . "'";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $db->gen("SELECT * FROM `" . MPREFIX . self::TABLE . "`" . $where . " ORDER BY RAND() LIMIT " . (int) self::SAMPLE);
|
|
|
|
|
+ $rows = [];
|
|
|
|
|
+ while ($row = $db->fetch()) {
|
|
|
|
|
+ $rows[] = $row;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($rows as $row) {
|
|
|
|
|
+ $problem = $this->buildProblem($columns, $row);
|
|
|
|
|
+ if ($problem !== null && !isset($recent[$this->key($problem['question'])])) {
|
|
|
|
|
+ return $problem;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- private function getSequence($difficulty) {
|
|
|
|
|
-
|
|
|
|
|
- //print_a(['slide'=>($difficulty['percent']/100)*2,'diff'=>$difficulty,'pow'=>(int)pow($difficulty['count'],($difficulty['percent']/100)*2)]);
|
|
|
|
|
-
|
|
|
|
|
- $startingNumber = mt_rand(0, (int) pow($difficulty['count'],($difficulty['percent']/100)*2));
|
|
|
|
|
- if($difficulty['count']>=300) {
|
|
|
|
|
- $incrementalNumber = mt_rand(1, 100 );
|
|
|
|
|
- }elseif($difficulty['count']>=200){
|
|
|
|
|
- $incrementalNumber = mt_rand(1, 10 );
|
|
|
|
|
- }elseif($difficulty['count']>=100){
|
|
|
|
|
- $incrementalNumber = mt_rand(1, 5 );
|
|
|
|
|
- }else{
|
|
|
|
|
- $incrementalNumber = mt_rand(1, 2);
|
|
|
|
|
- }
|
|
|
|
|
- $missingNumber = mt_rand(1, 10);
|
|
|
|
|
- $TempQuestion = [];
|
|
|
|
|
-
|
|
|
|
|
- for ($i = 1; $i <= 10; $i++) {
|
|
|
|
|
- if ($i == $missingNumber) {
|
|
|
|
|
- $TempQuestion[] = "___,";
|
|
|
|
|
- $solution = $startingNumber;
|
|
|
|
|
- } elseif ($i == 10) {
|
|
|
|
|
- $TempQuestion[] = $startingNumber;
|
|
|
|
|
- } else {
|
|
|
|
|
- $TempQuestion[] = $startingNumber . ",";
|
|
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * One table row as a question, or null when the row can't be used (missing
|
|
|
|
|
+ * text, repeated options, no single correct option, or the answer text and
|
|
|
|
|
+ * the letter disagree).
|
|
|
|
|
+ */
|
|
|
|
|
+ private function buildProblem(array $columns, array $row) {
|
|
|
|
|
+ $question = $this->cleanQuestion($row[$columns['question']]);
|
|
|
|
|
+ $options = [
|
|
|
|
|
+ $this->decode($row[$columns['a']]),
|
|
|
|
|
+ $this->decode($row[$columns['b']]),
|
|
|
|
|
+ $this->decode($row[$columns['c']]),
|
|
|
|
|
+ $this->decode($row[$columns['d']]),
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ if ($question === '' || in_array('', $options, true) || count(array_unique($options)) < 4) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $correct = null;
|
|
|
|
|
+ if (isset($columns['answer'])) {
|
|
|
|
|
+ $text = $this->decode($row[$columns['answer']]);
|
|
|
|
|
+ if (count(array_keys($options, $text, true)) === 1) {
|
|
|
|
|
+ $correct = $text;
|
|
|
}
|
|
}
|
|
|
- $startingNumber += $incrementalNumber;
|
|
|
|
|
}
|
|
}
|
|
|
- return [ 'answer1'=>$solution, 'question'=>$TempQuestion, 'type'=>'generalKnowledge'];
|
|
|
|
|
|
|
+ if (isset($columns['letter'])) {
|
|
|
|
|
+ $letter = strtoupper(trim((string) $row[$columns['letter']]));
|
|
|
|
|
+ $byLetter = (strlen($letter) === 1 && $letter >= 'A' && $letter <= 'D') ? $options[ord($letter) - 65] : null;
|
|
|
|
|
+ if ($correct === null) {
|
|
|
|
|
+ $correct = $byLetter;
|
|
|
|
|
+ } elseif ($byLetter !== null && $byLetter !== $correct) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($correct === null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ shuffle($options);
|
|
|
|
|
+
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'answer1' => $correct,
|
|
|
|
|
+ 'question' => $question,
|
|
|
|
|
+ 'type' => self::SUBJECT,
|
|
|
|
|
+ 'options' => $options,
|
|
|
|
|
+ 'category' => isset($columns['category']) ? $this->decode($row[$columns['category']]) : '',
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * e107 may have stored apostrophes as ': show and compare the real text.
|
|
|
|
|
+ */
|
|
|
|
|
+ private function decode($value) {
|
|
|
|
|
+ return trim(html_entity_decode((string) $value, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The question as it should be shown: decoded, without the
|
|
|
|
|
+ * "(Choose the correct answer.)" padding of the imported file (which also
|
|
|
|
|
+ * dropped the question mark: it is put back).
|
|
|
|
|
+ */
|
|
|
|
|
+ private function cleanQuestion($text) {
|
|
|
|
|
+ $text = $this->decode($text);
|
|
|
|
|
+ $stripped = preg_replace('/(?:\s*\(Choose the correct answer\.\))+\s*$/u', '', $text, -1, $count);
|
|
|
|
|
+ if ($stripped === null) {
|
|
|
|
|
+ return $text;
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($count > 0) {
|
|
|
|
|
+ $stripped = rtrim(trim($stripped), '?') . '?';
|
|
|
|
|
+ }
|
|
|
|
|
+ return trim($stripped);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private function htmlify($question) {
|
|
|
|
|
-
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * What makes two questions "the same": the cleaned text, lower case, no final punctuation.
|
|
|
|
|
+ */
|
|
|
|
|
+ private function key($question) {
|
|
|
|
|
+ return mb_strtolower(rtrim(trim((string) $question), "?. \t"), 'UTF-8');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function htmlify($problem) {
|
|
|
|
|
+ // The right answer and the order of the options stay on the server; the form only carries a token.
|
|
|
|
|
+ $token = TkAnswerStore::issue($this->user, $problem);
|
|
|
|
|
+
|
|
|
$frm = e107::getForm();
|
|
$frm = e107::getForm();
|
|
|
|
|
|
|
|
$fString = $frm->open('questionForm', 'post', '', ['autocomplete' => 'off', 'class' => 'formclass']);
|
|
$fString = $frm->open('questionForm', 'post', '', ['autocomplete' => 'off', 'class' => 'formclass']);
|
|
|
- $fString .= $frm->label('<h4>General Knowledge</h4>') . "</br>\n";
|
|
|
|
|
- $fString .= $frm->label('Find the missing number.') . "\n";
|
|
|
|
|
- $fString .= '</br>';
|
|
|
|
|
-
|
|
|
|
|
- foreach ($question['question'] as $seq) {
|
|
|
|
|
- $fString .= $frm->label($seq . " ") . "\n";
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $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('subject', 'generalKnowledge', ['id' => 'subject']);
|
|
|
|
|
|
|
+ $fString .= $frm->label('<h4>General knowledge</h4>') . "</br>\n";
|
|
|
|
|
+ if ($problem['category'] !== '') {
|
|
|
|
|
+ $fString .= '<p class="smalltext">Category: ' . htmlspecialchars($problem['category']) . '</p>';
|
|
|
|
|
+ }
|
|
|
|
|
+ $fString .= '<p style="font-size:140%;font-weight:bold;">' . htmlspecialchars($problem['question']) . '</p>';
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($problem['options'] as $i => $option) {
|
|
|
|
|
+ $letter = chr(65 + $i);
|
|
|
|
|
+ $fString .= '<div style="margin:8px 0;"><label style="font-size:120%;cursor:pointer;">'
|
|
|
|
|
+ . '<input type="radio" name="a1" value="' . $letter . '" required> '
|
|
|
|
|
+ . '<b>' . $letter . '.</b> ' . htmlspecialchars($option)
|
|
|
|
|
+ . '</label></div>';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $fString .= $frm->hidden('checkerData', $token, ['id' => 'checkerData']);
|
|
|
|
|
+ $fString .= $frm->hidden('subject', self::SUBJECT, ['id' => 'subject']);
|
|
|
$fString .= '</br>' . "\n";
|
|
$fString .= '</br>' . "\n";
|
|
|
- $fString .= e107::getForm()->button('Answer', 'Check answer') . "\n";
|
|
|
|
|
- $fString .= e107::getForm()->close();
|
|
|
|
|
|
|
+ $fString .= $frm->button('Answer', 'Check answer') . "\n";
|
|
|
|
|
+ $fString .= $frm->close();
|
|
|
$fString .= '</br></br>';
|
|
$fString .= '</br></br>';
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return $fString;
|
|
return $fString;
|
|
|
-
|
|
|
|
|
}
|
|
}
|
|
|
-}
|
|
|
|
|
|
|
+}
|