admin_prefs.php 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. <?php
  2. /*
  3. * e107 website system
  4. *
  5. * Copyright (C) 2008-2016 e107 Inc (e107.org)
  6. * Released under the terms and conditions of the
  7. * GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
  8. *
  9. * TimeKeeper admin area.
  10. *
  11. * Changes from the current repo version:
  12. * - the "Pending" screen pointed at a table called `Pending` (it doesn't
  13. * exist: the table is timekeeperPending) and its fields were copied from
  14. * the Questions screen, so none matched; now uses the real table/columns
  15. * - new "Preferences" page with a `stats_key` setting. This is the shared
  16. * secret that tk_stats_page.php and timekeeper.php?mode=short check
  17. * (?key=...), so it can be set from the admin area instead of a script
  18. * - pluginName is the plugin folder name in lower case ('timekeeper'); the
  19. * prefs are stored/read under that name (getPlugPref('timekeeper', ...))
  20. * - "Spelling Score" page (customPage): user and subject are escaped when
  21. * printed and in SQL, `e107.e107_tkReport` -> #tkReport, the three nested
  22. * query loops are one GROUP BY query per user, and "next word" no longer
  23. * prints blanks (or warns) when every word already has a usage
  24. * - removed the leftover rss_url*() methods copied from the RSS plugin
  25. * - new "Student Limits" screen: edit each student's row in the `timekeeper` table
  26. * (play window, free minutes, questions needed, daily maximum) and add a student,
  27. * instead of editing the table in phpMyAdmin
  28. */
  29. require_once("../../class2.php");
  30. if (!getperms("P") || !e107::isInstalled('timekeeper')) {
  31. e107::redirect('admin');
  32. exit;
  33. }
  34. // Maybe later i'll setup languages
  35. //e107::includeLan(e_PLUGIN."timekeeper/languages/".e_LANGUAGE."_admin_timekeeper_menu.php");
  36. class timekeeper_admin extends e_admin_dispatcher {
  37. protected $modes = array(
  38. 'main' => array(
  39. 'controller' => 'plugin_tk_admin_ui',
  40. 'path' => null,
  41. 'ui' => 'plugin_tk_admin_form_ui',
  42. 'uipath' => null
  43. ),
  44. 'score' => array(
  45. 'controller' => 'tk_Score_ui',
  46. 'path' => null,
  47. 'ui' => 'tk_Score_form_ui',
  48. 'uipath' => null
  49. ),
  50. 'pending' => array(
  51. 'controller' => 'tk_Pending_ui',
  52. 'path' => null,
  53. 'ui' => 'tk_Pending_form_ui',
  54. 'uipath' => null
  55. ),
  56. 'report' => array(
  57. 'controller' => 'tk_Report_ui',
  58. 'path' => null,
  59. 'ui' => 'tk_Report_form_ui',
  60. 'uipath' => null
  61. ),
  62. 'reportold' => array(
  63. 'controller' => 'tk_Reportold_ui',
  64. 'path' => null,
  65. 'ui' => 'tk_Reportold_form_ui',
  66. 'uipath' => null
  67. ),
  68. 'wordlist' => array(
  69. 'controller' => 'tk_Wordlist_ui',
  70. 'path' => null,
  71. 'ui' => 'tk_Wordlist_form_ui',
  72. 'uipath' => null
  73. ),
  74. 'geography' => array(
  75. 'controller' => 'tk_geography_ui',
  76. 'path' => null,
  77. 'ui' => 'tk_geography_form_ui',
  78. 'uipath' => null
  79. ),
  80. 'students' => array(
  81. 'controller' => 'tk_Students_ui',
  82. 'path' => null,
  83. 'ui' => 'tk_Students_form_ui',
  84. 'uipath' => null
  85. ),
  86. );
  87. protected $adminMenu = array(
  88. 'main/list' => array('caption' => 'List Questions', 'perm' => '0'),
  89. 'main/create' => array('caption' => LAN_CREATE, 'perm' => '0'),
  90. 'main/prefs' => array('caption' => LAN_PREFS, 'perm' => '0'),
  91. 'Students/custom' => array('caption' => 'Student Limits', 'perm' => '0'),
  92. 'other0' => array('divider' => true),
  93. //'subjects/list' => array('caption'=> 'List Subjects', 'perm' => '0'),
  94. //'subjects/create' => array('caption'=> 'Add Subjects', 'perm' => '0'),
  95. //'other1' => array('divider' => true),
  96. 'Score/list' => array('caption' => 'List Score', 'perm' => '0'),
  97. 'other2' => array('divider' => true),
  98. 'Pending/list' => array('caption' => 'List Pending', 'perm' => '0'),
  99. 'other3' => array('divider' => true),
  100. 'Report/list' => array('caption' => 'List Report', 'perm' => '0'),
  101. 'Reportold/list' => array('caption' => 'List Report (Old)', 'perm' => '0'),
  102. 'other4' => array('divider' => true),
  103. 'Wordlist/list' => array('caption' => 'Spelling Words', 'perm' => '0'),
  104. 'Wordlist/create' => array('caption' => 'Add Words', 'perm' => '0'),
  105. 'Wordlist/custom' => array('caption' => 'Spelling Score', 'perm' => '0'),
  106. 'other5' => array('divider' => true),
  107. 'Geography/list' => array('caption' => 'Geography', 'perm' => '0'),
  108. );
  109. protected $adminMenuAliases = array(
  110. 'main/edit' => 'main/list'
  111. );
  112. protected $menuTitle = 'TimeKeeper';
  113. }
  114. /*
  115. =========================================================================
  116. ===== Questions table interface (and plugin preferences)
  117. =========================================================================
  118. */
  119. class plugin_tk_admin_ui extends e_admin_ui {
  120. protected $pluginTitle = 'TimeKeeper';
  121. protected $pluginName = 'timekeeper';
  122. protected $table = 'timekeeperQuestions';
  123. protected $pid = 'timekeeper_id';
  124. protected $perPage = 10;
  125. protected $fields = array(
  126. 'checkboxes' => array('title' => '', 'type' => null, 'data' => false, 'width' => '5%', 'thclass' => 'center', 'forced' => '1', 'class' => 'center', 'toggle' => 'e-multiselect',),
  127. 'timekeeper_id' => array('title' => LAN_ID, 'data' => 'int', 'width' => '5%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  128. 'timekeeper_questiondescription' => array('title' => LAN_TITLE, 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  129. 'timekeeper_question' => array('title' => "Question", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  130. 'timekeeper_answers' => array('title' => "Answers", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  131. 'timekeeper_subject' => array('title' => "Subject", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  132. 'timekeeper_addeddatestamp' => array('title' => LAN_DATESTAMP, 'type' => 'datestamp', 'data' => 'int', 'readonly' => true, 'width' => 'auto', 'filter' => true, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  133. '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',),
  134. 'timekeeper_correctanswer' => array('title' => "Correct Answer", 'type' => 'number', 'data' => 'int', 'inline' => true, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  135. 'options' => array('title' => LAN_OPTIONS, 'type' => null, 'data' => '', 'width' => '10%', 'thclass' => 'center last', 'class' => 'center last', 'forced' => '1',),
  136. );
  137. protected $fieldpref = array('checkboxes', 'timekeeper_questiondescription', 'timekeeper_question', 'timekeeper_answers', 'timekeeper_correctanswer', 'timekeeper_yearlevel', 'options');
  138. // Plugin preferences (Preferences page). Read elsewhere with
  139. // e107::getPlugPref('timekeeper', 'stats_key', '').
  140. protected $prefs = array(
  141. 'stats_key' => array(
  142. 'title' => 'Stats / summary key',
  143. 'type' => 'text',
  144. 'data' => 'str',
  145. '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).',
  146. 'writeParms' => array('size' => 'xxlarge'),
  147. ),
  148. );
  149. }
  150. class plugin_tk_admin_form_ui extends e_admin_form_ui {
  151. }
  152. /*
  153. =========================================================================
  154. ===== Score table interface
  155. =========================================================================
  156. */
  157. class tk_Score_ui extends e_admin_ui {
  158. protected $pluginTitle = 'TimeKeeper';
  159. protected $pluginName = 'timekeeper';
  160. protected $table = 'timekeeperScore';
  161. protected $pid = 'timekeeper_id';
  162. protected $perPage = 10;
  163. protected $fields = array(
  164. 'checkboxes' => array('title' => '', 'type' => null, 'data' => false, 'width' => '5%', 'thclass' => 'center', 'forced' => '1', 'class' => 'center', 'toggle' => 'e-multiselect',),
  165. 'timekeeper_id' => array('title' => LAN_ID, 'data' => 'int', 'width' => '5%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  166. 'timekeeper_username' => array('title' => "User Name", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  167. 'timekeeper_lifetimequestions' => array('title' => "life time Question", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  168. 'timekeeper_lifetimecorrect' => array('title' => "life time correct", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  169. 'timekeeper_minutes' => array('title' => "minutes", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  170. 'timekeeper_lastquestiondatestamp' => array('title' => LAN_DATESTAMP, 'type' => 'datestamp', 'data' => 'int', 'readonly' => true, 'width' => 'auto', 'filter' => true, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  171. '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',),
  172. 'options' => array('title' => LAN_OPTIONS, 'type' => null, 'data' => '', 'width' => '10%', 'thclass' => 'center last', 'class' => 'center last', 'forced' => '1',),
  173. );
  174. protected $fieldpref = array('checkboxes', 'timekeeper_username', 'timekeeper_lifetimequestions', 'timekeeper_lifetimecorrect', 'timekeeper_minutes', 'options');
  175. }
  176. class tk_Score_form_ui extends e_admin_form_ui {
  177. }
  178. /*
  179. =========================================================================
  180. ===== Pending table interface
  181. =========================================================================
  182. */
  183. class tk_Pending_ui extends e_admin_ui {
  184. protected $pluginTitle = 'TimeKeeper';
  185. protected $pluginName = 'timekeeper';
  186. protected $table = 'timekeeperPending';
  187. protected $pid = 'timekeeper_id';
  188. protected $perPage = 10;
  189. protected $fields = array(
  190. 'checkboxes' => array('title' => '', 'type' => null, 'data' => false, 'width' => '5%', 'thclass' => 'center', 'forced' => '1', 'class' => 'center', 'toggle' => 'e-multiselect',),
  191. 'timekeeper_id' => array('title' => LAN_ID, 'data' => 'int', 'width' => '5%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  192. 'timekeeper_username' => array('title' => "User Name", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  193. 'timekeeper_question' => array('title' => "Question", 'type' => 'number', 'data' => 'int', 'readonly' => 1, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  194. 'timekeeper_answereddatestamp' => array('title' => LAN_DATESTAMP, 'type' => 'datestamp', 'data' => 'int', 'readonly' => true, 'width' => 'auto', 'filter' => true, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  195. 'timekeeper_answer' => array('title' => "Answer", 'type' => 'textarea', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  196. 'options' => array('title' => LAN_OPTIONS, 'type' => null, 'data' => '', 'width' => '10%', 'thclass' => 'center last', 'class' => 'center last', 'forced' => '1',),
  197. );
  198. protected $fieldpref = array('checkboxes', 'timekeeper_username', 'timekeeper_question', 'timekeeper_answereddatestamp', 'timekeeper_answer', 'options');
  199. }
  200. class tk_Pending_form_ui extends e_admin_form_ui {
  201. }
  202. /*
  203. =========================================================================
  204. ===== Report table interface
  205. =========================================================================
  206. */
  207. class tk_Report_ui extends e_admin_ui {
  208. protected $pluginTitle = 'TimeKeeper';
  209. protected $pluginName = 'timekeeper';
  210. protected $table = 'tkReport';
  211. protected $pid = 'id';
  212. protected $perPage = 10;
  213. protected $fields = array(
  214. 'checkboxes' => array('title' => '', 'type' => null, 'data' => false, 'width' => '5%', 'thclass' => 'center', 'forced' => '1', 'class' => 'center', 'toggle' => 'e-multiselect',),
  215. 'id' => array('title' => LAN_ID, 'data' => 'int', 'width' => '5%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  216. 'username' => array('title' => "User Name", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => 'auto', 'filter' => true, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  217. 'question' => array('title' => "Question", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  218. 'userAnswer' => array('title' => "User Answer", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  219. 'answer' => array('title' => "Correct Answer", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  220. 'subject' => array('title' => "Subject", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  221. 'answereddatestamp' => array('title' => LAN_DATESTAMP, 'type' => 'datestamp', 'data' => 'int', 'readonly' => true, 'width' => 'auto', 'filter' => true, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  222. 'correct' => array('title' => "Correct", 'type' => 'bool', 'data' => 'int', 'inline' => true, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  223. 'options' => array('title' => LAN_OPTIONS, 'type' => null, 'data' => '', 'width' => '10%', 'thclass' => 'center last', 'class' => 'center last', 'forced' => '1',),
  224. );
  225. protected $fieldpref = array('checkboxes', 'username', 'question', 'userAnswer', 'answer', 'options');
  226. }
  227. class tk_Report_form_ui extends e_admin_form_ui {
  228. }
  229. class tk_Reportold_ui extends e_admin_ui {
  230. protected $pluginTitle = 'TimeKeeper';
  231. protected $pluginName = 'timekeeper';
  232. protected $table = 'timekeeperReport';
  233. protected $pid = 'timekeeper_id';
  234. protected $perPage = 10;
  235. protected $fields = array(
  236. 'checkboxes' => array('title' => '', 'type' => null, 'data' => false, 'width' => '5%', 'thclass' => 'center', 'forced' => '1', 'class' => 'center', 'toggle' => 'e-multiselect',),
  237. 'timekeeper_id' => array('title' => LAN_ID, 'data' => 'int', 'width' => '5%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  238. 'timekeeper_username' => array('title' => "User Name", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => 'auto', 'filter' => true, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  239. 'timekeeper_question' => array('title' => "Question", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  240. 'timekeeper_userAnswer' => array('title' => "User Answer", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  241. 'timekeeper_answer' => array('title' => "Correct Answer", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  242. 'timekeeper_subject' => array('title' => "Subject", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  243. 'timekeeper_answereddatestamp' => array('title' => LAN_DATESTAMP, 'type' => 'datestamp', 'data' => 'int', 'readonly' => true, 'width' => 'auto', 'filter' => true, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  244. 'timekeeper_yearLevel' => array('title' => "Year Level", 'type' => 'number', 'data' => 'int', 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  245. 'timekeeper_correct' => array('title' => "Correct", 'type' => 'bool', 'data' => 'int', 'inline' => true, 'width' => 'auto', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  246. 'options' => array('title' => LAN_OPTIONS, 'type' => null, 'data' => '', 'width' => '10%', 'thclass' => 'center last', 'class' => 'center last', 'forced' => '1',),
  247. );
  248. protected $fieldpref = array('checkboxes', 'timekeeper_username', 'timekeeper_question', 'timekeeper_userAnswer', 'timekeeper_answer', 'timekeeper_yearLevel', 'options');
  249. }
  250. class tk_Reportold_form_ui extends e_admin_form_ui {
  251. }
  252. /*
  253. =========================================================================
  254. ===== Wordlist table interface
  255. =========================================================================
  256. */
  257. class tk_Wordlist_ui extends e_admin_ui {
  258. protected $pluginTitle = 'TimeKeeper';
  259. protected $pluginName = 'timekeeper';
  260. protected $table = 'tk_wordList';
  261. protected $pid = 'indexNo';
  262. protected $perPage = 10;
  263. protected $fields = array(
  264. 'checkboxes' => array('title' => '', 'type' => null, 'data' => false, 'width' => '5%', 'thclass' => 'center', 'forced' => '1', 'class' => 'center', 'toggle' => 'e-multiselect',),
  265. 'indexNo' => array('title' => LAN_ID, 'data' => 'int', 'width' => '5%', 'readonly' => 1, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  266. 'word' => array('title' => "Word", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => '10%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  267. 'usage' => array('title' => "Word Usage", 'type' => 'textarea', 'data' => 'str', 'width' => '65%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  268. 'year' => array('title' => "Year Level", 'type' => 'number', 'data' => 'int', 'width' => '5%', 'help' => '', 'readParms' => '', 'writeParms' => ['default' => 1, 'min' => 1, 'max' => 13], 'class' => 'left', 'thclass' => 'left',),
  269. 'options' => array('title' => LAN_OPTIONS, 'type' => null, 'data' => '', 'width' => '10%', 'thclass' => 'center last', 'class' => 'center last', 'forced' => '1',),
  270. );
  271. protected $fieldpref = array('checkboxes', 'indexNo', 'word', 'usage', 'year', 'options');
  272. private function esc($value) {
  273. return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
  274. }
  275. /**
  276. * "Spelling Score" page: last month's score per user and subject, a
  277. * difficulty figure per user, and how many words still need a usage.
  278. */
  279. public function customPage() {
  280. $sql = e107::getDb();
  281. $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>';
  282. $users = [];
  283. $sql->gen("SELECT DISTINCT username FROM `#tkReport` WHERE `answereddatestamp` > UNIX_TIMESTAMP(NOW() - INTERVAL 1 MONTH)");
  284. while ($row = $sql->fetch()) {
  285. $users[] = $row['username'];
  286. }
  287. foreach ($users as $user) {
  288. $u = $sql->escape($user);
  289. // Score per subject, last month.
  290. $sql->gen("SELECT `subject`, CAST((SUM(correct) / COUNT(id) * 100) AS DECIMAL(5,2)) AS percent, COUNT(id) AS `count`
  291. FROM `#tkReport`
  292. WHERE `answereddatestamp` > UNIX_TIMESTAMP(NOW() - INTERVAL 1 MONTH) AND `username` = '{$u}'
  293. GROUP BY `subject` ORDER BY `subject`");
  294. while ($row = $sql->fetch()) {
  295. $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>';
  296. }
  297. // Difficulty as the game computes it: all-time question count x last month's overall percent.
  298. $totalQuestionCount = (int) $sql->count('tkReport', '(id)', "username = '{$u}'");
  299. $sql->gen("SELECT CAST((SUM(correct)/COUNT(id)*100) AS DECIMAL(5,2)) AS percent
  300. FROM `#tkReport` WHERE username = '{$u}'
  301. AND answereddatestamp >= UNIX_TIMESTAMP(CURRENT_DATE() - INTERVAL 1 MONTH)
  302. AND answereddatestamp <= UNIX_TIMESTAMP(CURRENT_DATE() + INTERVAL 1 DAY)");
  303. $row = $sql->fetch();
  304. $percent = (is_array($row) && $row['percent'] !== null) ? (float) $row['percent'] : 0;
  305. $text .= '<tr><td colspan=2>Difficulty</td><td colspan=2>' . ($totalQuestionCount * $percent) . '</td></tr>';
  306. }
  307. $text .= '</table><br/><br/>';
  308. $missing = (int) $sql->retrieve("SELECT COUNT(*) FROM `#tk_wordList` WHERE `usage` = '' OR `usage` IS NULL");
  309. $text .= $missing . ' words need a usage entered.<br/>';
  310. $nextWord = $sql->retrieve("SELECT `indexNo`, `word` FROM `#tk_wordList` WHERE `usage` = '' OR `usage` IS NULL ORDER BY `indexNo` LIMIT 1");
  311. if (is_array($nextWord)) {
  312. $text .= 'The next word to be entered is "' . $this->esc($nextWord['word']) . '" at position ' . (int) $nextWord['indexNo'] . '.';
  313. } else {
  314. $text .= 'Every word has a usage entered.';
  315. }
  316. $ns = e107::getRender();
  317. $ns->tablerender("Scores", $text);
  318. }
  319. }
  320. class tk_Wordlist_form_ui extends e_admin_form_ui {
  321. }
  322. class tk_geography_ui extends e_admin_ui {
  323. protected $pluginTitle = 'TimeKeeper';
  324. protected $pluginName = 'timekeeper';
  325. protected $table = 'tk_countries';
  326. protected $pid = 'id';
  327. protected $perPage = 10;
  328. protected $fields = array(
  329. 'checkboxes' => array('title' => '', 'type' => null, 'data' => false, 'width' => '5%', 'thclass' => 'center', 'forced' => '1', 'class' => 'center', 'toggle' => 'e-multiselect',),
  330. 'id' => array('title' => LAN_ID, 'data' => 'int', 'width' => '5%', 'readonly' => 1, 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  331. 'country' => array('title' => "Country", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => '10%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  332. 'currency' => array('title' => "Currency", 'type' => 'text', 'data' => 'str', 'width' => '10%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  333. 'capital' => array('title' => "Capital", 'type' => 'text', 'data' => 'str', 'width' => '10%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  334. 'isoCode' => array('title' => "isoCode", 'type' => 'text', 'data' => 'str', 'width' => '5%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  335. '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',),
  336. 'culture' => array('title' => "Culture", 'type' => 'textarea', 'data' => 'str', 'width' => '20%', 'help' => '', 'readParms' => '', 'writeParms' => '', 'class' => 'left', 'thclass' => 'left',),
  337. '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',),
  338. 'map_coords' => array('title' => "Map Coords", 'type' => 'text', 'data' => 'str', 'width' => '10%', 'help' => '', 'readParms' => '', 'writeParms' => ['default' => '{}'], 'class' => 'left', 'thclass' => 'left',),
  339. 'options' => array('title' => LAN_OPTIONS, 'type' => null, 'data' => '', 'width' => '10%', 'thclass' => 'center last', 'class' => 'center last', 'forced' => '1',),
  340. );
  341. protected $fieldpref = array('checkboxes', 'id', 'country', 'isoCode', 'difficulty', 'options');
  342. }
  343. class tk_geography_form_ui extends e_admin_form_ui {
  344. }
  345. /*
  346. =========================================================================
  347. ===== Student limits (timekeeper table) interface
  348. =========================================================================
  349. */
  350. class tk_Students_ui extends e_admin_ui {
  351. protected $pluginTitle = 'TimeKeeper';
  352. protected $pluginName = 'timekeeper';
  353. protected $table = 'timekeeper';
  354. protected $pid = 'username';
  355. protected $perPage = 10;
  356. protected $fields = array(
  357. 'username' => array('title' => "User Name", 'type' => 'text', 'data' => 'str', 'readonly' => 1, 'width' => 'auto', 'class' => 'left', 'thclass' => 'left',),
  358. 'enabled' => array('title' => "Limits on", 'type' => 'bool', 'data' => 'int', 'width' => 'auto', 'class' => 'left', 'thclass' => 'left',),
  359. 'freeMins' => array('title' => "Free minutes", 'type' => 'number', 'data' => 'int', 'width' => 'auto', 'class' => 'left', 'thclass' => 'left',),
  360. 'enableTime' => array('title' => "Play from", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'class' => 'left', 'thclass' => 'left',),
  361. 'disableTime' => array('title' => "Play until", 'type' => 'text', 'data' => 'str', 'width' => 'auto', 'class' => 'left', 'thclass' => 'left',),
  362. 'minQuestionCount' => array('title' => "Questions needed", 'type' => 'number', 'data' => 'int', 'width' => 'auto', 'class' => 'left', 'thclass' => 'left',),
  363. 'maxDailyPlayTime' => array('title' => "Max minutes a day", 'type' => 'number', 'data' => 'int', 'width' => 'auto', 'class' => 'left', 'thclass' => 'left',),
  364. );
  365. protected $fieldpref = array('username', 'enabled', 'freeMins', 'enableTime', 'disableTime', 'minQuestionCount', 'maxDailyPlayTime');
  366. private function esc($value) {
  367. return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
  368. }
  369. /**
  370. * "Student Limits" page: edit every student's row in one form, and add a student.
  371. * Reached from the menu as Students/custom.
  372. */
  373. public function customPage() {
  374. $sql = e107::getDb();
  375. $mes = e107::getMessage();
  376. $action = filter_input(INPUT_POST, 'tk_action');
  377. if ($action === 'save') {
  378. $this->saveStudents($sql, $mes);
  379. } elseif ($action === 'add') {
  380. $this->addStudent($sql, $mes);
  381. }
  382. $text = $this->studentsForm($sql) . $this->addForm();
  383. e107::getRender()->tablerender('Student Limits', $mes->render() . $text);
  384. }
  385. private function loadStudents($sql) {
  386. $rows = array();
  387. $sql->gen("SELECT * FROM `#timekeeper` ORDER BY `username`");
  388. while ($row = $sql->fetch()) {
  389. $rows[] = $row;
  390. }
  391. return $rows;
  392. }
  393. private function intValue($entry, $key, $min, $max, $label, &$errors) {
  394. $value = isset($entry[$key]) ? trim((string) $entry[$key]) : '';
  395. if (!ctype_digit($value) || (int) $value < $min || (int) $value > $max) {
  396. $errors[] = $label . ' must be a whole number from ' . $min . ' to ' . $max . '.';
  397. return null;
  398. }
  399. return (int) $value;
  400. }
  401. private function timeValue($entry, $key, $label, &$errors) {
  402. $value = isset($entry[$key]) ? trim((string) $entry[$key]) : '';
  403. 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)) {
  404. $errors[] = $label . ' must look like 9.30 or 22.00 (hours, a dot, then two digits for the minutes).';
  405. return null;
  406. }
  407. return (float) $value;
  408. }
  409. /**
  410. * Save the edited rows. A row with any invalid field is left unchanged
  411. * (and reported); the others are saved.
  412. */
  413. private function saveStudents($sql, $mes) {
  414. $posted = (isset($_POST['tk']) && is_array($_POST['tk'])) ? $_POST['tk'] : array();
  415. $existing = array();
  416. foreach ($this->loadStudents($sql) as $row) {
  417. $existing[$row['username']] = true;
  418. }
  419. $saved = 0;
  420. foreach ($posted as $entry) {
  421. if (!is_array($entry) || !isset($entry['username'])) {
  422. continue;
  423. }
  424. $username = (string) $entry['username'];
  425. if (!isset($existing[$username])) {
  426. continue; // only students that are already in the table
  427. }
  428. $errors = array();
  429. $freeMins = $this->intValue($entry, 'freeMins', 0, 1440, 'Free minutes', $errors);
  430. $minQuestionCount = $this->intValue($entry, 'minQuestionCount', 0, 100000, 'Questions needed', $errors);
  431. $maxDailyPlayTime = $this->intValue($entry, 'maxDailyPlayTime', 0, 1440, 'Max minutes a day', $errors);
  432. $enableTime = $this->timeValue($entry, 'enableTime', 'Play from', $errors);
  433. $disableTime = $this->timeValue($entry, 'disableTime', 'Play until', $errors);
  434. if (!empty($errors)) {
  435. foreach ($errors as $error) {
  436. $mes->addError($this->esc($username) . ': ' . $error . ' Not saved.');
  437. }
  438. continue;
  439. }
  440. $sql->update('timekeeper', array(
  441. 'enabled' => isset($entry['enabled']) ? 1 : 0,
  442. 'freeMins' => $freeMins,
  443. 'enableTime' => $enableTime,
  444. 'disableTime' => $disableTime,
  445. 'minQuestionCount' => $minQuestionCount,
  446. 'maxDailyPlayTime' => $maxDailyPlayTime,
  447. 'WHERE' => "username = '" . $sql->escape($username) . "'",
  448. ));
  449. $saved++;
  450. }
  451. if ($saved > 0) {
  452. $mes->addSuccess('Saved the limits for ' . $saved . ($saved === 1 ? ' student.' : ' students.'));
  453. }
  454. }
  455. /**
  456. * Add a student with the same starting limits timer.php uses.
  457. */
  458. private function addStudent($sql, $mes) {
  459. $username = trim((string) filter_input(INPUT_POST, 'tk_new_user'));
  460. if (!preg_match('/^[A-Za-z0-9_.-]{1,50}$/', $username)) {
  461. $mes->addError('Enter an e107 login name (letters, numbers, . _ - only).');
  462. return;
  463. }
  464. if ($sql->count('user', '(*)', 'user_loginname="' . $sql->escape($username) . '"') < 1) {
  465. $mes->addError('There is no e107 user with the login name "' . $this->esc($username) . '".');
  466. return;
  467. }
  468. if ($sql->count('timekeeper', '(*)', 'username="' . $sql->escape($username) . '"') > 0) {
  469. $mes->addError($this->esc($username) . ' already has limits.');
  470. return;
  471. }
  472. $sql->insert('timekeeper', array(
  473. 'username' => $username,
  474. 'enabled' => 1,
  475. 'freeMins' => 10,
  476. 'enableTime' => 10.00,
  477. 'disableTime' => 22.00,
  478. 'minQuestionCount' => 100,
  479. 'usedMinutes' => 0,
  480. 'lastDate' => 0,
  481. 'maxDailyPlayTime' => 1440,
  482. ));
  483. $mes->addSuccess('Added ' . $this->esc($username) . ' with the starting limits. Change them below.');
  484. }
  485. private function studentsForm($sql) {
  486. $rows = $this->loadStudents($sql);
  487. if (empty($rows)) {
  488. return '<p>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.</p>';
  489. }
  490. $frm = e107::getForm();
  491. $text = $frm->open('tkStudents', 'post');
  492. $text .= $frm->hidden('tk_action', 'save');
  493. $text .= '<table class="table table-striped table-bordered"><thead><tr>'
  494. . '<th>Student</th><th>Limits on</th><th>Free minutes a day</th><th>Play from</th><th>Play until</th>'
  495. . '<th>Questions needed today</th><th>Max minutes a day</th><th>Used today</th><th>Earned minutes</th>'
  496. . '</tr></thead><tbody>';
  497. foreach ($rows as $i => $row) {
  498. $name = $row['username'];
  499. $key = 'tk[' . $i . ']';
  500. $usedToday = ((int) $row['lastDate'] > 0 && date('Ymd', (int) $row['lastDate']) === date('Ymd')) ? (int) $row['usedMinutes'] : 0;
  501. $earned = (int) $sql->retrieve('timekeeperScore', 'timekeeper_minutes', 'timekeeper_username="' . $sql->escape($name) . '"');
  502. $text .= '<tr>'
  503. . '<td>' . $this->esc($name) . $frm->hidden($key . '[username]', $name) . '</td>'
  504. . '<td>' . $frm->checkbox($key . '[enabled]', 1, !empty($row['enabled'])) . '</td>'
  505. . '<td>' . $frm->text($key . '[freeMins]', (int) $row['freeMins'], 6, array('size' => 'mini')) . '</td>'
  506. . '<td>' . $frm->text($key . '[enableTime]', number_format((float) $row['enableTime'], 2, '.', ''), 5, array('size' => 'mini')) . '</td>'
  507. . '<td>' . $frm->text($key . '[disableTime]', number_format((float) $row['disableTime'], 2, '.', ''), 5, array('size' => 'mini')) . '</td>'
  508. . '<td>' . $frm->text($key . '[minQuestionCount]', (int) $row['minQuestionCount'], 6, array('size' => 'mini')) . '</td>'
  509. . '<td>' . $frm->text($key . '[maxDailyPlayTime]', (int) $row['maxDailyPlayTime'], 6, array('size' => 'mini')) . '</td>'
  510. . '<td>' . $usedToday . '</td>'
  511. . '<td>' . $earned . '</td>'
  512. . '</tr>';
  513. }
  514. $text .= '</tbody></table>';
  515. $text .= '<div class="buttons-bar center">' . $frm->button('tk_save_button', 'Save changes') . '</div>';
  516. $text .= $frm->close();
  517. $text .= '<p class="smalltext">'
  518. . '<b>Limits on</b> off = no limit for that student. '
  519. . '<b>Free minutes</b> are available every day without earning them. '
  520. . '<b>Play from / until</b> are hours.minutes, e.g. 9.30 or 22.00 (always two digits for the minutes: 9.5 means 9:50). '
  521. . '<b>Questions needed</b> is how many questions must be answered today before earned minutes can be used. '
  522. . '<b>Max minutes a day</b> is the most play per day, free and earned (0 = no play). '
  523. . '<b>Used today</b> and <b>Earned minutes</b> are shown for information; change earned minutes on the Score screen.'
  524. . '</p>';
  525. return $text;
  526. }
  527. private function addForm() {
  528. $frm = e107::getForm();
  529. return '<h4>Add a student</h4>'
  530. . $frm->open('tkAddStudent', 'post')
  531. . $frm->hidden('tk_action', 'add')
  532. . $frm->text('tk_new_user', '', 50, array('placeholder' => 'e107 login name'))
  533. . ' ' . $frm->button('tk_add_button', 'Add student')
  534. . $frm->close();
  535. }
  536. }
  537. class tk_Students_form_ui extends e_admin_form_ui {
  538. }
  539. new timekeeper_admin();
  540. require_once(e_ADMIN . "auth.php");
  541. e107::getAdminUI()->runPage();
  542. require_once(e_ADMIN . "footer.php");
  543. exit;