Gregory Huxley 1 долоо хоног өмнө
parent
commit
d0cec6e6c3

+ 91 - 190
admin_prefs.php

@@ -7,6 +7,22 @@
  * Released under the terms and conditions of the
  * Released under the terms and conditions of the
  * GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
  * GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
  *
  *
+ * TimeKeeper admin area.
+ *
+ * Changes from the current repo version:
+ *  - the "Pending" screen pointed at a table called `Pending` (it doesn't
+ *    exist: the table is timekeeperPending) and its fields were copied from
+ *    the Questions screen, so none matched; now uses the real table/columns
+ *  - new "Preferences" page with a `stats_key` setting. This is the shared
+ *    secret that tk_stats_page.php and timekeeper.php?mode=short check
+ *    (?key=...), so it can be set from the admin area instead of a script
+ *  - pluginName is the plugin folder name in lower case ('timekeeper'); the
+ *    prefs are stored/read under that name (getPlugPref('timekeeper', ...))
+ *  - "Spelling Score" page (customPage): user and subject are escaped when
+ *    printed and in SQL, `e107.e107_tkReport` -> #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
  */
  */
 
 
 
 
@@ -69,6 +85,7 @@ class timekeeper_admin extends e_admin_dispatcher {
     protected $adminMenu = array(
     protected $adminMenu = array(
         'main/list' => array('caption' => 'List Questions', 'perm' => '0'),
         'main/list' => array('caption' => 'List Questions', 'perm' => '0'),
         'main/create' => array('caption' => LAN_CREATE, 'perm' => '0'),
         'main/create' => array('caption' => LAN_CREATE, 'perm' => '0'),
+        'main/prefs' => array('caption' => LAN_PREFS, 'perm' => '0'),
         'other0' => array('divider' => true),
         'other0' => array('divider' => true),
         //'subjects/list'		=> array('caption'=> 'List Subjects', 'perm' => '0'),
         //'subjects/list'		=> array('caption'=> 'List Subjects', 'perm' => '0'),
         //'subjects/create'	=> array('caption'=> 'Add Subjects', 'perm' => '0'),
         //'subjects/create'	=> array('caption'=> 'Add Subjects', 'perm' => '0'),
@@ -95,14 +112,14 @@ class timekeeper_admin extends e_admin_dispatcher {
 
 
 /*
 /*
   =========================================================================
   =========================================================================
-  =====	Questions table interface
+  =====	Questions table interface (and plugin preferences)
   =========================================================================
   =========================================================================
  */
  */
 
 
 class plugin_tk_admin_ui extends e_admin_ui {
 class plugin_tk_admin_ui extends e_admin_ui {
 
 
     protected $pluginTitle = 'TimeKeeper';
     protected $pluginTitle = 'TimeKeeper';
-    protected $pluginName = 'TimeKeeper';
+    protected $pluginName = 'timekeeper';
     protected $table = 'timekeeperQuestions';
     protected $table = 'timekeeperQuestions';
     protected $pid = 'timekeeper_id';
     protected $pid = 'timekeeper_id';
     protected $perPage = 10;
     protected $perPage = 10;
@@ -120,6 +137,18 @@ class plugin_tk_admin_ui extends e_admin_ui {
     );
     );
     protected $fieldpref = array('checkboxes', 'timekeeper_questiondescription', 'timekeeper_question', 'timekeeper_answers', 'timekeeper_correctanswer', 'timekeeper_yearlevel', 'options');
     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=<this value> 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 {
 class plugin_tk_admin_form_ui extends e_admin_form_ui {
@@ -135,7 +164,7 @@ class plugin_tk_admin_form_ui extends e_admin_form_ui {
 class tk_Score_ui extends e_admin_ui {
 class tk_Score_ui extends e_admin_ui {
 
 
     protected $pluginTitle = 'TimeKeeper';
     protected $pluginTitle = 'TimeKeeper';
-    protected $pluginName = 'TimeKeeper';
+    protected $pluginName = 'timekeeper';
     protected $table = 'timekeeperScore';
     protected $table = 'timekeeperScore';
     protected $pid = 'timekeeper_id';
     protected $pid = 'timekeeper_id';
     protected $perPage = 10;
     protected $perPage = 10;
@@ -152,42 +181,10 @@ class tk_Score_ui extends e_admin_ui {
     );
     );
     protected $fieldpref = array('checkboxes', 'timekeeper_username', 'timekeeper_lifetimequestions', 'timekeeper_lifetimecorrect', 'timekeeper_minutes', 'options');
     protected $fieldpref = array('checkboxes', 'timekeeper_username', 'timekeeper_lifetimequestions', 'timekeeper_lifetimecorrect', 'timekeeper_minutes', 'options');
 
 
-    /*
-      protected $prefs = array(
-      'rss_othernews'	   		=> array('title'=> RSS_LAN_ADMIN_13, 'type' => 'boolean', 'data' => 'int'),
-      'rss_summarydiz' 		=> array('title'=> RSS_LAN_ADMIN_19, 'type' => 'boolean', 'data' => 'integer'),
-      'rss_shownewsimage' 	=> array('title'=> RSS_LAN_ADMIN_33, 'type' => 'boolean', 'data' => 'int')
-      );
-     */
 }
 }
 
 
 class tk_Score_form_ui extends e_admin_form_ui {
 class tk_Score_form_ui extends e_admin_form_ui {
 
 
-    // Custom Method/Function 
-    function rss_url3($curVal, $mode) {
-
-
-        switch ($mode) {
-            case 'read': // List Page
-
-                $type = $this->getController()->getListModel()->get('rss_type');
-                $topic = $this->getController()->getListModel()->get('rss_topicid');
-
-                $link = e107::url('rss_menu', 'rss', array('rss_type' => $type, 'rss_url' => $curVal, 'rss_topicid' => $topic));
-                return "<a href='" . $link . "'>" . $curVal . "</a>";
-
-
-            case 'write': // Edit Page
-                $link = SITEURL . "feed/"; // e107::url('rss_menu','index').'/';
-                return "<div class='form-inline'>" . $link . e107::getForm()->text('rss_url', $curVal, 255, 'size=small') . "/rss/{Topic id}</div>";
-
-
-            case 'filter':
-            case 'batch':
-                return null;
-        }
-    }
-
 }
 }
 
 
 /*
 /*
@@ -199,60 +196,25 @@ class tk_Score_form_ui extends e_admin_form_ui {
 class tk_Pending_ui extends e_admin_ui {
 class tk_Pending_ui extends e_admin_ui {
 
 
     protected $pluginTitle = 'TimeKeeper';
     protected $pluginTitle = 'TimeKeeper';
-    protected $pluginName = 'TimeKeeper';
-    protected $table = 'Pending';
+    protected $pluginName = 'timekeeper';
+    protected $table = 'timekeeperPending';
     protected $pid = 'timekeeper_id';
     protected $pid = 'timekeeper_id';
     protected $perPage = 10;
     protected $perPage = 10;
     protected $fields = array(
     protected $fields = array(
         'checkboxes' => array('title' => '', 'type' => null, 'data' => false, 'width' => '5%', 'thclass' => 'center', 'forced' => '1', 'class' => 'center', 'toggle' => 'e-multiselect',),
         '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_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',),
+        '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',),
         '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');
+    protected $fieldpref = array('checkboxes', 'timekeeper_username', 'timekeeper_question', 'timekeeper_answereddatestamp', 'timekeeper_answer', 'options');
 
 
-    /*
-      protected $prefs = array(
-      'rss_othernews'	   		=> array('title'=> RSS_LAN_ADMIN_13, 'type' => 'boolean', 'data' => 'int'),
-      'rss_summarydiz' 		=> array('title'=> RSS_LAN_ADMIN_19, 'type' => 'boolean', 'data' => 'integer'),
-      'rss_shownewsimage' 	=> array('title'=> RSS_LAN_ADMIN_33, 'type' => 'boolean', 'data' => 'int')
-      );
-     */
 }
 }
 
 
 class tk_Pending_form_ui extends e_admin_form_ui {
 class tk_Pending_form_ui extends e_admin_form_ui {
 
 
-    // Custom Method/Function 
-    function rss_url4($curVal, $mode) {
-
-
-        switch ($mode) {
-            case 'read': // List Page
-
-                $type = $this->getController()->getListModel()->get('rss_type');
-                $topic = $this->getController()->getListModel()->get('rss_topicid');
-
-                $link = e107::url('rss_menu', 'rss', array('rss_type' => $type, 'rss_url' => $curVal, 'rss_topicid' => $topic));
-                return "<a href='" . $link . "'>" . $curVal . "</a>";
-
-
-            case 'write': // Edit Page
-                $link = SITEURL . "feed/"; // e107::url('rss_menu','index').'/';
-                return "<div class='form-inline'>" . $link . e107::getForm()->text('rss_url', $curVal, 255, 'size=small') . "/rss/{Topic id}</div>";
-
-
-            case 'filter':
-            case 'batch':
-                return null;
-        }
-    }
-
 }
 }
 
 
 /*
 /*
@@ -264,7 +226,7 @@ class tk_Pending_form_ui extends e_admin_form_ui {
 class tk_Report_ui extends e_admin_ui {
 class tk_Report_ui extends e_admin_ui {
 
 
     protected $pluginTitle = 'TimeKeeper';
     protected $pluginTitle = 'TimeKeeper';
-    protected $pluginName = 'TimeKeeper';
+    protected $pluginName = 'timekeeper';
     protected $table = 'tkReport';
     protected $table = 'tkReport';
     protected $pid = 'id';
     protected $pid = 'id';
     protected $perPage = 10;
     protected $perPage = 10;
@@ -282,48 +244,16 @@ class tk_Report_ui extends e_admin_ui {
     );
     );
     protected $fieldpref = array('checkboxes', 'username', 'question', 'userAnswer', 'answer', 'options');
     protected $fieldpref = array('checkboxes', 'username', 'question', 'userAnswer', 'answer', 'options');
 
 
-    /*
-      protected $prefs = array(
-      'rss_othernews'	   		=> array('title'=> RSS_LAN_ADMIN_13, 'type' => 'boolean', 'data' => 'int'),
-      'rss_summarydiz' 		=> array('title'=> RSS_LAN_ADMIN_19, 'type' => 'boolean', 'data' => 'integer'),
-      'rss_shownewsimage' 	=> array('title'=> RSS_LAN_ADMIN_33, 'type' => 'boolean', 'data' => 'int')
-      );
-     */
 }
 }
 
 
 class tk_Report_form_ui extends e_admin_form_ui {
 class tk_Report_form_ui extends e_admin_form_ui {
 
 
-    // Custom Method/Function 
-    function rss_url5($curVal, $mode) {
-
-
-        switch ($mode) {
-            case 'read': // List Page
-
-                $type = $this->getController()->getListModel()->get('rss_type');
-                $topic = $this->getController()->getListModel()->get('rss_topicid');
-
-                $link = e107::url('rss_menu', 'rss', array('rss_type' => $type, 'rss_url' => $curVal, 'rss_topicid' => $topic));
-                return "<a href='" . $link . "'>" . $curVal . "</a>";
-
-
-            case 'write': // Edit Page
-                $link = SITEURL . "feed/"; // e107::url('rss_menu','index').'/';
-                return "<div class='form-inline'>" . $link . e107::getForm()->text('rss_url', $curVal, 255, 'size=small') . "/rss/{Topic id}</div>";
-
-
-            case 'filter':
-            case 'batch':
-                return null;
-        }
-    }
-
 }
 }
 
 
 class tk_Reportold_ui extends e_admin_ui {
 class tk_Reportold_ui extends e_admin_ui {
 
 
     protected $pluginTitle = 'TimeKeeper';
     protected $pluginTitle = 'TimeKeeper';
-    protected $pluginName = 'TimeKeeper';
+    protected $pluginName = 'timekeeper';
     protected $table = 'timekeeperReport';
     protected $table = 'timekeeperReport';
     protected $pid = 'timekeeper_id';
     protected $pid = 'timekeeper_id';
     protected $perPage = 10;
     protected $perPage = 10;
@@ -342,42 +272,10 @@ class tk_Reportold_ui extends e_admin_ui {
     );
     );
     protected $fieldpref = array('checkboxes', 'timekeeper_username', 'timekeeper_question', 'timekeeper_userAnswer', 'timekeeper_answer', 'timekeeper_yearLevel', 'options');
     protected $fieldpref = array('checkboxes', 'timekeeper_username', 'timekeeper_question', 'timekeeper_userAnswer', 'timekeeper_answer', 'timekeeper_yearLevel', 'options');
 
 
-    /*
-      protected $prefs = array(
-      'rss_othernews'	   		=> array('title'=> RSS_LAN_ADMIN_13, 'type' => 'boolean', 'data' => 'int'),
-      'rss_summarydiz' 		=> array('title'=> RSS_LAN_ADMIN_19, 'type' => 'boolean', 'data' => 'integer'),
-      'rss_shownewsimage' 	=> array('title'=> RSS_LAN_ADMIN_33, 'type' => 'boolean', 'data' => 'int')
-      );
-     */
 }
 }
 
 
 class tk_Reportold_form_ui extends e_admin_form_ui {
 class tk_Reportold_form_ui extends e_admin_form_ui {
 
 
-    // Custom Method/Function 
-    function rss_url5($curVal, $mode) {
-
-
-        switch ($mode) {
-            case 'read': // List Page
-
-                $type = $this->getController()->getListModel()->get('rss_type');
-                $topic = $this->getController()->getListModel()->get('rss_topicid');
-
-                $link = e107::url('rss_menu', 'rss', array('rss_type' => $type, 'rss_url' => $curVal, 'rss_topicid' => $topic));
-                return "<a href='" . $link . "'>" . $curVal . "</a>";
-
-
-            case 'write': // Edit Page
-                $link = SITEURL . "feed/"; // e107::url('rss_menu','index').'/';
-                return "<div class='form-inline'>" . $link . e107::getForm()->text('rss_url', $curVal, 255, 'size=small') . "/rss/{Topic id}</div>";
-
-
-            case 'filter':
-            case 'batch':
-                return null;
-        }
-    }
-
 }
 }
 
 
 /*
 /*
@@ -389,7 +287,7 @@ class tk_Reportold_form_ui extends e_admin_form_ui {
 class tk_Wordlist_ui extends e_admin_ui {
 class tk_Wordlist_ui extends e_admin_ui {
 
 
     protected $pluginTitle = 'TimeKeeper';
     protected $pluginTitle = 'TimeKeeper';
-    protected $pluginName = 'TimeKeeper';
+    protected $pluginName = 'timekeeper';
     protected $table = 'tk_wordList';
     protected $table = 'tk_wordList';
     protected $pid = 'indexNo';
     protected $pid = 'indexNo';
     protected $perPage = 10;
     protected $perPage = 10;
@@ -403,58 +301,63 @@ class tk_Wordlist_ui extends e_admin_ui {
     );
     );
     protected $fieldpref = array('checkboxes', 'indexNo', 'word', 'usage', 'year', 'options');
     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() {
     public function customPage() {
 
 
-        $sql = e107::getdb();
+        $sql = e107::getDb();
+
         $text = '<table style="width:600px;"><tr><td style="width:25%;">User</td><td style="width:25%;">Subject</td><td style="width:24%;">Percentage</td><td style="width:25%;">Questions last month</td></tr>';
         $text = '<table style="width:600px;"><tr><td style="width:25%;">User</td><td style="width:25%;">Subject</td><td style="width:24%;">Percentage</td><td style="width:25%;">Questions last month</td></tr>';
-        $Users = [];
-        $getUsers = "SELECT DISTINCT username FROM e107.e107_tkReport WHERE `answereddatestamp` > UNIX_TIMESTAMP((NOW()) - INTERVAL 1 MONTH);";
-        $getSubjects = "SELECT DISTINCT `subject` FROM e107_tkReport WHERE `answereddatestamp` > UNIX_TIMESTAMP((NOW()) - INTERVAL 1 MONTH) AND `username` = '<user>';";
-        $getScore = "SELECT CAST((SUM(correct) / COUNT(id) * 100) AS DECIMAL (5 , 2 )) AS percent, COUNT(id) as `count`,'<subject>' AS `subject`
-            FROM e107_tkReport WHERE `answereddatestamp` > UNIX_TIMESTAMP((NOW()) - INTERVAL 1 MONTH) AND `username` = '<user>' AND `subject` = '<subject>';";
-        
-        $sql->gen($getUsers);
+
+        $users = [];
+        $sql->gen("SELECT DISTINCT username FROM `#tkReport` WHERE `answereddatestamp` > UNIX_TIMESTAMP(NOW() - INTERVAL 1 MONTH)");
         while ($row = $sql->fetch()) {
         while ($row = $sql->fetch()) {
-            $Users[] = $row['username'];
+            $users[] = $row['username'];
         }
         }
 
 
-        foreach ($Users as $user) {
-            $Subjects = [];
-            
-            $sql->gen(str_replace('<user>', $user, $getSubjects));
+        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()) {
             while ($row = $sql->fetch()) {
-                $Subjects[] = $row['subject'];
-            }
-            
-            foreach ($Subjects as $Subject) {
-                $sql->gen(str_replace('<subject>', $Subject, str_replace('<user>', $user, $getScore)));
-                while ($row = $sql->fetch()) {
-                    $text .= '<tr><td>'.$user.'</td><td>'.$row['subject'].'</td><td>'.$row['percent'].'</td><td>'.$row['count'].'</td></tr>';
-                }
-            }
-            
-            
-            $returnArray['totalQuestionCount'] = e107::getDb()->count("tkReport", "(id)", "username = '" . $user . "'");
-            
-            e107::getDb()->gen("SELECT COUNT(id) as cntr, CAST((SUM(correct)/COUNT(id)*100) as DECIMAL(5,2)) as percent
-            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);
+                $text .= '<tr><td>' . $this->esc($user) . '</td><td>' . $this->esc($row['subject']) . '</td><td>' . $this->esc($row['percent']) . '</td><td>' . (int) $row['count'] . '</td></tr>';
             }
             }
-            $diffi = $returnArray['totalQuestionCount']*$returnArray['percent'];
-            $text .= '<tr><td colspan=2>Difficulty</td><td colspan=2>'.$diffi.'</td></tr>';
-            
+
+            // 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 .= '<tr><td colspan=2>Difficulty</td><td colspan=2>' . ($totalQuestionCount * $percent) . '</td></tr>';
         }
         }
-        
+
         $text .= '</table><br/><br/>';
         $text .= '</table><br/><br/>';
-        
-        $text .= $sql->retrieve('SELECT COUNT(`usage`) FROM `#tk_wordList` WHERE `usage`="";');
-        $text .= ' words need a usage entered.<br/>';
-        $nextWord = $sql->retrieve('SELECT `indexNo`,`word` FROM `#tk_wordList` WHERE `usage`="";');
-        $text .= 'The next word to be entered is "'.$nextWord['word'].'" at position '.$nextWord['indexNo'].'.';
-        
+
+        $missing = (int) $sql->retrieve("SELECT COUNT(*) FROM `#tk_wordList` WHERE `usage` = '' OR `usage` IS NULL");
+        $text .= $missing . ' words need a usage entered.<br/>';
+
+        $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 = e107::getRender();
         $ns->tablerender("Scores", $text);
         $ns->tablerender("Scores", $text);
     }
     }
@@ -468,7 +371,7 @@ class tk_Wordlist_form_ui extends e_admin_form_ui {
 class tk_geography_ui extends e_admin_ui {
 class tk_geography_ui extends e_admin_ui {
 
 
     protected $pluginTitle = 'TimeKeeper';
     protected $pluginTitle = 'TimeKeeper';
-    protected $pluginName = 'TimeKeeper';
+    protected $pluginName = 'timekeeper';
     protected $table = 'tk_countries';
     protected $table = 'tk_countries';
     protected $pid = 'id';
     protected $pid = 'id';
     protected $perPage = 10;
     protected $perPage = 10;
@@ -499,6 +402,4 @@ require_once(e_ADMIN . "auth.php");
 e107::getAdminUI()->runPage();
 e107::getAdminUI()->runPage();
 
 
 require_once(e_ADMIN . "footer.php");
 require_once(e_ADMIN . "footer.php");
-exit;
-
-
+exit;

+ 63 - 44
timekeeper.php

@@ -1,63 +1,82 @@
 <?php
 <?php
 
 
+/**
+ * TimeKeeper front page / entry point.
+ *
+ * Changes from the current repo version:
+ *  - mode=Generate PDF and mode=short had no access check, so anyone, logged
+ *    in or not, could read any student's full question report or score with
+ *    ?mode=Generate%20PDF&user=<name>&output=html&range=pastYear.
+ *    Now:
+ *      Generate PDF  -> must be logged in, and either a Main Admin or the
+ *                       student the report is about
+ *      short         -> same, OR a valid ?key= matching the plugin pref
+ *                       `stats_key` (for a wall display / client that can't log in)
+ *  - removed the dead commented-out code (it's all in git history)
+ *
+ * IMPORTANT: anything that fetches ?mode=short without logging in (a kiosk,
+ * a script) will now get 403 until stats_key is set and the URL has &key=...
+ */
+
 if (!defined('e107_INIT')) {
 if (!defined('e107_INIT')) {
     require_once('../../class2.php');
     require_once('../../class2.php');
 }
 }
-
-$e107 = e107::getInstance();
-
 if (!e107::isInstalled('timekeeper')) {
 if (!e107::isInstalled('timekeeper')) {
     e107::redirect();
     e107::redirect();
     exit;
     exit;
 }
 }
 
 
-require_once (e_PLUGIN . 'timekeeper/PreHeaderFunctions.php');
-//require_once (e_PLUGIN . 'timekeeper/QuestionGenerator.php');
-//require_once (e_PLUGIN . 'timekeeper/questionGenerators/subjects.php');
-//require_once (e_PLUGIN . 'timekeeper/TimeKeeperMain.php');
-//require_once (e_PLUGIN . 'timekeeper/FormGenerator.php');
-//require_once (e_PLUGIN . 'timekeeper/AnswerChecker.php');
+require_once(e_PLUGIN . 'timekeeper/PreHeaderFunctions.php');
 
 
+/**
+ * May the current visitor see the report for $user?
+ *
+ * @param string $user     the ?user= value being requested
+ * @param bool   $allowKey also accept the shared ?key= (pref `stats_key`)
+ */
+function tk_may_view_reports($user, $allowKey) {
+    if ($allowKey) {
+        $secret = (string) e107::getPlugPref('timekeeper', 'stats_key', '');
+        $given = filter_input(INPUT_GET, 'key');
+        if ($secret !== '' && is_string($given) && hash_equals($secret, $given)) {
+            return true;
+        }
+    }
 
 
+    if (!USER) {
+        return false;
+    }
+
+    return check_class('Main Admin') || strcasecmp(e107::getUser()->getLoginName(), $user) === 0;
+}
 
 
-//print_a($_POST);
 $mode = filter_input(INPUT_POST, 'mode');
 $mode = filter_input(INPUT_POST, 'mode');
 if ($mode == "") {
 if ($mode == "") {
     $mode = filter_input(INPUT_GET, 'mode');
     $mode = filter_input(INPUT_GET, 'mode');
 }
 }
 
 
-if ($mode == 'Generate PDF') {
-    $pdf = new ExcelFunctions;
-    $pdf->GeneratePdf();
-} else if ($mode == 'short') {
-    $json = new Summary;
-    $json->GenerateSummary();
+if ($mode == 'Generate PDF' || $mode == 'short') {
+    $reportUser = filter_input(INPUT_GET, 'user');
+
+    if (!is_string($reportUser) || !tk_may_view_reports($reportUser, $mode == 'short')) {
+        http_response_code(403);
+        exit('Forbidden');
+    }
+
+    if ($mode == 'Generate PDF') {
+        $pdf = new ExcelFunctions;
+        $pdf->GeneratePdf();
+    } else {
+        $json = new Summary;
+        $json->GenerateSummary();
+    }
 }
 }
-//
-//// New checker
-//$submitButton2 = filter_input(INPUT_POST, 'Answer');
-//$answerText = filter_input(INPUT_POST, 'a1');
-//if (isset($submitButton2)&&!empty($answerText)) {
-//    $AnswerChecker = new AnswerChecker();
-//    $text = $AnswerChecker->checkAnswers();
-//}
-
-// Get language file
-//e107::includeLan(e_PLUGIN.'timekeeper/languages/'.e_LANGUAGE.'_admin_rss_menu.php');
-
-
-//$timekeeperFront = new TimeKeeperMain;
-//$timekeeperFront->answerResponce = $text;
-require_once(HEADERF);      // render the header (everything before the main content area)
-//$time = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
-//if (!check_class("Main Admin")) {
-//    $timekeeperFront->run();
-//}else{
-    require_once (e_PLUGIN . 'timekeeper/TK2.php');
-    $tk2 = new TK2;
-    $tk2->run();
-//}
-
-//print_a("Did nothing in $time seconds\n");
-require_once(FOOTERF);     // render the footer (everything after the main content area)
-exit;
+
+require_once(HEADERF); // render the header (everything before the main content area)
+
+require_once(e_PLUGIN . 'timekeeper/TK2.php');
+$tk2 = new TK2;
+$tk2->run();
+
+require_once(FOOTERF); // render the footer (everything after the main content area)
+exit;

+ 1 - 1
timekeeperClientServices/silasdebug.dat

@@ -1 +1 @@
--0-
+-25-

+ 29 - 10
timekeeper_sql.php

@@ -1,11 +1,14 @@
 CREATE TABLE `timekeeper` (
 CREATE TABLE `timekeeper` (
-  `timekeeper_username` varchar(255) NOT NULL DEFAULT '',
-  `timekeeper_Enabled` bit(1) DEFAULT NULL,
-  `timekeeper_Freemins` int(10) unsigned NOT NULL DEFAULT '0',
-  `timekeeper_Enable_Time` double NOT NULL DEFAULT '0',
-  `timekeeper_Disable_Time` double NOT NULL DEFAULT '0',
-  `timekeeper_Min_Question_Count` int(11) DEFAULT '0',
-  PRIMARY KEY (`timekeeper_username`)
+  `username` varchar(255) NOT NULL DEFAULT '',
+  `enabled` tinyint(1) NOT NULL DEFAULT '0',
+  `freeMins` int(10) NOT NULL DEFAULT '0',
+  `enableTime` double NOT NULL DEFAULT '0',
+  `disableTime` double NOT NULL DEFAULT '0',
+  `minQuestionCount` int(11) NOT NULL DEFAULT '0',
+  `usedMinutes` int(11) NOT NULL DEFAULT '0',
+  `lastDate` int(11) NOT NULL DEFAULT '0',
+  `maxDailyPlayTime` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`username`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 
 CREATE TABLE `timekeeperPending` (
 CREATE TABLE `timekeeperPending` (
@@ -88,14 +91,30 @@ CREATE TABLE `tkReport` (
   `answer` text NOT NULL,
   `answer` text NOT NULL,
   `correct` tinyint(1) unsigned NOT NULL DEFAULT '0',
   `correct` tinyint(1) unsigned NOT NULL DEFAULT '0',
   `answereddatestamp` int(10) unsigned NOT NULL DEFAULT '0',
   `answereddatestamp` int(10) unsigned NOT NULL DEFAULT '0',
-  PRIMARY KEY (`id`)
+  PRIMARY KEY (`id`),
+  KEY `user_subject_date` (`username`,`subject`,`answereddatestamp`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 
 CREATE TABLE `tk_wordList` (
 CREATE TABLE `tk_wordList` (
-  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `indexNo` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `word` varchar(100) NOT NULL,
   `word` varchar(100) NOT NULL,
   `usage` text,
   `usage` text,
   `year` tinyint(3) unsigned NOT NULL DEFAULT '1',
   `year` tinyint(3) unsigned NOT NULL DEFAULT '1',
+  PRIMARY KEY (`indexNo`),
+  KEY `year` (`year`),
+  KEY `word` (`word`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `tk_countries` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `country` varchar(100) NOT NULL DEFAULT '',
+  `currency` varchar(100) DEFAULT NULL,
+  `capital` varchar(100) DEFAULT NULL,
+  `isoCode` varchar(3) DEFAULT NULL,
+  `difficulty` tinyint(3) unsigned NOT NULL DEFAULT '10',
+  `culture` text,
+  `continent` varchar(50) DEFAULT NULL,
+  `map_coords` text,
   PRIMARY KEY (`id`),
   PRIMARY KEY (`id`),
-  KEY `year` (`year`)
+  KEY `continent` (`continent`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

+ 3 - 14
tk_stats_page.php

@@ -14,10 +14,8 @@
  *  - a student with exactly 1 answer today now also gets the "games enabled"
  *  - a student with exactly 1 answer today now also gets the "games enabled"
  *    line (the singular branch skipped it)
  *    line (the singular branch skipped it)
  *  - `e107.e107_tkReport` -> MPREFIX; removed the leftover print_r('hi')
  *  - `e107.e107_tkReport` -> MPREFIX; removed the leftover print_r('hi')
- *  - settings row: reads `username` / `minQuestionCount` as before and falls
- *    back to the timekeeper_username / timekeeper_Min_Question_Count names
- *    used by timekeeper_sql.php, until it's settled which one your table has
  */
  */
+
 if (!defined('e107_INIT')) {
 if (!defined('e107_INIT')) {
     require_once('../../class2.php');
     require_once('../../class2.php');
 }
 }
@@ -43,20 +41,11 @@ $reportTable = '`' . MPREFIX . 'tkReport`';
 
 
 // Settings row (unknown users show nothing).
 // Settings row (unknown users show nothing).
 $settings = $sql->retrieve('timekeeper', '*', 'username="' . $u . '"');
 $settings = $sql->retrieve('timekeeper', '*', 'username="' . $u . '"');
-if (!is_array($settings)) {
-    $settings = $sql->retrieve('timekeeper', '*', 'timekeeper_username="' . $u . '"');
-}
 if (!is_array($settings)) {
 if (!is_array($settings)) {
     exit;
     exit;
 }
 }
 
 
-$minQuestions = 0;
-foreach (['minQuestionCount', 'timekeeper_Min_Question_Count'] as $column) {
-    if (isset($settings[$column])) {
-        $minQuestions = (int) $settings[$column];
-        break;
-    }
-}
+$minQuestions = (int) $settings['minQuestionCount'];
 
 
 // Score per subject, all time.
 // Score per subject, all time.
 $sql->gen("SELECT `subject`, COUNT(id) AS `count`,
 $sql->gen("SELECT `subject`, COUNT(id) AS `count`,
@@ -112,4 +101,4 @@ if ($answeredToday > 0) {
 $text .= '</div>';
 $text .= '</div>';
 
 
 echo '<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="60"></head>'
 echo '<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="60"></head>'
- . '<body style="background-color: #1a0000;color:white;">' . $text . '</body></html>';
+        . '<body style="background-color: #1a0000;color:white;">' . $text . '</body></html>';