#tkReport, the three nested * query loops are one GROUP BY query per user, and "next word" no longer * prints blanks (or warns) when every word already has a usage * - removed the leftover rss_url*() methods copied from the RSS plugin * - new "Student Limits" screen: edit each student's row in the `timekeeper` table * (play window, free minutes, questions needed, daily maximum) and add a student, * instead of editing the table in phpMyAdmin */ require_once("../../class2.php"); if (!getperms("P") || !e107::isInstalled('timekeeper')) { e107::redirect('admin'); exit; } // Maybe later i'll setup languages //e107::includeLan(e_PLUGIN."timekeeper/languages/".e_LANGUAGE."_admin_timekeeper_menu.php"); class timekeeper_admin extends e_admin_dispatcher { protected $modes = array( 'main' => array( 'controller' => 'plugin_tk_admin_ui', 'path' => null, 'ui' => 'plugin_tk_admin_form_ui', 'uipath' => null ), 'score' => array( 'controller' => 'tk_Score_ui', 'path' => null, 'ui' => 'tk_Score_form_ui', 'uipath' => null ), 'pending' => array( 'controller' => 'tk_Pending_ui', 'path' => null, 'ui' => 'tk_Pending_form_ui', 'uipath' => null ), 'report' => array( 'controller' => 'tk_Report_ui', 'path' => null, 'ui' => 'tk_Report_form_ui', 'uipath' => null ), 'reportold' => array( 'controller' => 'tk_Reportold_ui', 'path' => null, 'ui' => 'tk_Reportold_form_ui', 'uipath' => null ), 'wordlist' => array( 'controller' => 'tk_Wordlist_ui', 'path' => null, 'ui' => 'tk_Wordlist_form_ui', 'uipath' => null ), 'geography' => array( 'controller' => 'tk_geography_ui', 'path' => null, 'ui' => 'tk_geography_form_ui', 'uipath' => null ), 'students' => array( 'controller' => 'tk_Students_ui', 'path' => null, 'ui' => 'tk_Students_form_ui', 'uipath' => null ), ); protected $adminMenu = array( 'main/list' => array('caption' => 'List Questions', 'perm' => '0'), 'main/create' => array('caption' => LAN_CREATE, 'perm' => '0'), 'main/prefs' => array('caption' => LAN_PREFS, 'perm' => '0'), 'Students/custom' => array('caption' => 'Student Limits', 'perm' => '0'), 'other0' => array('divider' => true), //'subjects/list' => array('caption'=> 'List Subjects', 'perm' => '0'), //'subjects/create' => array('caption'=> 'Add Subjects', 'perm' => '0'), //'other1' => array('divider' => true), 'Score/list' => array('caption' => 'List Score', 'perm' => '0'), 'other2' => array('divider' => true), 'Pending/list' => array('caption' => 'List Pending', 'perm' => '0'), 'other3' => array('divider' => true), 'Report/list' => array('caption' => 'List Report', 'perm' => '0'), 'Reportold/list' => array('caption' => 'List Report (Old)', 'perm' => '0'), 'other4' => array('divider' => true), 'Wordlist/list' => array('caption' => 'Spelling Words', 'perm' => '0'), 'Wordlist/create' => array('caption' => 'Add Words', 'perm' => '0'), 'Wordlist/custom' => array('caption' => 'Spelling Score', 'perm' => '0'), 'other5' => array('divider' => true), 'Geography/list' => array('caption' => 'Geography', 'perm' => '0'), ); protected $adminMenuAliases = array( 'main/edit' => 'main/list' ); protected $menuTitle = 'TimeKeeper'; } /* ========================================================================= ===== Questions table interface (and plugin preferences) ========================================================================= */ class plugin_tk_admin_ui extends e_admin_ui { protected $pluginTitle = 'TimeKeeper'; protected $pluginName = 'timekeeper'; protected $table = 'timekeeperQuestions'; protected $pid = 'timekeeper_id'; protected $perPage = 10; protected $fields = array( 'checkboxes' => array('title' => '', 'type' => null, 'data' => false, 'width' => '5%', 'thclass' => 'center', 'forced' => '1', 'class' => 'center', 'toggle' => 'e-multiselect',), 'timekeeper_id' => array('title' => LAN_ID, 'data' => 'int', 'width' => '5%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_questiondescription' => array('title' => LAN_TITLE, 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_question' => array('title' => "Question", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_answers' => array('title' => "Answers", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_subject' => array('title' => "Subject", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_addeddatestamp' => array('title' => LAN_DATESTAMP, 'type' => 'datestamp', 'data' => 'int', 'readonly' => true, 'width' => 'auto', 'filter' => true, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_yearlevel' => array('title' => "Year Level", 'type' => 'dropdown', 'data' => 'int', 'inline' => true, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => array('optArray' => array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)), 'class' => 'left', 'thclass' => 'left',), 'timekeeper_correctanswer' => array('title' => "Correct Answer", 'type' => 'number', 'data' => 'int', 'inline' => true, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'options' => array('title' => LAN_OPTIONS, 'type' => null, 'data' => '', 'width' => '10%', 'thclass' => 'center last', 'class' => 'center last', 'forced' => '1',), ); protected $fieldpref = array('checkboxes', 'timekeeper_questiondescription', 'timekeeper_question', 'timekeeper_answers', 'timekeeper_correctanswer', 'timekeeper_yearlevel', 'options'); // Plugin preferences (Preferences page). Read elsewhere with // e107::getPlugPref('timekeeper', 'stats_key', ''). protected $prefs = array( 'stats_key' => array( 'title' => 'Stats / summary key', 'type' => 'text', 'data' => 'str', 'help' => 'Shared secret for tk_stats_page.php and timekeeper.php?mode=short: add &key= to the URL. Leave empty for no key (tk_stats_page.php is then open to anyone).', 'writeParms' => array('size' => 'xxlarge'), ), ); } class plugin_tk_admin_form_ui extends e_admin_form_ui { } /* ========================================================================= ===== Score table interface ========================================================================= */ class tk_Score_ui extends e_admin_ui { protected $pluginTitle = 'TimeKeeper'; protected $pluginName = 'timekeeper'; protected $table = 'timekeeperScore'; protected $pid = 'timekeeper_id'; protected $perPage = 10; protected $fields = array( 'checkboxes' => array('title' => '', 'type' => null, 'data' => false, 'width' => '5%', 'thclass' => 'center', 'forced' => '1', 'class' => 'center', 'toggle' => 'e-multiselect',), 'timekeeper_id' => array('title' => LAN_ID, 'data' => 'int', 'width' => '5%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_username' => array('title' => "User Name", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_lifetimequestions' => array('title' => "life time Question", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_lifetimecorrect' => array('title' => "life time correct", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_minutes' => array('title' => "minutes", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_lastquestiondatestamp' => array('title' => LAN_DATESTAMP, 'type' => 'datestamp', 'data' => 'int', 'readonly' => true, 'width' => 'auto', 'filter' => true, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_yearlevel' => array('title' => "Year Level", 'type' => 'dropdown', 'data' => 'int', 'inline' => true, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => array('optArray' => array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)), 'class' => 'left', 'thclass' => 'left',), 'options' => array('title' => LAN_OPTIONS, 'type' => null, 'data' => '', 'width' => '10%', 'thclass' => 'center last', 'class' => 'center last', 'forced' => '1',), ); protected $fieldpref = array('checkboxes', 'timekeeper_username', 'timekeeper_lifetimequestions', 'timekeeper_lifetimecorrect', 'timekeeper_minutes', 'options'); } class tk_Score_form_ui extends e_admin_form_ui { } /* ========================================================================= ===== Pending table interface ========================================================================= */ class tk_Pending_ui extends e_admin_ui { protected $pluginTitle = 'TimeKeeper'; protected $pluginName = 'timekeeper'; protected $table = 'timekeeperPending'; protected $pid = 'timekeeper_id'; protected $perPage = 10; protected $fields = array( 'checkboxes' => array('title' => '', 'type' => null, 'data' => false, 'width' => '5%', 'thclass' => 'center', 'forced' => '1', 'class' => 'center', 'toggle' => 'e-multiselect',), 'timekeeper_id' => array('title' => LAN_ID, 'data' => 'int', 'width' => '5%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_username' => array('title' => "User Name", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_question' => array('title' => "Question", 'type' => 'number', 'data' => 'int', 'readonly' => 1, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_answereddatestamp' => array('title' => LAN_DATESTAMP, 'type' => 'datestamp', 'data' => 'int', 'readonly' => true, 'width' => 'auto', 'filter' => true, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_answer' => array('title' => "Answer", 'type' => 'textarea', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'options' => array('title' => LAN_OPTIONS, 'type' => null, 'data' => '', 'width' => '10%', 'thclass' => 'center last', 'class' => 'center last', 'forced' => '1',), ); protected $fieldpref = array('checkboxes', 'timekeeper_username', 'timekeeper_question', 'timekeeper_answereddatestamp', 'timekeeper_answer', 'options'); } class tk_Pending_form_ui extends e_admin_form_ui { } /* ========================================================================= ===== Report table interface ========================================================================= */ class tk_Report_ui extends e_admin_ui { protected $pluginTitle = 'TimeKeeper'; protected $pluginName = 'timekeeper'; protected $table = 'tkReport'; protected $pid = 'id'; protected $perPage = 10; protected $fields = array( 'checkboxes' => array('title' => '', 'type' => null, 'data' => false, 'width' => '5%', 'thclass' => 'center', 'forced' => '1', 'class' => 'center', 'toggle' => 'e-multiselect',), 'id' => array('title' => LAN_ID, 'data' => 'int', 'width' => '5%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'username' => array('title' => "User Name", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => 'auto', 'filter' => true, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'question' => array('title' => "Question", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'userAnswer' => array('title' => "User Answer", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'answer' => array('title' => "Correct Answer", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'subject' => array('title' => "Subject", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'answereddatestamp' => array('title' => LAN_DATESTAMP, 'type' => 'datestamp', 'data' => 'int', 'readonly' => true, 'width' => 'auto', 'filter' => true, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'correct' => array('title' => "Correct", 'type' => 'bool', 'data' => 'int', 'inline' => true, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'options' => array('title' => LAN_OPTIONS, 'type' => null, 'data' => '', 'width' => '10%', 'thclass' => 'center last', 'class' => 'center last', 'forced' => '1',), ); protected $fieldpref = array('checkboxes', 'username', 'question', 'userAnswer', 'answer', 'options'); } class tk_Report_form_ui extends e_admin_form_ui { } class tk_Reportold_ui extends e_admin_ui { protected $pluginTitle = 'TimeKeeper'; protected $pluginName = 'timekeeper'; protected $table = 'timekeeperReport'; protected $pid = 'timekeeper_id'; protected $perPage = 10; protected $fields = array( 'checkboxes' => array('title' => '', 'type' => null, 'data' => false, 'width' => '5%', 'thclass' => 'center', 'forced' => '1', 'class' => 'center', 'toggle' => 'e-multiselect',), 'timekeeper_id' => array('title' => LAN_ID, 'data' => 'int', 'width' => '5%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_username' => array('title' => "User Name", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => 'auto', 'filter' => true, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_question' => array('title' => "Question", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_userAnswer' => array('title' => "User Answer", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_answer' => array('title' => "Correct Answer", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_subject' => array('title' => "Subject", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_answereddatestamp' => array('title' => LAN_DATESTAMP, 'type' => 'datestamp', 'data' => 'int', 'readonly' => true, 'width' => 'auto', 'filter' => true, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_yearLevel' => array('title' => "Year Level", 'type' => 'number', 'data' => 'int', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'timekeeper_correct' => array('title' => "Correct", 'type' => 'bool', 'data' => 'int', 'inline' => true, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'options' => array('title' => LAN_OPTIONS, 'type' => null, 'data' => '', 'width' => '10%', 'thclass' => 'center last', 'class' => 'center last', 'forced' => '1',), ); protected $fieldpref = array('checkboxes', 'timekeeper_username', 'timekeeper_question', 'timekeeper_userAnswer', 'timekeeper_answer', 'timekeeper_yearLevel', 'options'); } class tk_Reportold_form_ui extends e_admin_form_ui { } /* ========================================================================= ===== Wordlist table interface ========================================================================= */ class tk_Wordlist_ui extends e_admin_ui { protected $pluginTitle = 'TimeKeeper'; protected $pluginName = 'timekeeper'; protected $table = 'tk_wordList'; protected $pid = 'indexNo'; protected $perPage = 10; protected $fields = array( 'checkboxes' => array('title' => '', 'type' => null, 'data' => false, 'width' => '5%', 'thclass' => 'center', 'forced' => '1', 'class' => 'center', 'toggle' => 'e-multiselect',), 'indexNo' => array('title' => LAN_ID, 'data' => 'int', 'width' => '5%', 'readonly' => 1, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'word' => array('title' => "Word", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => '10%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'usage' => array('title' => "Word Usage", 'type' => 'textarea', 'data' => 'str', 'width' => '65%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'year' => array('title' => "Year Level", 'type' => 'number', 'data' => 'int', 'width' => '5%', 'help' => '', 'readParms' => '', 'writeParms' => ['default' => 1, 'min' => 1, 'max' => 13], 'class' => 'left', 'thclass' => 'left',), 'options' => array('title' => LAN_OPTIONS, 'type' => null, 'data' => '', 'width' => '10%', 'thclass' => 'center last', 'class' => 'center last', 'forced' => '1',), ); protected $fieldpref = array('checkboxes', 'indexNo', 'word', 'usage', 'year', 'options'); private function esc($value) { return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); } /** * "Spelling Score" page: last month's score per user and subject, a * difficulty figure per user, and how many words still need a usage. */ public function customPage() { $sql = e107::getDb(); $text = ''; $users = []; $sql->gen("SELECT DISTINCT username FROM `#tkReport` WHERE `answereddatestamp` > UNIX_TIMESTAMP(NOW() - INTERVAL 1 MONTH)"); while ($row = $sql->fetch()) { $users[] = $row['username']; } foreach ($users as $user) { $u = $sql->escape($user); // Score per subject, last month. $sql->gen("SELECT `subject`, CAST((SUM(correct) / COUNT(id) * 100) AS DECIMAL(5,2)) AS percent, COUNT(id) AS `count` FROM `#tkReport` WHERE `answereddatestamp` > UNIX_TIMESTAMP(NOW() - INTERVAL 1 MONTH) AND `username` = '{$u}' GROUP BY `subject` ORDER BY `subject`"); while ($row = $sql->fetch()) { $text .= ''; } // Difficulty as the game computes it: all-time question count x last month's overall percent. $totalQuestionCount = (int) $sql->count('tkReport', '(id)', "username = '{$u}'"); $sql->gen("SELECT CAST((SUM(correct)/COUNT(id)*100) AS DECIMAL(5,2)) AS percent FROM `#tkReport` WHERE username = '{$u}' AND answereddatestamp >= UNIX_TIMESTAMP(CURRENT_DATE() - INTERVAL 1 MONTH) AND answereddatestamp <= UNIX_TIMESTAMP(CURRENT_DATE() + INTERVAL 1 DAY)"); $row = $sql->fetch(); $percent = (is_array($row) && $row['percent'] !== null) ? (float) $row['percent'] : 0; $text .= ''; } $text .= '
UserSubjectPercentageQuestions last month
' . $this->esc($user) . '' . $this->esc($row['subject']) . '' . $this->esc($row['percent']) . '' . (int) $row['count'] . '
Difficulty' . ($totalQuestionCount * $percent) . '


'; $missing = (int) $sql->retrieve("SELECT COUNT(*) FROM `#tk_wordList` WHERE `usage` = '' OR `usage` IS NULL"); $text .= $missing . ' words need a usage entered.
'; $nextWord = $sql->retrieve("SELECT `indexNo`, `word` FROM `#tk_wordList` WHERE `usage` = '' OR `usage` IS NULL ORDER BY `indexNo` LIMIT 1"); if (is_array($nextWord)) { $text .= 'The next word to be entered is "' . $this->esc($nextWord['word']) . '" at position ' . (int) $nextWord['indexNo'] . '.'; } else { $text .= 'Every word has a usage entered.'; } $ns = e107::getRender(); $ns->tablerender("Scores", $text); } } class tk_Wordlist_form_ui extends e_admin_form_ui { } class tk_geography_ui extends e_admin_ui { protected $pluginTitle = 'TimeKeeper'; protected $pluginName = 'timekeeper'; protected $table = 'tk_countries'; protected $pid = 'id'; protected $perPage = 10; protected $fields = array( 'checkboxes' => array('title' => '', 'type' => null, 'data' => false, 'width' => '5%', 'thclass' => 'center', 'forced' => '1', 'class' => 'center', 'toggle' => 'e-multiselect',), 'id' => array('title' => LAN_ID, 'data' => 'int', 'width' => '5%', 'readonly' => 1, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'country' => array('title' => "Country", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => '10%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'currency' => array('title' => "Currency", 'type' => 'text', 'data' => 'str', 'width' => '10%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'capital' => array('title' => "Capital", 'type' => 'text', 'data' => 'str', 'width' => '10%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'isoCode' => array('title' => "isoCode", 'type' => 'text', 'data' => 'str', 'width' => '5%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'difficulty' => array('title' => "Difficulty", 'type' => 'number', 'data' => 'int', 'inline' => true, 'width' => '5%', 'help' => '', 'readParms' => '', 'writeParms' => ['default' => 10, 'min' => 1, 'max' => 10], 'class' => 'left', 'thclass' => 'left',), 'culture' => array('title' => "Culture", 'type' => 'textarea', 'data' => 'str', 'width' => '20%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',), 'continent' => array('title' => "Continent", 'type' => 'dropdown', 'data' => 'str', 'inline' => true, 'width' => '10%', 'help' => '', 'readParms' => '', 'writeParms' => ['optArray' => ['Africa'=>'Africa', 'Asia'=>'Asia', 'Europe'=>'Europe', 'North America'=>'North America', 'South America'=>'South America', 'Oceania'=>'Oceania']], 'class' => 'left', 'thclass' => 'left',), 'map_coords' => array('title' => "Map Coords", 'type' => 'text', 'data' => 'str', 'width' => '10%', 'help' => '', 'readParms' => '', 'writeParms' => ['default' => '{}'], 'class' => 'left', 'thclass' => 'left',), 'options' => array('title' => LAN_OPTIONS, 'type' => null, 'data' => '', 'width' => '10%', 'thclass' => 'center last', 'class' => 'center last', 'forced' => '1',), ); protected $fieldpref = array('checkboxes', 'id', 'country', 'isoCode', 'difficulty', 'options'); } class tk_geography_form_ui extends e_admin_form_ui { } /* ========================================================================= ===== Student limits (timekeeper table) interface ========================================================================= */ class tk_Students_ui extends e_admin_ui { protected $pluginTitle = 'TimeKeeper'; protected $pluginName = 'timekeeper'; protected $table = 'timekeeper'; protected $pid = 'username'; protected $perPage = 10; protected $fields = array( 'username' => array('title' => "User Name", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => 'auto', 'class' => 'left', 'thclass' => 'left',), 'enabled' => array('title' => "Limits on", 'type' => 'bool', 'data' => 'int', 'width' => 'auto', 'class' => 'left', 'thclass' => 'left',), 'freeMins' => array('title' => "Free minutes", 'type' => 'number', 'data' => 'int', 'width' => 'auto', 'class' => 'left', 'thclass' => 'left',), 'enableTime' => array('title' => "Play from", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'class' => 'left', 'thclass' => 'left',), 'disableTime' => array('title' => "Play until", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'class' => 'left', 'thclass' => 'left',), 'minQuestionCount' => array('title' => "Questions needed", 'type' => 'number', 'data' => 'int', 'width' => 'auto', 'class' => 'left', 'thclass' => 'left',), 'maxDailyPlayTime' => array('title' => "Max minutes a day", 'type' => 'number', 'data' => 'int', 'width' => 'auto', 'class' => 'left', 'thclass' => 'left',), ); protected $fieldpref = array('username', 'enabled', 'freeMins', 'enableTime', 'disableTime', 'minQuestionCount', 'maxDailyPlayTime'); private function esc($value) { return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); } /** * "Student Limits" page: edit every student's row in one form, and add a student. * Reached from the menu as Students/custom. */ public function customPage() { $sql = e107::getDb(); $mes = e107::getMessage(); $action = filter_input(INPUT_POST, 'tk_action'); if ($action === 'save') { $this->saveStudents($sql, $mes); } elseif ($action === 'add') { $this->addStudent($sql, $mes); } $text = $this->studentsForm($sql) . $this->addForm(); e107::getRender()->tablerender('Student Limits', $mes->render() . $text); } private function loadStudents($sql) { $rows = array(); $sql->gen("SELECT * FROM `#timekeeper` ORDER BY `username`"); while ($row = $sql->fetch()) { $rows[] = $row; } return $rows; } private function intValue($entry, $key, $min, $max, $label, &$errors) { $value = isset($entry[$key]) ? trim((string) $entry[$key]) : ''; if (!ctype_digit($value) || (int) $value < $min || (int) $value > $max) { $errors[] = $label . ' must be a whole number from ' . $min . ' to ' . $max . '.'; return null; } return (int) $value; } private function timeValue($entry, $key, $label, &$errors) { $value = isset($entry[$key]) ? trim((string) $entry[$key]) : ''; if (!preg_match('/^(\d{1,2})\.(\d{2})$/', $value, $match) || (int) $match[1] > 24 || (int) $match[2] > 59 || ((int) $match[1] === 24 && (int) $match[2] > 0)) { $errors[] = $label . ' must look like 9.30 or 22.00 (hours, a dot, then two digits for the minutes).'; return null; } return (float) $value; } /** * Save the edited rows. A row with any invalid field is left unchanged * (and reported); the others are saved. */ private function saveStudents($sql, $mes) { $posted = (isset($_POST['tk']) && is_array($_POST['tk'])) ? $_POST['tk'] : array(); $existing = array(); foreach ($this->loadStudents($sql) as $row) { $existing[$row['username']] = true; } $saved = 0; foreach ($posted as $entry) { if (!is_array($entry) || !isset($entry['username'])) { continue; } $username = (string) $entry['username']; if (!isset($existing[$username])) { continue; // only students that are already in the table } $errors = array(); $freeMins = $this->intValue($entry, 'freeMins', 0, 1440, 'Free minutes', $errors); $minQuestionCount = $this->intValue($entry, 'minQuestionCount', 0, 100000, 'Questions needed', $errors); $maxDailyPlayTime = $this->intValue($entry, 'maxDailyPlayTime', 0, 1440, 'Max minutes a day', $errors); $enableTime = $this->timeValue($entry, 'enableTime', 'Play from', $errors); $disableTime = $this->timeValue($entry, 'disableTime', 'Play until', $errors); if (!empty($errors)) { foreach ($errors as $error) { $mes->addError($this->esc($username) . ': ' . $error . ' Not saved.'); } continue; } $sql->update('timekeeper', array( 'enabled' => isset($entry['enabled']) ? 1 : 0, 'freeMins' => $freeMins, 'enableTime' => $enableTime, 'disableTime' => $disableTime, 'minQuestionCount' => $minQuestionCount, 'maxDailyPlayTime' => $maxDailyPlayTime, 'WHERE' => "username = '" . $sql->escape($username) . "'", )); $saved++; } if ($saved > 0) { $mes->addSuccess('Saved the limits for ' . $saved . ($saved === 1 ? ' student.' : ' students.')); } } /** * Add a student with the same starting limits timer.php uses. */ private function addStudent($sql, $mes) { $username = trim((string) filter_input(INPUT_POST, 'tk_new_user')); if (!preg_match('/^[A-Za-z0-9_.-]{1,50}$/', $username)) { $mes->addError('Enter an e107 login name (letters, numbers, . _ - only).'); return; } if ($sql->count('user', '(*)', 'user_loginname="' . $sql->escape($username) . '"') < 1) { $mes->addError('There is no e107 user with the login name "' . $this->esc($username) . '".'); return; } if ($sql->count('timekeeper', '(*)', 'username="' . $sql->escape($username) . '"') > 0) { $mes->addError($this->esc($username) . ' already has limits.'); return; } $sql->insert('timekeeper', array( 'username' => $username, 'enabled' => 1, 'freeMins' => 10, 'enableTime' => 10.00, 'disableTime' => 22.00, 'minQuestionCount' => 100, 'usedMinutes' => 0, 'lastDate' => 0, 'maxDailyPlayTime' => 1440, )); $mes->addSuccess('Added ' . $this->esc($username) . ' with the starting limits. Change them below.'); } private function studentsForm($sql) { $rows = $this->loadStudents($sql); if (empty($rows)) { return '

No students have limits yet. They are created automatically the first time the enforcement client asks about a student, or you can add one below.

'; } $frm = e107::getForm(); $text = $frm->open('tkStudents', 'post'); $text .= $frm->hidden('tk_action', 'save'); $text .= '' . '' . '' . ''; foreach ($rows as $i => $row) { $name = $row['username']; $key = 'tk[' . $i . ']'; $usedToday = ((int) $row['lastDate'] > 0 && date('Ymd', (int) $row['lastDate']) === date('Ymd')) ? (int) $row['usedMinutes'] : 0; $earned = (int) $sql->retrieve('timekeeperScore', 'timekeeper_minutes', 'timekeeper_username="' . $sql->escape($name) . '"'); $text .= '' . '' . '' . '' . '' . '' . '' . '' . '' . '' . ''; } $text .= '
StudentLimits onFree minutes a dayPlay fromPlay untilQuestions needed todayMax minutes a dayUsed todayEarned minutes
' . $this->esc($name) . $frm->hidden($key . '[username]', $name) . '' . $frm->checkbox($key . '[enabled]', 1, !empty($row['enabled'])) . '' . $frm->text($key . '[freeMins]', (int) $row['freeMins'], 6, array('size' => 'mini')) . '' . $frm->text($key . '[enableTime]', number_format((float) $row['enableTime'], 2, '.', ''), 5, array('size' => 'mini')) . '' . $frm->text($key . '[disableTime]', number_format((float) $row['disableTime'], 2, '.', ''), 5, array('size' => 'mini')) . '' . $frm->text($key . '[minQuestionCount]', (int) $row['minQuestionCount'], 6, array('size' => 'mini')) . '' . $frm->text($key . '[maxDailyPlayTime]', (int) $row['maxDailyPlayTime'], 6, array('size' => 'mini')) . '' . $usedToday . '' . $earned . '
'; $text .= '
' . $frm->button('tk_save_button', 'Save changes') . '
'; $text .= $frm->close(); $text .= '

' . 'Limits on off = no limit for that student. ' . 'Free minutes are available every day without earning them. ' . 'Play from / until are hours.minutes, e.g. 9.30 or 22.00 (always two digits for the minutes: 9.5 means 9:50). ' . 'Questions needed is how many questions must be answered today before earned minutes can be used. ' . 'Max minutes a day is the most play per day, free and earned (0 = no play). ' . 'Used today and Earned minutes are shown for information; change earned minutes on the Score screen.' . '

'; return $text; } private function addForm() { $frm = e107::getForm(); return '

Add a student

' . $frm->open('tkAddStudent', 'post') . $frm->hidden('tk_action', 'add') . $frm->text('tk_new_user', '', 50, array('placeholder' => 'e107 login name')) . ' ' . $frm->button('tk_add_button', 'Add student') . $frm->close(); } } class tk_Students_form_ui extends e_admin_form_ui { } new timekeeper_admin(); require_once(e_ADMIN . "auth.php"); e107::getAdminUI()->runPage(); require_once(e_ADMIN . "footer.php"); exit;