PageRenderTime 48ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/mod/lesson/pagetypes/matching.php

https://bitbucket.org/synergylearning/campusconnect
PHP | 551 lines | 446 code | 62 blank | 43 comment | 120 complexity | f6167fe906fd22a4c41d7383c09bd14d MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, LGPL-2.1, Apache-2.0, BSD-3-Clause, AGPL-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. * Matching
  18. *
  19. * @package mod
  20. * @subpackage lesson
  21. * @copyright 2009 Sam Hemelryk
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. **/
  24. defined('MOODLE_INTERNAL') || die();
  25. /** Matching question type */
  26. define("LESSON_PAGE_MATCHING", "5");
  27. class lesson_page_type_matching extends lesson_page {
  28. protected $type = lesson_page::TYPE_QUESTION;
  29. protected $typeid = LESSON_PAGE_MATCHING;
  30. protected $typeidstring = 'matching';
  31. protected $string = null;
  32. public function get_typeid() {
  33. return $this->typeid;
  34. }
  35. public function get_typestring() {
  36. if ($this->string===null) {
  37. $this->string = get_string($this->typeidstring, 'lesson');
  38. }
  39. return $this->string;
  40. }
  41. public function get_idstring() {
  42. return $this->typeidstring;
  43. }
  44. public function display($renderer, $attempt) {
  45. global $USER, $CFG, $PAGE;
  46. $mform = $this->make_answer_form($attempt);
  47. $data = new stdClass;
  48. $data->id = $PAGE->cm->id;
  49. $data->pageid = $this->properties->id;
  50. $mform->set_data($data);
  51. return $mform->display();
  52. }
  53. protected function make_answer_form($attempt=null) {
  54. global $USER, $CFG;
  55. // don't shuffle answers (could be an option??)
  56. $getanswers = array_slice($this->get_answers(), 2);
  57. $answers = array();
  58. foreach ($getanswers as $getanswer) {
  59. $answers[$getanswer->id] = $getanswer;
  60. }
  61. $responses = array();
  62. foreach ($answers as $answer) {
  63. // get all the response
  64. if ($answer->response != null) {
  65. $responses[] = trim($answer->response);
  66. }
  67. }
  68. $responseoptions = array(''=>get_string('choosedots'));
  69. if (!empty($responses)) {
  70. shuffle($responses);
  71. foreach ($responses as $response) {
  72. $responseoptions[htmlspecialchars($response)] = $response;
  73. }
  74. }
  75. if (isset($USER->modattempts[$this->lesson->id]) && !empty($attempt->useranswer)) {
  76. $useranswers = explode(',', $attempt->useranswer);
  77. $t = 0;
  78. } else {
  79. $useranswers = array();
  80. }
  81. $action = $CFG->wwwroot.'/mod/lesson/continue.php';
  82. $params = array('answers'=>$answers, 'useranswers'=>$useranswers, 'responseoptions'=>$responseoptions, 'lessonid'=>$this->lesson->id, 'contents'=>$this->get_contents());
  83. $mform = new lesson_display_answer_form_matching($action, $params);
  84. return $mform;
  85. }
  86. public function create_answers($properties) {
  87. global $DB;
  88. // now add the answers
  89. $newanswer = new stdClass;
  90. $newanswer->lessonid = $this->lesson->id;
  91. $newanswer->pageid = $this->properties->id;
  92. $newanswer->timecreated = $this->properties->timecreated;
  93. $answers = array();
  94. // need to add two to offset correct response and wrong response
  95. $this->lesson->maxanswers = $this->lesson->maxanswers + 2;
  96. for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
  97. $answer = clone($newanswer);
  98. if (!empty($properties->answer_editor[$i]) && is_array($properties->answer_editor[$i])) {
  99. $answer->answer = $properties->answer_editor[$i]['text'];
  100. $answer->answerformat = $properties->answer_editor[$i]['format'];
  101. }
  102. if (!empty($properties->response_editor[$i])) {
  103. $answer->response = $properties->response_editor[$i];
  104. $answer->responseformat = 0;
  105. }
  106. if (isset($properties->jumpto[$i])) {
  107. $answer->jumpto = $properties->jumpto[$i];
  108. }
  109. if ($this->lesson->custom && isset($properties->score[$i])) {
  110. $answer->score = $properties->score[$i];
  111. }
  112. if (isset($answer->answer) && $answer->answer != '') {
  113. $answer->id = $DB->insert_record("lesson_answers", $answer);
  114. $answers[$answer->id] = new lesson_page_answer($answer);
  115. } else if ($i < 2) {
  116. $answer->id = $DB->insert_record("lesson_answers", $answer);
  117. $answers[$answer->id] = new lesson_page_answer($answer);
  118. } else {
  119. break;
  120. }
  121. }
  122. $this->answers = $answers;
  123. return $answers;
  124. }
  125. public function check_answer() {
  126. global $CFG, $PAGE;
  127. $formattextdefoptions = new stdClass();
  128. $formattextdefoptions->noclean = true;
  129. $formattextdefoptions->para = false;
  130. $result = parent::check_answer();
  131. $mform = $this->make_answer_form();
  132. $data = $mform->get_data();
  133. require_sesskey();
  134. if (!$data) {
  135. redirect(new moodle_url('/mod/lesson/view.php', array('id'=>$PAGE->cm->id, 'pageid'=>$this->properties->id)));
  136. }
  137. $response = $data->response;
  138. $getanswers = $this->get_answers();
  139. $correct = array_shift($getanswers);
  140. $wrong = array_shift($getanswers);
  141. $answers = array();
  142. foreach ($getanswers as $key => $answer) {
  143. if ($answer->answer !== '' or $answer->response !== '') {
  144. $answers[$answer->id] = $answer;
  145. }
  146. }
  147. // get the user's exact responses for record keeping
  148. $hits = 0;
  149. $userresponse = array();
  150. foreach ($response as $id => $value) {
  151. if ($value == '') {
  152. $result->noanswer = true;
  153. return $result;
  154. }
  155. $value = htmlspecialchars_decode($value);
  156. $userresponse[] = $value;
  157. // Make sure the user's answer exists in question's answer
  158. if (array_key_exists($id, $answers)) {
  159. $answer = $answers[$id];
  160. $result->studentanswer .= '<br />'.format_text($answer->answer, $answer->answerformat, $formattextdefoptions).' = '.$value;
  161. if (trim($answer->response) == trim($value)) {
  162. $hits++;
  163. }
  164. }
  165. }
  166. $result->userresponse = implode(",", $userresponse);
  167. if ($hits == count($answers)) {
  168. $result->correctanswer = true;
  169. $result->response = format_text($correct->answer, $correct->answerformat, $formattextdefoptions);
  170. $result->answerid = $correct->id;
  171. $result->newpageid = $correct->jumpto;
  172. } else {
  173. $result->correctanswer = false;
  174. $result->response = format_text($wrong->answer, $wrong->answerformat, $formattextdefoptions);
  175. $result->answerid = $wrong->id;
  176. $result->newpageid = $wrong->jumpto;
  177. }
  178. return $result;
  179. }
  180. public function option_description_string() {
  181. return get_string("firstanswershould", "lesson");
  182. }
  183. public function display_answers(html_table $table) {
  184. $answers = $this->get_answers();
  185. $options = new stdClass;
  186. $options->noclean = true;
  187. $options->para = false;
  188. $i = 1;
  189. $n = 0;
  190. foreach ($answers as $answer) {
  191. if ($n < 2) {
  192. if ($answer->answer != null) {
  193. $cells = array();
  194. if ($n == 0) {
  195. $cells[] = "<span class=\"label\">".get_string("correctresponse", "lesson").'</span>';
  196. } else {
  197. $cells[] = "<span class=\"label\">".get_string("wrongresponse", "lesson").'</span>';
  198. }
  199. $cells[] = format_text($answer->answer, $answer->answerformat, $options);
  200. $table->data[] = new html_table_row($cells);
  201. }
  202. if ($n == 0) {
  203. $cells = array();
  204. $cells[] = '<span class="label">'.get_string("correctanswerscore", "lesson")."</span>: ";
  205. $cells[] = $answer->score;
  206. $table->data[] = new html_table_row($cells);
  207. $cells = array();
  208. $cells[] = '<span class="label">'.get_string("correctanswerjump", "lesson")."</span>: ";
  209. $cells[] = $this->get_jump_name($answer->jumpto);
  210. $table->data[] = new html_table_row($cells);
  211. } elseif ($n == 1) {
  212. $cells = array();
  213. $cells[] = '<span class="label">'.get_string("wronganswerscore", "lesson")."</span>: ";
  214. $cells[] = $answer->score;
  215. $table->data[] = new html_table_row($cells);
  216. $cells = array();
  217. $cells[] = '<span class="label">'.get_string("wronganswerjump", "lesson")."</span>: ";
  218. $cells[] = $this->get_jump_name($answer->jumpto);
  219. $table->data[] = new html_table_row($cells);
  220. }
  221. if ($n === 0){
  222. $table->data[count($table->data)-1]->cells[0]->style = 'width:20%;';
  223. }
  224. $n++;
  225. $i--;
  226. } else {
  227. $cells = array();
  228. if ($this->lesson->custom && $answer->score > 0) {
  229. // if the score is > 0, then it is correct
  230. $cells[] = '<span class="labelcorrect">'.get_string("answer", "lesson")." $i</span>: \n";
  231. } else if ($this->lesson->custom) {
  232. $cells[] = '<span class="label">'.get_string("answer", "lesson")." $i</span>: \n";
  233. } else if ($this->lesson->jumpto_is_correct($this->properties->id, $answer->jumpto)) {
  234. $cells[] = '<span class="labelcorrect">'.get_string("answer", "lesson")." $i</span>: \n";
  235. } else {
  236. $cells[] = '<span class="label">'.get_string("answer", "lesson")." $i</span>: \n";
  237. }
  238. $cells[] = format_text($answer->answer, $answer->answerformat, $options);
  239. $table->data[] = new html_table_row($cells);
  240. $cells = array();
  241. $cells[] = '<span class="label">'.get_string("matchesanswer", "lesson")." $i</span>: ";
  242. $cells[] = format_text($answer->response, $answer->responseformat, $options);
  243. $table->data[] = new html_table_row($cells);
  244. }
  245. $i++;
  246. }
  247. return $table;
  248. }
  249. /**
  250. * Updates the page and its answers
  251. *
  252. * @global moodle_database $DB
  253. * @global moodle_page $PAGE
  254. * @param stdClass $properties
  255. * @return bool
  256. */
  257. public function update($properties, $context = null, $maxbytes = null) {
  258. global $DB, $PAGE;
  259. $answers = $this->get_answers();
  260. $properties->id = $this->properties->id;
  261. $properties->lessonid = $this->lesson->id;
  262. $properties->timemodified = time();
  263. $properties = file_postupdate_standard_editor($properties, 'contents', array('noclean'=>true, 'maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$PAGE->course->maxbytes), context_module::instance($PAGE->cm->id), 'mod_lesson', 'page_contents', $properties->id);
  264. $DB->update_record("lesson_pages", $properties);
  265. // need to add two to offset correct response and wrong response
  266. $this->lesson->maxanswers += 2;
  267. for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
  268. if (!array_key_exists($i, $this->answers)) {
  269. $this->answers[$i] = new stdClass;
  270. $this->answers[$i]->lessonid = $this->lesson->id;
  271. $this->answers[$i]->pageid = $this->id;
  272. $this->answers[$i]->timecreated = $this->timecreated;
  273. }
  274. if (!empty($properties->answer_editor[$i]) && is_array($properties->answer_editor[$i])) {
  275. $this->answers[$i]->answer = $properties->answer_editor[$i]['text'];
  276. $this->answers[$i]->answerformat = $properties->answer_editor[$i]['format'];
  277. }
  278. if (!empty($properties->response_editor[$i])) {
  279. $this->answers[$i]->response = $properties->response_editor[$i];
  280. $this->answers[$i]->responseformat = 0;
  281. }
  282. if (isset($properties->jumpto[$i])) {
  283. $this->answers[$i]->jumpto = $properties->jumpto[$i];
  284. }
  285. if ($this->lesson->custom && isset($properties->score[$i])) {
  286. $this->answers[$i]->score = $properties->score[$i];
  287. }
  288. // we don't need to check for isset here because properties called it's own isset method.
  289. if ($this->answers[$i]->answer != '') {
  290. if (!isset($this->answers[$i]->id)) {
  291. $this->answers[$i]->id = $DB->insert_record("lesson_answers", $this->answers[$i]);
  292. } else {
  293. $DB->update_record("lesson_answers", $this->answers[$i]->properties());
  294. }
  295. } else if ($i < 2) {
  296. if (!isset($this->answers[$i]->id)) {
  297. $this->answers[$i]->id = $DB->insert_record("lesson_answers", $this->answers[$i]);
  298. } else {
  299. $DB->update_record("lesson_answers", $this->answers[$i]->properties());
  300. }
  301. } else if (isset($this->answers[$i]->id)) {
  302. $DB->delete_records('lesson_answers', array('id'=>$this->answers[$i]->id));
  303. unset($this->answers[$i]);
  304. }
  305. }
  306. return true;
  307. }
  308. public function stats(array &$pagestats, $tries) {
  309. if(count($tries) > $this->lesson->maxattempts) { // if there are more tries than the max that is allowed, grab the last "legal" attempt
  310. $temp = $tries[$this->lesson->maxattempts - 1];
  311. } else {
  312. // else, user attempted the question less than the max, so grab the last one
  313. $temp = end($tries);
  314. }
  315. if ($temp->correct) {
  316. if (isset($pagestats[$temp->pageid]["correct"])) {
  317. $pagestats[$temp->pageid]["correct"]++;
  318. } else {
  319. $pagestats[$temp->pageid]["correct"] = 1;
  320. }
  321. }
  322. if (isset($pagestats[$temp->pageid]["total"])) {
  323. $pagestats[$temp->pageid]["total"]++;
  324. } else {
  325. $pagestats[$temp->pageid]["total"] = 1;
  326. }
  327. return true;
  328. }
  329. public function report_answers($answerpage, $answerdata, $useranswer, $pagestats, &$i, &$n) {
  330. $answers = array();
  331. foreach ($this->get_answers() as $answer) {
  332. $answers[$answer->id] = $answer;
  333. }
  334. $formattextdefoptions = new stdClass;
  335. $formattextdefoptions->para = false; //I'll use it widely in this page
  336. foreach ($answers as $answer) {
  337. if ($n == 0 && $useranswer != null && $useranswer->correct) {
  338. if ($answer->response == null && $useranswer != null) {
  339. $answerdata->response = get_string("thatsthecorrectanswer", "lesson");
  340. } else {
  341. $answerdata->response = $answer->response;
  342. }
  343. if ($this->lesson->custom) {
  344. $answerdata->score = get_string("pointsearned", "lesson").": ".$answer->score;
  345. } else {
  346. $answerdata->score = get_string("receivedcredit", "lesson");
  347. }
  348. } elseif ($n == 1 && $useranswer != null && !$useranswer->correct) {
  349. if ($answer->response == null && $useranswer != null) {
  350. $answerdata->response = get_string("thatsthewronganswer", "lesson");
  351. } else {
  352. $answerdata->response = $answer->response;
  353. }
  354. if ($this->lesson->custom) {
  355. $answerdata->score = get_string("pointsearned", "lesson").": ".$answer->score;
  356. } else {
  357. $answerdata->score = get_string("didnotreceivecredit", "lesson");
  358. }
  359. } elseif ($n > 1) {
  360. $data = '<label class="accesshide" for="answer_' . $n . '">' . get_string('answer', 'lesson') . '</label>';
  361. $data .= strip_tags(format_string($answer->answer)) . ' ';
  362. if ($useranswer != null) {
  363. $userresponse = explode(",", $useranswer->useranswer);
  364. $data .= '<label class="accesshide" for="stu_answer_response_' . $n . '">' . get_string('matchesanswer', 'lesson') . '</label>';
  365. $data .= "<select id=\"stu_answer_response_" . $n . "\" disabled=\"disabled\"><option selected=\"selected\">";
  366. if (array_key_exists($i, $userresponse)) {
  367. $data .= $userresponse[$i];
  368. }
  369. $data .= "</option></select>";
  370. } else {
  371. $data .= '<label class="accesshide" for="answer_response_' . $n . '">' . get_string('matchesanswer', 'lesson') . '</label>';
  372. $data .= "<select id=\"answer_response_" . $n . "\" disabled=\"disabled\"><option selected=\"selected\">".strip_tags(format_string($answer->response))."</option></select>";
  373. }
  374. if ($n == 2) {
  375. if (isset($pagestats[$this->properties->id])) {
  376. if (!array_key_exists('correct', $pagestats[$this->properties->id])) {
  377. $pagestats[$this->properties->id]["correct"] = 0;
  378. }
  379. $percent = $pagestats[$this->properties->id]["correct"] / $pagestats[$this->properties->id]["total"] * 100;
  380. $percent = round($percent, 2);
  381. $percent .= "% ".get_string("answeredcorrectly", "lesson");
  382. } else {
  383. $percent = get_string("nooneansweredthisquestion", "lesson");
  384. }
  385. } else {
  386. $percent = '';
  387. }
  388. $answerdata->answers[] = array($data, $percent);
  389. $i++;
  390. }
  391. $n++;
  392. $answerpage->answerdata = $answerdata;
  393. }
  394. return $answerpage;
  395. }
  396. public function get_jumps() {
  397. global $DB;
  398. // The jumps for matching question type are stored in the 1st and 2nd answer record.
  399. $jumps = array();
  400. if ($answers = $DB->get_records("lesson_answers", array("lessonid" => $this->lesson->id, "pageid" => $this->properties->id), 'id', '*', 0, 2)) {
  401. foreach ($answers as $answer) {
  402. $jumps[] = $this->get_jump_name($answer->jumpto);
  403. }
  404. } else {
  405. $jumps[] = $this->get_jump_name($this->properties->nextpageid);
  406. }
  407. return $jumps;
  408. }
  409. }
  410. class lesson_add_page_form_matching extends lesson_add_page_form_base {
  411. public $qtype = 'matching';
  412. public $qtypestring = 'matching';
  413. public function custom_definition() {
  414. $this->_form->addElement('header', 'correctresponse', get_string('correctresponse', 'lesson'));
  415. $this->_form->addElement('editor', 'answer_editor[0]', get_string('correctresponse', 'lesson'), array('rows'=>'4', 'columns'=>'80'), array('noclean'=>true));
  416. $this->add_jumpto(0, get_string('correctanswerjump','lesson'), LESSON_NEXTPAGE);
  417. $this->add_score(0, get_string("correctanswerscore", "lesson"), 1);
  418. $this->_form->addElement('header', 'wrongresponse', get_string('wrongresponse', 'lesson'));
  419. $this->_form->addElement('editor', 'answer_editor[1]', get_string('wrongresponse', 'lesson'), array('rows'=>'4', 'columns'=>'80'), array('noclean'=>true));
  420. $this->add_jumpto(1, get_string('wronganswerjump','lesson'), LESSON_THISPAGE);
  421. $this->add_score(1, get_string("wronganswerscore", "lesson"), 0);
  422. for ($i = 2; $i < $this->_customdata['lesson']->maxanswers+2; $i++) {
  423. $this->_form->addElement('header', 'matchingpair'.($i-1), get_string('matchingpair', 'lesson', $i-1));
  424. $this->add_answer($i, null, ($i < 4));
  425. $required = ($i < 4);
  426. $label = get_string('matchesanswer','lesson');
  427. $count = $i;
  428. $this->_form->addElement('text', 'response_editor['.$count.']', $label, array('size'=>'50'));
  429. $this->_form->setType('response_editor['.$count.']', PARAM_NOTAGS);
  430. $this->_form->setDefault('response_editor['.$count.']', '');
  431. if ($required) {
  432. $this->_form->addRule('response_editor['.$count.']', get_string('required'), 'required', null, 'client');
  433. }
  434. }
  435. }
  436. }
  437. class lesson_display_answer_form_matching extends moodleform {
  438. public function definition() {
  439. global $USER, $OUTPUT;
  440. $mform = $this->_form;
  441. $answers = $this->_customdata['answers'];
  442. $useranswers = $this->_customdata['useranswers'];
  443. $responseoptions = $this->_customdata['responseoptions'];
  444. $lessonid = $this->_customdata['lessonid'];
  445. $contents = $this->_customdata['contents'];
  446. $mform->addElement('header', 'pageheader');
  447. $mform->addElement('html', $OUTPUT->container($contents, 'contents'));
  448. $hasattempt = false;
  449. $disabled = '';
  450. if (isset($useranswers) && !empty($useranswers)) {
  451. $hasattempt = true;
  452. $disabled = array('disabled' => 'disabled');
  453. }
  454. $options = new stdClass;
  455. $options->para = false;
  456. $options->noclean = true;
  457. $mform->addElement('hidden', 'id');
  458. $mform->setType('id', PARAM_INT);
  459. $mform->addElement('hidden', 'pageid');
  460. $mform->setType('pageid', PARAM_INT);
  461. $i = 0;
  462. foreach ($answers as $answer) {
  463. $mform->addElement('html', '<div class="answeroption">');
  464. if ($answer->response != null) {
  465. $responseid = 'response['.$answer->id.']';
  466. if ($hasattempt) {
  467. $responseid = 'response_'.$answer->id;
  468. $mform->addElement('hidden', 'response['.$answer->id.']', htmlspecialchars($useranswers[$i]));
  469. // Temporary fixed until MDL-38885 gets integrated
  470. $mform->setType('response', PARAM_TEXT);
  471. }
  472. $mform->addElement('select', $responseid, format_text($answer->answer,$answer->answerformat,$options), $responseoptions, $disabled);
  473. $mform->setType($responseid, PARAM_TEXT);
  474. if ($hasattempt) {
  475. $mform->setDefault($responseid, htmlspecialchars(trim($useranswers[$i])));
  476. } else {
  477. $mform->setDefault($responseid, 'answeroption');
  478. }
  479. }
  480. $mform->addElement('html', '</div>');
  481. $i++;
  482. }
  483. if ($hasattempt) {
  484. $this->add_action_buttons(null, get_string("nextpage", "lesson"));
  485. } else {
  486. $this->add_action_buttons(null, get_string("submit", "lesson"));
  487. }
  488. }
  489. }