PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/mod/h5pactivity/classes/output/result.php

https://bitbucket.org/moodle/moodle
PHP | 300 lines | 146 code | 32 blank | 122 comment | 16 complexity | fd42acaabe1a4a0b2f05a7c478ed859e MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0
  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. * Contains class mod_h5pactivity\output\result
  18. *
  19. * @package mod_h5pactivity
  20. * @copyright 2020 Ferran Recio
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. namespace mod_h5pactivity\output;
  24. defined('MOODLE_INTERNAL') || die();
  25. use renderable;
  26. use templatable;
  27. use renderer_base;
  28. use stdClass;
  29. /**
  30. * Class to display an attempt tesult in mod_h5pactivity.
  31. *
  32. * @copyright 2020 Ferran Recio
  33. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34. */
  35. class result implements renderable, templatable {
  36. /** Correct answer state. */
  37. const CORRECT = 1;
  38. /** Incorrect answer state. */
  39. const INCORRECT = 2;
  40. /** Checked answer state. */
  41. const CHECKED = 3;
  42. /** Unchecked answer state. */
  43. const UNCHECKED = 4;
  44. /** Pass answer state. */
  45. const PASS = 5;
  46. /** Pass answer state. */
  47. const FAIL = 6;
  48. /** Unkown answer state. */
  49. const UNKNOWN = 7;
  50. /** Text answer state. */
  51. const TEXT = 8;
  52. /** @var stdClass result record */
  53. protected $result;
  54. /** @var mixed additional decoded data */
  55. protected $additionals;
  56. /** @var mixed response decoded data */
  57. protected $response;
  58. /** @var mixed correctpattern decoded data */
  59. protected $correctpattern = [];
  60. /**
  61. * Constructor.
  62. *
  63. * @param stdClass $result a h5pactivity_attempts_results record
  64. */
  65. protected function __construct(stdClass $result) {
  66. $this->result = $result;
  67. if (empty($result->additionals)) {
  68. $this->additionals = new stdClass();
  69. } else {
  70. $this->additionals = json_decode($result->additionals);
  71. }
  72. $this->response = $this->decode_response($result->response);
  73. if (!empty($result->correctpattern)) {
  74. $correctpattern = json_decode($result->correctpattern);
  75. foreach ($correctpattern as $pattern) {
  76. $this->correctpattern[] = $this->decode_response($pattern);
  77. }
  78. }
  79. }
  80. /**
  81. * return the correct result output depending on the interactiontype
  82. *
  83. * @param stdClass $result h5pactivity_attempts_results record
  84. * @return result|null the result output class if any
  85. */
  86. public static function create_from_record(stdClass $result): ?self {
  87. // Compound result track is omitted from the report.
  88. if ($result->interactiontype == 'compound') {
  89. return null;
  90. }
  91. $classname = "mod_h5pactivity\\output\\result\\{$result->interactiontype}";
  92. $classname = str_replace('-', '', $classname);
  93. if (class_exists($classname)) {
  94. return new $classname($result);
  95. }
  96. return new self($result);
  97. }
  98. /**
  99. * Return a decoded response structure.
  100. *
  101. * @param string $value the current response structure
  102. * @return array an array of reponses
  103. */
  104. private function decode_response(string $value): array {
  105. // If [,] means a list of elements.
  106. $list = explode('[,]', $value);
  107. // Inside a list element [.] means sublist (pair) and [:] a range.
  108. foreach ($list as $key => $item) {
  109. if (strpos($item, '[.]') !== false) {
  110. $list[$key] = explode('[.]', $item);
  111. } else if (strpos($item, '[:]') !== false) {
  112. $list[$key] = explode('[:]', $item);
  113. }
  114. }
  115. return $list;
  116. }
  117. /**
  118. * Export this data so it can be used as the context for a mustache template.
  119. *
  120. * @param renderer_base $output
  121. * @return stdClass
  122. */
  123. public function export_for_template(renderer_base $output): stdClass {
  124. $result = $this->result;
  125. $data = (object)[
  126. 'id' => $result->id,
  127. 'attemptid' => $result->attemptid,
  128. 'subcontent' => $result->subcontent,
  129. 'timecreated' => $result->timecreated,
  130. 'interactiontype' => $result->interactiontype,
  131. 'description' => strip_tags($result->description),
  132. 'rawscore' => $result->rawscore,
  133. 'maxscore' => $result->maxscore,
  134. 'duration' => $result->duration,
  135. 'completion' => $result->completion,
  136. 'success' => $result->success,
  137. ];
  138. $result;
  139. $options = $this->export_options();
  140. if (!empty($options)) {
  141. $data->hasoptions = true;
  142. $data->optionslabel = $this->get_optionslabel();
  143. $data->correctlabel = $this->get_correctlabel();
  144. $data->answerlabel = $this->get_answerlabel();
  145. $data->options = array_values($options);
  146. $data->track = true;
  147. }
  148. if (!empty($result->maxscore)) {
  149. $data->score = get_string('score_out_of', 'mod_h5pactivity', $result);
  150. }
  151. return $data;
  152. }
  153. /**
  154. * Return the options data structure.
  155. *
  156. * Result types have to override this method generate a specific options report.
  157. *
  158. * An option is an object with:
  159. * - id: the option ID
  160. * - description: option description text
  161. * - useranswer (optional): what the user answer (see get_answer method)
  162. * - correctanswer (optional): the correct answer (see get_answer method)
  163. *
  164. * @return array of options
  165. */
  166. protected function export_options(): ?array {
  167. return [];
  168. }
  169. /**
  170. * Return a label for result user options/choices.
  171. *
  172. * Specific result types can override this method to customize
  173. * the result options table header.
  174. *
  175. * @return string to use in options table
  176. */
  177. protected function get_optionslabel(): string {
  178. return get_string('choice', 'mod_h5pactivity');
  179. }
  180. /**
  181. * Return a label for result user correct answer.
  182. *
  183. * Specific result types can override this method to customize
  184. * the result options table header.
  185. *
  186. * @return string to use in options table
  187. */
  188. protected function get_correctlabel(): string {
  189. return get_string('correct_answer', 'mod_h5pactivity');
  190. }
  191. /**
  192. * Return a label for result user attempt answer.
  193. *
  194. * Specific result types can override this method to customize
  195. * the result options table header.
  196. *
  197. * @return string to use in options table
  198. */
  199. protected function get_answerlabel(): string {
  200. return get_string('attempt_answer', 'mod_h5pactivity');
  201. }
  202. /**
  203. * Extract descriptions from array.
  204. *
  205. * @param array $data additional attribute to parse
  206. * @return string[] the resulting strings
  207. */
  208. protected function get_descriptions(array $data): array {
  209. $result = [];
  210. foreach ($data as $key => $value) {
  211. $description = $this->get_description($value);
  212. $index = $value->id ?? $key;
  213. $index = trim($index);
  214. if (is_numeric($index)) {
  215. $index = intval($index);
  216. }
  217. $result[$index] = (object)['description' => $description, 'id' => $index];
  218. }
  219. ksort($result);
  220. return $result;
  221. }
  222. /**
  223. * Extract description from data element.
  224. *
  225. * @param stdClass $data additional attribute to parse
  226. * @return string the resulting string
  227. */
  228. protected function get_description(stdClass $data): string {
  229. if (!isset($data->description)) {
  230. return '';
  231. }
  232. $translations = (array) $data->description;
  233. if (empty($translations)) {
  234. return '';
  235. }
  236. // By default, H5P packages only send "en-US" descriptions.
  237. $result = $translations['en-US'] ?? array_shift($translations);
  238. return trim($result);
  239. }
  240. /**
  241. * Return an answer data to show results.
  242. *
  243. * @param int $state the answer state
  244. * @param string $answer the extra text to display (default null)
  245. * @return stdClass with "answer" text and the state attribute to be displayed
  246. */
  247. protected function get_answer(int $state, string $answer = null): stdClass {
  248. $states = [
  249. self::CORRECT => 'correct',
  250. self::INCORRECT => 'incorrect',
  251. self::CHECKED => 'checked',
  252. self::UNCHECKED => 'unchecked',
  253. self::PASS => 'pass',
  254. self::FAIL => 'fail',
  255. self::UNKNOWN => 'unkown',
  256. self::TEXT => 'text',
  257. ];
  258. $state = $states[$state] ?? self::UNKNOWN;
  259. if ($answer === null) {
  260. $answer = get_string('answer_'.$state, 'mod_h5pactivity');
  261. }
  262. $result = (object)[
  263. 'answer' => $answer,
  264. $state => true,
  265. ];
  266. return $result;
  267. }
  268. }