PageRenderTime 38ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/attempt/review.php

https://github.com/KieranRBriggs/moodle-mod_hotpot
PHP | 397 lines | 209 code | 54 blank | 134 comment | 46 complexity | 013735e1ef142b94bd84a7b4c7066900 MD5 | raw file
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Review results of an attempt at a HotPot quiz
  18. * Output format: hp
  19. *
  20. * @package mod-hotpot
  21. * @copyright 2010 Gordon Bateson <gordon.bateson@gmail.com>
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. */
  24. defined('MOODLE_INTERNAL') || die();
  25. /**
  26. * mod_hotpot_attempt_review
  27. *
  28. * @copyright 2010 Gordon Bateson
  29. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30. * @since Moodle 2.0
  31. */
  32. class mod_hotpot_attempt_review {
  33. /**
  34. * return true if question text should appear on review page, or false otherwise
  35. *
  36. * @return boolean
  37. */
  38. static function show_question_text() {
  39. return true;
  40. }
  41. /**
  42. * attempt_fields
  43. *
  44. * @return xxx
  45. */
  46. static function attempt_fields() {
  47. return array('attempt', 'score', 'penalties', 'status', 'duration', 'timemodified');
  48. }
  49. /**
  50. * response_text_fields
  51. *
  52. * @return xxx
  53. */
  54. static function response_text_fields() {
  55. return array('correct', 'ignored', 'wrong');
  56. }
  57. /**
  58. * response_num_fields
  59. *
  60. * @return xxx
  61. */
  62. static function response_num_fields() {
  63. return array('score', 'weighting', 'hints', 'clues', 'checks');
  64. }
  65. /**
  66. * does this output format allow quiz attempts to be reviewed?
  67. *
  68. * @return boolean true if attempts can be reviewed, otherwise false
  69. */
  70. static function provide_review() {
  71. return true;
  72. }
  73. /**
  74. * can the current quiz attempt be reviewed now?
  75. *
  76. * @return boolean true if attempt can be reviewed, otherwise false
  77. */
  78. static function can_reviewattempts() {
  79. return self::provide_review();
  80. // when $hotpot->reviewoptions are implemented,
  81. // we can do something like the following ...
  82. if (self::provide_review() && $hotpot->reviewoptions) {
  83. if ($attempt = $hotpot->get_attempt()) {
  84. if ($hotpot->reviewoptions & hotpot::REVIEW_DURINGATTEMPT) {
  85. // during attempt
  86. if ($hotpot->attempt->status==hotpot::STATUS_INPROGRESS) {
  87. return true;
  88. }
  89. }
  90. if ($hotpot->reviewoptions & hotpot::REVIEW_AFTERATTEMPT) {
  91. // after attempt (but before quiz closes)
  92. if ($hotpot->attempt->status==hotpot::STATUS_COMPLETED) {
  93. return true;
  94. }
  95. if ($hotpot->attempt->status==hotpot::STATUS_ABANDONED) {
  96. return true;
  97. }
  98. if ($hotpot->attempt->status==hotpot::STATUS_TIMEDOUT) {
  99. return true;
  100. }
  101. }
  102. if ($hotpot->reviewoptions & hotpot::REVIEW_AFTERCLOSE) {
  103. // after the quiz closes
  104. if ($hotpot->timeclose < $hotpot->time) {
  105. return true;
  106. }
  107. }
  108. }
  109. }
  110. return false;
  111. }
  112. /**
  113. * review
  114. */
  115. static function review($hotpot, $class) {
  116. global $DB;
  117. // for the time-being we set this setting manually here
  118. // but one day it will be settable in "mod/hotpot/mod_form.php"
  119. $hotpot->reviewoptions = hotpot::REVIEW_DURINGATTEMPT | hotpot::REVIEW_AFTERATTEMPT | hotpot::REVIEW_AFTERCLOSE;
  120. // set $reviewoptions to relevant part of $hotpot->reviewoptions
  121. $reviewoptions = 0;
  122. if ($hotpot->can_reviewallattempts()) {
  123. // teacher can always review (anybody's) quiz attempts
  124. $reviewoptions = (hotpot::REVIEW_AFTERATTEMPT | hotpot::REVIEW_AFTERCLOSE);
  125. } else if ($hotpot->timeclose && $hotpot->timeclose > $hotpot->time) {
  126. // quiz is closed
  127. if ($hotpot->reviewoptions & hotpot::REVIEW_AFTERCLOSE) {
  128. // user can review quiz attempt after quiz closes
  129. $reviewoptions = ($hotpot->reviewoptions & hotpot::REVIEW_AFTERCLOSE);
  130. } else if ($hotpot->reviewoptions & hotpot::REVIEW_AFTERATTEMPT) {
  131. return get_string('noreviewbeforeclose', 'hotpot', userdate($hotpot->timeclose));
  132. } else {
  133. return get_string('noreview', 'hotpot');
  134. }
  135. } else {
  136. // quiz is still open
  137. if ($hotpot->reviewoptions & hotpot::REVIEW_AFTERATTEMPT) {
  138. // user can review quiz attempt while quiz is open
  139. $reviewoptions = ($hotpot->reviewoptions & hotpot::REVIEW_AFTERATTEMPT);
  140. } else if ($hotpot->reviewoptions & hotpot::REVIEW_AFTERCLOSE) {
  141. return get_string('noreviewafterclose', 'hotpot');
  142. } else {
  143. return get_string('noreview', 'hotpot');
  144. }
  145. }
  146. // if necessary, remove score and weighting fields
  147. $response_num_fields = call_user_func(array($class, 'response_num_fields'));
  148. if (! ($reviewoptions & hotpot::REVIEW_SCORES)) {
  149. $response_num_fields = preg_grep('/^score|weighting$/', $response_num_fields, PREG_GREP_INVERT);
  150. }
  151. // if necessary, remove reponses fields
  152. $response_text_fields = call_user_func(array($class, 'response_text_fields'));
  153. if (! ($reviewoptions & hotpot::REVIEW_RESPONSES)) {
  154. $response_text_fields = array();
  155. }
  156. // set flag to remove, if necessary, labels that show whether responses are correct or not
  157. if (! ($reviewoptions & hotpot::REVIEW_ANSWERS)) {
  158. $neutralize_text_fields = true;
  159. } else {
  160. $neutralize_text_fields = false;
  161. }
  162. $table = new html_table();
  163. $table->id = 'responses';
  164. $table->class = 'generaltable generalbox';
  165. $table->cellpadding = 4;
  166. $table->cellspacing = 0;
  167. if (count($response_num_fields)) {
  168. $question_colspan = count($response_num_fields) * 2;
  169. $textfield_colspan = $question_colspan - 1;
  170. } else {
  171. $question_colspan = 2;
  172. $textfield_colspan = 1;
  173. }
  174. $strtimeformat = get_string('strftimerecentfull');
  175. $attempt_fields = call_user_func(array($class, 'attempt_fields'));
  176. foreach ($attempt_fields as $field) {
  177. $row = new html_table_row();
  178. // add heading
  179. $text = call_user_func(array($class, 'format_attempt_heading'), $field);
  180. $cell = new html_table_cell($text, array('class'=>'attemptfield'));
  181. $row->cells[] = $cell;
  182. // add data
  183. $text = call_user_func(array($class, 'format_attempt_data'), $hotpot->attempt, $field, $strtimeformat);
  184. $cell = new html_table_cell($text, array('class'=>'attemptvalue'));
  185. $cell->colspan = $textfield_colspan;
  186. $row->cells[] = $cell;
  187. $table->data[] = $row;
  188. }
  189. // get questions and responses relevant to this quiz attempt
  190. $questions = $DB->get_records('hotpot_questions', array('hotpotid' => $hotpot->id));
  191. $responses = $DB->get_records('hotpot_responses', array('attemptid' => $hotpot->attempt->id));
  192. if (empty($questions) || empty($responses)) {
  193. $row = new html_table_row();
  194. $cell = new html_table_cell(get_string('noresponses', 'hotpot'), array('class'=>'noresponses'));
  195. $cell->colspan = $question_colspan;
  196. $row->cells[] = $cell;
  197. $table->data[] = $row;
  198. } else {
  199. // we have some responses, so print them
  200. foreach ($responses as $response) {
  201. if (empty($questions[$response->questionid])) {
  202. continue; // invalid question id - shouldn't happen !!
  203. }
  204. // add separator
  205. if (count($table->data)) {
  206. call_user_func(array($class, 'add_separator'), &$table, $question_colspan);
  207. // may be we are not allowed to pass params by reference with call_user_func(), so ...
  208. // $table = call_user_func(array($class, 'add_separator'), $table, $question_colspan)
  209. }
  210. // question text
  211. if (call_user_func(array($class, 'show_question_text'))) {
  212. if ($text = hotpot::get_question_text($questions[$response->questionid])) {
  213. call_user_func(array($class, 'add_question_text'), &$table, $text, $question_colspan);
  214. }
  215. }
  216. // string fields
  217. $neutral_text = '';
  218. foreach ($response_text_fields as $field) {
  219. if (empty($response->$field)) {
  220. continue; // shouldn't happen !!
  221. }
  222. $text = array();
  223. if ($records = hotpot::get_strings($response->$field)) {
  224. foreach ($records as $record) {
  225. $text[] = $record->string;
  226. }
  227. }
  228. unset($records);
  229. if (! $text = implode(',', $text)) {
  230. continue; // skip empty rows
  231. }
  232. if ($neutralize_text_fields) {
  233. $neutral_text .= ($neutral_text ? ',' : '').$text;
  234. } else {
  235. call_user_func(array($class, 'add_text_field'), &$table, $field, $text, $textfield_colspan);
  236. }
  237. }
  238. if ($neutral_text) {
  239. call_user_func(array($class, 'add_text_field'), &$table, 'responses', $neutral_text, $textfield_colspan);
  240. }
  241. // numeric fields
  242. $row = new html_table_row();
  243. foreach ($response_num_fields as $field) {
  244. call_user_func(array($class, 'add_num_field'), &$row, $field, $response->$field);
  245. }
  246. $table->data[] = $row;
  247. }
  248. }
  249. return html_writer::table($table);
  250. }
  251. /**
  252. * format_attempt_heading
  253. *
  254. * @param xxx $field
  255. * @return xxx
  256. */
  257. static function format_attempt_heading($field) {
  258. switch ($field) {
  259. case 'timemodified': return get_string('time', 'quiz');
  260. case 'attempt' : return get_string('attemptnumber', 'hotpot');
  261. case 'score' : return get_string('score', 'quiz');
  262. default : return get_string($field, 'hotpot');
  263. }
  264. }
  265. /**
  266. * format_attempt_data
  267. *
  268. * @param xxx $attempt
  269. * @param xxx $field
  270. * @param xxx $strtimeformat
  271. * @return xxx
  272. */
  273. static function format_attempt_data($attempt, $field, $strtimeformat) {
  274. switch ($field) {
  275. case 'status' : return hotpot::format_status($attempt->$field);
  276. case 'duration' : return format_time($attempt->timemodified - $attempt->timestart);
  277. case 'timemodified': return trim(userdate($attempt->$field, $strtimeformat));
  278. default : return $attempt->$field;
  279. }
  280. }
  281. /**
  282. * add_separator
  283. *
  284. * @param xxx $table (passed by reference)
  285. * @param xxx $colspan
  286. */
  287. static function add_separator(&$table, $colspan) {
  288. $row = new html_table_row();
  289. $text = html_writer::tag('div', '', array('class' => 'tabledivider'));
  290. $cell = new html_table_cell($text);
  291. $cell->colspan = $colspan;
  292. $row->cells[] = $cell;
  293. $table->data[] = $row;
  294. }
  295. /**
  296. * add_question_text
  297. *
  298. * @param xxx $table (passed by reference)
  299. * @param xxx $text
  300. * @param xxx $colspan
  301. */
  302. static function add_question_text(&$table, $text, $colspan) {
  303. $row = new html_table_row();
  304. $cell = new html_table_cell($text, array('class'=>'questiontext'));
  305. $cell->colspan = $colspan;
  306. $row->cells[] = $cell;
  307. $table->data[] = $row;
  308. }
  309. /**
  310. * add_text_field
  311. *
  312. * @param xxx $table (passed by reference)
  313. * @param xxx $field
  314. * @param xxx $text
  315. * @param xxx $colspan
  316. */
  317. static function add_text_field(&$table, $field, $text, $colspan) {
  318. $row = new html_table_row();
  319. // heading
  320. $cell = new html_table_cell(get_string($field, 'hotpot'), array('class'=>'responsefield'));
  321. $row->cells[] = $cell;
  322. // data
  323. $cell = new html_table_cell($text, array('class'=>'responsevalue'));
  324. $cell->colspan = $colspan;
  325. $row->cells[] = $cell;
  326. $table->data[] = $row;
  327. }
  328. /**
  329. * add_num_field
  330. *
  331. * @param xxx $row (passed by reference)
  332. * @param xxx $field
  333. * @param xxx $value
  334. */
  335. static function add_num_field(&$row, $field, $value) {
  336. // heading
  337. $cell = new html_table_cell(get_string($field, 'hotpot'), array('class'=>'responsefield'));
  338. $row->cells[] = $cell;
  339. // data
  340. $cell = new html_table_cell($value, array('class'=>'responsevalue'));
  341. $row->cells[] = $cell;
  342. }
  343. }