TK2.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. <?php
  2. /**
  3. * TimeKeeper front page controller: shows the student's stats, checks a
  4. * submitted answer, and asks a question generator for the next question.
  5. *
  6. * Changes from the current repo version:
  7. * - removed adminDev(): for every Main Admin it ran on every page load,
  8. * printed debug dumps (the students' settings and stats, test numbers),
  9. * hard-coded two students' user names, set the time zone, and generated a
  10. * question from the unfinished "continents" generator
  11. * - checkAnswer() called delete(..., true): the third argument is e107's
  12. * debug flag, so the SQL was printed on the page after every answer.
  13. * It's gone, and the user name is escaped
  14. * - generators are loaded with require_once and an absolute path (include
  15. * plus ./relative paths depended on the working directory and could
  16. * redeclare a class)
  17. * - getQuestion(): generalKnowledge fell through into elements (no break);
  18. * countries/flags/capitalCities built an elements2 object after including
  19. * continents.php (class not found). Subjects that aren't built yet now
  20. * show a plain message instead
  21. * - getScores()/getLastQuestion(): MPREFIX instead of `e107.e107_...`, user
  22. * escaped, and a student with no answers this month gets zeros (not
  23. * null) for the stats
  24. * - showMessage(): user name escaped
  25. * - someone with neither the student nor the teacher class now sees a
  26. * message instead of an empty page
  27. * - division and rounding are switched on (rounding at 50,000, division at 300,000)
  28. * - generalKnowledge is a real generator now (multiple choice from tkGeneralKnowledge; still switched off)
  29. * - new flags subject (switched off: see $subjectsList)
  30. * - new fractions subject (switched off: see $subjectsList)
  31. * - new word_problems subject (switched off: see $subjectsList); the old
  32. * worded_* subjects were mapped to the spelling generator and are gone
  33. */
  34. require_once __DIR__ . '/TkAnswerStore.php';
  35. class TK2 {
  36. private $data = [];
  37. private $subjectsList = [
  38. // Math
  39. [['numbers', 1, 0], // number sequences
  40. ['addition', 1, 10000], // Numeracy adding
  41. ['subtraction', 1, 20000], // Numeracy minus'
  42. ['multiplication', 1, 200000], // Numeracy times'
  43. ['division', 1, 300000], // Numeracy deviding
  44. ['rounding', 1, 50000], // Numeracy rounding (money to the nearest 5 cents)
  45. ['word_problems', 1, 300000], // Worded maths problems: change the 0 to a 1 to switch them on
  46. ['fractions', 1, 350000], // Fractions: change the 0 to a 1 to switch them on
  47. ],
  48. // English
  49. [['spelling', 1, 0], // Plain spelling
  50. ['anagrams', 1, 4000000], // Shuffle letters in a word
  51. ],
  52. // General knowledge
  53. [['generalKnowledge', 1, 0], // Name kinda says it all
  54. ],
  55. // Science
  56. [['elements', 1, 150000], // Periodic table of elements
  57. ],
  58. // Geography
  59. [['continents', 0, 0], // Point to the continent
  60. ['countries', 0, 0], // Point to the country
  61. ['flags', 0, 120000], // Name the country by the flag picture: change the 0 to a 1 to switch it on
  62. ['capitalCities', 0, 0], // Name the capital for a country
  63. ],
  64. // History
  65. // Art
  66. [['artists', 0, 0], // Name the artist for famous paintings
  67. ],
  68. // Technology
  69. // language
  70. [['malaySpelling', 0, 0], // This could be interesting
  71. ],
  72. // Health - Health and Physical Education
  73. [['nameBodyParts', 0, 0], // Name the body parts from the images or point to the part
  74. ['nameBodyOrgans', 0, 0], // Name the organs or what they do
  75. ['basicFirstAid', 0, 0], // Some BASIC things to know for first aid
  76. ],
  77. ];
  78. /**
  79. * Run the timekeeper functionality
  80. */
  81. public function run(): void {
  82. $isStudent = check_class("TIMEKEEPER_STUDENT");
  83. $isTeacher = check_class("TIMEKEEPER_TEACHER");
  84. if ($isStudent) {
  85. $this->student();
  86. }
  87. if ($isTeacher) {
  88. $this->teacher();
  89. }
  90. if (!$isStudent && !$isTeacher) {
  91. e107::getRender()->tablerender('TimeKeeper', USER ? 'Your account has no TimeKeeper role (student or teacher).' : 'Please log in to use TimeKeeper.');
  92. }
  93. }
  94. /**
  95. * Run the sequence for teachers
  96. */
  97. private function teacher(): void {
  98. }
  99. /**
  100. * Run the sequence for students
  101. */
  102. private function student(): void {
  103. $user = e107::getUser()->getLoginName();
  104. $this->checkAnswer($user);
  105. $this->getScores($user);
  106. $this->showMessage();
  107. // If the last question has not been answered ask again.
  108. if ($this->getLastQuestion()) {
  109. return;
  110. }
  111. $this->getSubject();
  112. $this->getQuestion();
  113. }
  114. /**
  115. * Check if the last question was answered and check the answer
  116. * @param string $user
  117. */
  118. private function checkAnswer($user): void {
  119. $subject = filter_input(INPUT_POST, 'subject');
  120. if (is_string($subject) && $subject !== '') {
  121. require_once __DIR__ . '/answerChecker2.php';
  122. $ac = new answerChecker2();
  123. $ac->checkQuestionSubject($subject, $user);
  124. $db = e107::getDb();
  125. $db->delete('tkLastQuestion', "user = '" . $db->escape($user) . "'");
  126. }
  127. }
  128. /**
  129. * Show the last question asked if it hasn't been answered.
  130. * @return bool True when a question was shown.
  131. */
  132. private function getLastQuestion(): bool {
  133. $db = e107::getDb();
  134. $lastQuestion = $db->retrieve("SELECT LastQuestionHtml FROM `" . MPREFIX . "tkLastQuestion` WHERE user = '" . $db->escape($this->data['user']) . "' ORDER BY id DESC LIMIT 1");
  135. if (!empty($lastQuestion)) {
  136. e107::getRender()->tablerender('Last Question', base64_decode($lastQuestion));
  137. return true;
  138. }
  139. return false;
  140. }
  141. /**
  142. * Get the question based on selected subject
  143. * @return void
  144. */
  145. private function getQuestion(): void {
  146. switch ($this->data['selectedSubject']) {
  147. case 'numbers':
  148. require_once __DIR__ . '/questionGenerators/math/numbers2.php';
  149. $questionGen = new numbers2($this->data['user']);
  150. $questionGen->getNumbers();
  151. break;
  152. case 'addition':
  153. case 'subtraction':
  154. case 'multiplication':
  155. case 'division':
  156. case 'rounding':
  157. require_once __DIR__ . '/questionGenerators/math/numeracy2.php';
  158. $questionGen = new numeracy2($this->data['user'], $this->data['selectedSubject']);
  159. $questionGen->getNumeracy();
  160. break;
  161. case 'fractions':
  162. require_once __DIR__ . '/questionGenerators/math/fractions.php';
  163. $questionGen = new fractions($this->data['user']);
  164. $questionGen->getFractions();
  165. break;
  166. case 'word_problems':
  167. require_once __DIR__ . '/questionGenerators/math/math_word_problems.php';
  168. $questionGen = new math_word_problems($this->data['user']);
  169. $questionGen->getWordProblem();
  170. break;
  171. case 'spelling':
  172. require_once __DIR__ . '/questionGenerators/english/spelling2.php';
  173. $questionGen = new spelling2($this->data['user']);
  174. $questionGen->getSpellingWord();
  175. break;
  176. case 'anagrams':
  177. require_once __DIR__ . '/questionGenerators/english/anagrams.php';
  178. $questionGen = new anagrams($this->data['user']);
  179. $questionGen->getanagram();
  180. break;
  181. case 'elements':
  182. require_once __DIR__ . '/questionGenerators/science/elements2.php';
  183. $questionGen = new elements2($this->data['user']);
  184. $questionGen->getElements();
  185. break;
  186. case 'flags':
  187. require_once __DIR__ . '/questionGenerators/geography/flags.php';
  188. $questionGen = new flags($this->data['user']);
  189. $questionGen->getFlag();
  190. break;
  191. case 'generalKnowledge':
  192. require_once __DIR__ . '/questionGenerators/general/generalKnowledge.php';
  193. $questionGen = new generalKnowledge($this->data['user']);
  194. $questionGen->getGeneralKnowledge();
  195. break;
  196. case 'continents':
  197. case 'countries':
  198. case 'capitalCities':
  199. case 'artists':
  200. case 'malaySpelling':
  201. case 'nameBodyParts':
  202. case 'nameBodyOrgans':
  203. case 'basicFirstAid':
  204. default:
  205. // Not built yet (or unknown): none of these are in the random pool.
  206. e107::getRender()->tablerender('TimeKeeper', 'This subject is not available yet. Please refresh the page.');
  207. break;
  208. }
  209. }
  210. /**
  211. * Get the list of available subjects and select a random one.
  212. * @return void
  213. */
  214. private function getSubject(): void {
  215. $usableSubjects = [];
  216. foreach ($this->subjectsList as $subjects) {
  217. foreach ($subjects as $subject) {
  218. if ($subject[1] === 1 && $this->data['difficulty'] >= $subject[2]) {
  219. $usableSubjects[] = $subject[0];
  220. }
  221. }
  222. }
  223. if (empty($usableSubjects)) {
  224. $usableSubjects[] = 'numbers';
  225. }
  226. $this->data['selectedSubject'] = $usableSubjects[mt_rand(0, count($usableSubjects) - 1)];
  227. }
  228. /**
  229. * Show an initial stats message to the user.
  230. * @return void
  231. */
  232. private function showMessage(): void {
  233. $text = "You have answered " . (int) $this->data['cntr'] . " questions this month.<br/>"
  234. . "Your accuacy for the month is " . htmlspecialchars((string) $this->data['percent']) . "%.<br/>"
  235. . "Your difficulty is set at " . htmlspecialchars((string) $this->data['difficulty']) . ".<br/><br/>";
  236. e107::getRender()->tablerender('Your stats ' . htmlspecialchars(ucfirst($this->data['user'])), $text);
  237. }
  238. /**
  239. * Get the initial values needed to generate a sane question.
  240. * @param string $user
  241. * @return void
  242. */
  243. private function getScores($user): void {
  244. $db = e107::getDb();
  245. $u = $db->escape($user);
  246. $returnArray = [];
  247. $returnArray['totalQuestionCount'] = (int) $db->count('tkReport', '(id)', "username = '{$u}'");
  248. $db->gen("SELECT COUNT(id) AS cntr, CAST((SUM(correct)/COUNT(id)*100) AS DECIMAL(5,2)) AS percent
  249. FROM `" . MPREFIX . "tkReport` WHERE username = '{$u}'
  250. AND answereddatestamp >= UNIX_TIMESTAMP(CURRENT_DATE() - INTERVAL 1 MONTH)
  251. AND answereddatestamp <= UNIX_TIMESTAMP(CURRENT_DATE() + INTERVAL 1 DAY)");
  252. $row = $db->fetch();
  253. $returnArray['cntr'] = is_array($row) ? (int) $row['cntr'] : 0;
  254. $returnArray['percent'] = (is_array($row) && $row['percent'] !== null) ? (float) $row['percent'] : 0;
  255. $returnArray['user'] = $user;
  256. $returnArray['difficulty'] = $returnArray['totalQuestionCount'] * $returnArray['percent'];
  257. $this->data = $returnArray;
  258. }
  259. }