PageRenderTime 41ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/question/format/gift/format.php

https://bitbucket.org/kudutest1/moodlegit
PHP | 775 lines | 546 code | 114 blank | 115 comment | 147 complexity | 38e93c01eaa722f2a50a37ccc5cebd0c 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. * GIFT format question importer/exporter.
  18. *
  19. * @package qformat
  20. * @subpackage gift
  21. * @copyright 2003 Paul Tsuchido Shew
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. */
  24. defined('MOODLE_INTERNAL') || die();
  25. /**
  26. * The GIFT import filter was designed as an easy to use method
  27. * for teachers writing questions as a text file. It supports most
  28. * question types and the missing word format.
  29. *
  30. * Multiple Choice / Missing Word
  31. * Who's buried in Grant's tomb?{~Grant ~Jefferson =no one}
  32. * Grant is {~buried =entombed ~living} in Grant's tomb.
  33. * True-False:
  34. * Grant is buried in Grant's tomb.{FALSE}
  35. * Short-Answer.
  36. * Who's buried in Grant's tomb?{=no one =nobody}
  37. * Numerical
  38. * When was Ulysses S. Grant born?{#1822:5}
  39. * Matching
  40. * Match the following countries with their corresponding
  41. * capitals.{=Canada->Ottawa =Italy->Rome =Japan->Tokyo}
  42. *
  43. * Comment lines start with a double backslash (//).
  44. * Optional question names are enclosed in double colon(::).
  45. * Answer feedback is indicated with hash mark (#).
  46. * Percentage answer weights immediately follow the tilde (for
  47. * multiple choice) or equal sign (for short answer and numerical),
  48. * and are enclosed in percent signs (% %). See docs and examples.txt for more.
  49. *
  50. * This filter was written through the collaboration of numerous
  51. * members of the Moodle community. It was originally based on
  52. * the missingword format, which included code from Thomas Robb
  53. * and others. Paul Tsuchido Shew wrote this filter in December 2003.
  54. *
  55. * @copyright 2003 Paul Tsuchido Shew
  56. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  57. */
  58. class qformat_gift extends qformat_default {
  59. public function provide_import() {
  60. return true;
  61. }
  62. public function provide_export() {
  63. return true;
  64. }
  65. public function export_file_extension() {
  66. return '.txt';
  67. }
  68. protected function answerweightparser(&$answer) {
  69. $answer = substr($answer, 1); // removes initial %
  70. $end_position = strpos($answer, "%");
  71. $answer_weight = substr($answer, 0, $end_position); // gets weight as integer
  72. $answer_weight = $answer_weight/100; // converts to percent
  73. $answer = substr($answer, $end_position+1); // removes comment from answer
  74. return $answer_weight;
  75. }
  76. protected function commentparser($answer, $defaultformat) {
  77. $bits = explode('#', $answer, 2);
  78. $ans = $this->parse_text_with_format(trim($bits[0]), $defaultformat);
  79. if (count($bits) > 1) {
  80. $feedback = $this->parse_text_with_format(trim($bits[1]), $defaultformat);
  81. } else {
  82. $feedback = array('text' => '', 'format' => $defaultformat, 'files' => array());
  83. }
  84. return array($ans, $feedback);
  85. }
  86. protected function split_truefalse_comment($answer, $defaultformat) {
  87. $bits = explode('#', $answer, 3);
  88. $ans = $this->parse_text_with_format(trim($bits[0]), $defaultformat);
  89. if (count($bits) > 1) {
  90. $wrongfeedback = $this->parse_text_with_format(trim($bits[1]), $defaultformat);
  91. } else {
  92. $wrongfeedback = array('text' => '', 'format' => $defaultformat, 'files' => array());
  93. }
  94. if (count($bits) > 2) {
  95. $rightfeedback = $this->parse_text_with_format(trim($bits[2]), $defaultformat);
  96. } else {
  97. $rightfeedback = array('text' => '', 'format' => $defaultformat, 'files' => array());
  98. }
  99. return array($ans, $wrongfeedback, $rightfeedback);
  100. }
  101. protected function escapedchar_pre($string) {
  102. //Replaces escaped control characters with a placeholder BEFORE processing
  103. $escapedcharacters = array("\\:", "\\#", "\\=", "\\{", "\\}", "\\~", "\\n" ); //dlnsk
  104. $placeholders = array("&&058;", "&&035;", "&&061;", "&&123;", "&&125;", "&&126;", "&&010"); //dlnsk
  105. $string = str_replace("\\\\", "&&092;", $string);
  106. $string = str_replace($escapedcharacters, $placeholders, $string);
  107. $string = str_replace("&&092;", "\\", $string);
  108. return $string;
  109. }
  110. protected function escapedchar_post($string) {
  111. //Replaces placeholders with corresponding character AFTER processing is done
  112. $placeholders = array("&&058;", "&&035;", "&&061;", "&&123;", "&&125;", "&&126;", "&&010"); //dlnsk
  113. $characters = array(":", "#", "=", "{", "}", "~", "\n" ); //dlnsk
  114. $string = str_replace($placeholders, $characters, $string);
  115. return $string;
  116. }
  117. protected function check_answer_count($min, $answers, $text) {
  118. $countanswers = count($answers);
  119. if ($countanswers < $min) {
  120. $this->error(get_string('importminerror', 'qformat_gift'), $text);
  121. return false;
  122. }
  123. return true;
  124. }
  125. protected function parse_text_with_format($text, $defaultformat = FORMAT_MOODLE) {
  126. $result = array(
  127. 'text' => $text,
  128. 'format' => $defaultformat,
  129. 'files' => array(),
  130. );
  131. if (strpos($text, '[') === 0) {
  132. $formatend = strpos($text, ']');
  133. $result['format'] = $this->format_name_to_const(substr($text, 1, $formatend - 1));
  134. if ($result['format'] == -1) {
  135. $result['format'] = $defaultformat;
  136. } else {
  137. $result['text'] = substr($text, $formatend + 1);
  138. }
  139. }
  140. $result['text'] = trim($this->escapedchar_post($result['text']));
  141. return $result;
  142. }
  143. public function readquestion($lines) {
  144. // Given an array of lines known to define a question in this format, this function
  145. // converts it into a question object suitable for processing and insertion into Moodle.
  146. $question = $this->defaultquestion();
  147. $comment = NULL;
  148. // define replaced by simple assignment, stop redefine notices
  149. $gift_answerweight_regex = '/^%\-*([0-9]{1,2})\.?([0-9]*)%/';
  150. // REMOVED COMMENTED LINES and IMPLODE
  151. foreach ($lines as $key => $line) {
  152. $line = trim($line);
  153. if (substr($line, 0, 2) == '//') {
  154. $lines[$key] = ' ';
  155. }
  156. }
  157. $text = trim(implode(' ', $lines));
  158. if ($text == '') {
  159. return false;
  160. }
  161. // Substitute escaped control characters with placeholders
  162. $text = $this->escapedchar_pre($text);
  163. // Look for category modifier
  164. if (preg_match('~^\$CATEGORY:~', $text)) {
  165. // $newcategory = $matches[1];
  166. $newcategory = trim(substr($text, 10));
  167. // build fake question to contain category
  168. $question->qtype = 'category';
  169. $question->category = $newcategory;
  170. return $question;
  171. }
  172. // QUESTION NAME parser
  173. if (substr($text, 0, 2) == '::') {
  174. $text = substr($text, 2);
  175. $namefinish = strpos($text, '::');
  176. if ($namefinish === false) {
  177. $question->name = false;
  178. // name will be assigned after processing question text below
  179. } else {
  180. $questionname = substr($text, 0, $namefinish);
  181. $question->name = $this->clean_question_name($this->escapedchar_post($questionname));
  182. $text = trim(substr($text, $namefinish+2)); // Remove name from text
  183. }
  184. } else {
  185. $question->name = false;
  186. }
  187. // Find the answer section.
  188. $answerstart = strpos($text, '{');
  189. $answerfinish = strpos($text, '}');
  190. $description = false;
  191. if ($answerstart === false && $answerfinish === false) {
  192. // No answer means it's a description.
  193. $description = true;
  194. $answertext = '';
  195. $answerlength = 0;
  196. } else if ($answerstart === false || $answerfinish === false) {
  197. $this->error(get_string('braceerror', 'qformat_gift'), $text);
  198. return false;
  199. } else {
  200. $answerlength = $answerfinish - $answerstart;
  201. $answertext = trim(substr($text, $answerstart + 1, $answerlength - 1));
  202. }
  203. // Format the question text, without answer, inserting "_____" as necessary.
  204. if ($description) {
  205. $questiontext = $text;
  206. } else if (substr($text, -1) == "}") {
  207. // No blank line if answers follow question, outside of closing punctuation.
  208. $questiontext = substr_replace($text, "", $answerstart, $answerlength + 1);
  209. } else {
  210. // Inserts blank line for missing word format.
  211. $questiontext = substr_replace($text, "_____", $answerstart, $answerlength + 1);
  212. }
  213. // Look to see if there is any general feedback.
  214. $gfseparator = strrpos($answertext, '####');
  215. if ($gfseparator === false) {
  216. $generalfeedback = '';
  217. } else {
  218. $generalfeedback = substr($answertext, $gfseparator + 4);
  219. $answertext = trim(substr($answertext, 0, $gfseparator));
  220. }
  221. // Get questiontext format from questiontext.
  222. $text = $this->parse_text_with_format($questiontext);
  223. $question->questiontextformat = $text['format'];
  224. $question->questiontext = $text['text'];
  225. // Get generalfeedback format from questiontext.
  226. $text = $this->parse_text_with_format($generalfeedback, $question->questiontextformat);
  227. $question->generalfeedback = $text['text'];
  228. $question->generalfeedbackformat = $text['format'];
  229. // set question name if not already set
  230. if ($question->name === false) {
  231. $question->name = $this->create_default_question_name($question->questiontext, get_string('questionname', 'question'));
  232. }
  233. // determine QUESTION TYPE
  234. $question->qtype = NULL;
  235. // give plugins first try
  236. // plugins must promise not to intercept standard qtypes
  237. // MDL-12346, this could be called from lesson mod which has its own base class =(
  238. if (method_exists($this, 'try_importing_using_qtypes') && ($try_question = $this->try_importing_using_qtypes($lines, $question, $answertext))) {
  239. return $try_question;
  240. }
  241. if ($description) {
  242. $question->qtype = 'description';
  243. } else if ($answertext == '') {
  244. $question->qtype = 'essay';
  245. } else if ($answertext{0} == '#') {
  246. $question->qtype = 'numerical';
  247. } else if (strpos($answertext, '~') !== false) {
  248. // only Multiplechoice questions contain tilde ~
  249. $question->qtype = 'multichoice';
  250. } else if (strpos($answertext, '=') !== false
  251. && strpos($answertext, '->') !== false) {
  252. // only Matching contains both = and ->
  253. $question->qtype = 'match';
  254. } else { // either truefalse or shortanswer
  255. // truefalse question check
  256. $truefalse_check = $answertext;
  257. if (strpos($answertext, '#') > 0) {
  258. // strip comments to check for TrueFalse question
  259. $truefalse_check = trim(substr($answertext, 0, strpos($answertext,"#")));
  260. }
  261. $valid_tf_answers = array('T', 'TRUE', 'F', 'FALSE');
  262. if (in_array($truefalse_check, $valid_tf_answers)) {
  263. $question->qtype = 'truefalse';
  264. } else { // Must be shortanswer
  265. $question->qtype = 'shortanswer';
  266. }
  267. }
  268. if (!isset($question->qtype)) {
  269. $giftqtypenotset = get_string('giftqtypenotset', 'qformat_gift');
  270. $this->error($giftqtypenotset, $text);
  271. return false;
  272. }
  273. switch ($question->qtype) {
  274. case 'description':
  275. $question->defaultmark = 0;
  276. $question->length = 0;
  277. return $question;
  278. case 'essay':
  279. $question->responseformat = 'editor';
  280. $question->responsefieldlines = 15;
  281. $question->attachments = 0;
  282. $question->graderinfo = array(
  283. 'text' => '', 'format' => FORMAT_HTML, 'files' => array());
  284. return $question;
  285. case 'multichoice':
  286. if (strpos($answertext,"=") === false) {
  287. $question->single = 0; // multiple answers are enabled if no single answer is 100% correct
  288. } else {
  289. $question->single = 1; // only one answer allowed (the default)
  290. }
  291. $question = $this->add_blank_combined_feedback($question);
  292. $answertext = str_replace("=", "~=", $answertext);
  293. $answers = explode("~", $answertext);
  294. if (isset($answers[0])) {
  295. $answers[0] = trim($answers[0]);
  296. }
  297. if (empty($answers[0])) {
  298. array_shift($answers);
  299. }
  300. $countanswers = count($answers);
  301. if (!$this->check_answer_count(2, $answers, $text)) {
  302. return false;
  303. }
  304. foreach ($answers as $key => $answer) {
  305. $answer = trim($answer);
  306. // determine answer weight
  307. if ($answer[0] == '=') {
  308. $answer_weight = 1;
  309. $answer = substr($answer, 1);
  310. } else if (preg_match($gift_answerweight_regex, $answer)) { // check for properly formatted answer weight
  311. $answer_weight = $this->answerweightparser($answer);
  312. } else { //default, i.e., wrong anwer
  313. $answer_weight = 0;
  314. }
  315. list($question->answer[$key], $question->feedback[$key]) =
  316. $this->commentparser($answer, $question->questiontextformat);
  317. $question->fraction[$key] = $answer_weight;
  318. } // end foreach answer
  319. return $question;
  320. case 'match':
  321. $question = $this->add_blank_combined_feedback($question);
  322. $answers = explode('=', $answertext);
  323. if (isset($answers[0])) {
  324. $answers[0] = trim($answers[0]);
  325. }
  326. if (empty($answers[0])) {
  327. array_shift($answers);
  328. }
  329. if (!$this->check_answer_count(2,$answers,$text)) {
  330. return false;
  331. }
  332. foreach ($answers as $key => $answer) {
  333. $answer = trim($answer);
  334. if (strpos($answer, "->") === false) {
  335. $this->error(get_string('giftmatchingformat','qformat_gift'), $answer);
  336. return false;
  337. }
  338. $marker = strpos($answer, '->');
  339. $question->subquestions[$key] = $this->parse_text_with_format(
  340. substr($answer, 0, $marker), $question->questiontextformat);
  341. $question->subanswers[$key] = trim($this->escapedchar_post(
  342. substr($answer, $marker + 2)));
  343. }
  344. return $question;
  345. case 'truefalse':
  346. list($answer, $wrongfeedback, $rightfeedback) =
  347. $this->split_truefalse_comment($answertext, $question->questiontextformat);
  348. if ($answer['text'] == "T" OR $answer['text'] == "TRUE") {
  349. $question->correctanswer = 1;
  350. $question->feedbacktrue = $rightfeedback;
  351. $question->feedbackfalse = $wrongfeedback;
  352. } else {
  353. $question->correctanswer = 0;
  354. $question->feedbacktrue = $wrongfeedback;
  355. $question->feedbackfalse = $rightfeedback;
  356. }
  357. $question->penalty = 1;
  358. return $question;
  359. case 'shortanswer':
  360. // Shortanswer question.
  361. $answers = explode("=", $answertext);
  362. if (isset($answers[0])) {
  363. $answers[0] = trim($answers[0]);
  364. }
  365. if (empty($answers[0])) {
  366. array_shift($answers);
  367. }
  368. if (!$this->check_answer_count(1, $answers, $text)) {
  369. return false;
  370. }
  371. foreach ($answers as $key => $answer) {
  372. $answer = trim($answer);
  373. // Answer weight
  374. if (preg_match($gift_answerweight_regex, $answer)) { // check for properly formatted answer weight
  375. $answer_weight = $this->answerweightparser($answer);
  376. } else { //default, i.e., full-credit anwer
  377. $answer_weight = 1;
  378. }
  379. list($answer, $question->feedback[$key]) = $this->commentparser(
  380. $answer, $question->questiontextformat);
  381. $question->answer[$key] = $answer['text'];
  382. $question->fraction[$key] = $answer_weight;
  383. }
  384. return $question;
  385. case 'numerical':
  386. // Note similarities to ShortAnswer
  387. $answertext = substr($answertext, 1); // remove leading "#"
  388. // If there is feedback for a wrong answer, store it for now.
  389. if (($pos = strpos($answertext, '~')) !== false) {
  390. $wrongfeedback = substr($answertext, $pos);
  391. $answertext = substr($answertext, 0, $pos);
  392. } else {
  393. $wrongfeedback = '';
  394. }
  395. $answers = explode("=", $answertext);
  396. if (isset($answers[0])) {
  397. $answers[0] = trim($answers[0]);
  398. }
  399. if (empty($answers[0])) {
  400. array_shift($answers);
  401. }
  402. if (count($answers) == 0) {
  403. // invalid question
  404. $giftnonumericalanswers = get_string('giftnonumericalanswers','qformat_gift');
  405. $this->error($giftnonumericalanswers, $text);
  406. return false;
  407. }
  408. foreach ($answers as $key => $answer) {
  409. $answer = trim($answer);
  410. // Answer weight
  411. if (preg_match($gift_answerweight_regex, $answer)) { // check for properly formatted answer weight
  412. $answer_weight = $this->answerweightparser($answer);
  413. } else { //default, i.e., full-credit anwer
  414. $answer_weight = 1;
  415. }
  416. list($answer, $question->feedback[$key]) = $this->commentparser(
  417. $answer, $question->questiontextformat);
  418. $question->fraction[$key] = $answer_weight;
  419. $answer = $answer['text'];
  420. //Calculate Answer and Min/Max values
  421. if (strpos($answer,"..") > 0) { // optional [min]..[max] format
  422. $marker = strpos($answer,"..");
  423. $max = trim(substr($answer, $marker+2));
  424. $min = trim(substr($answer, 0, $marker));
  425. $ans = ($max + $min)/2;
  426. $tol = $max - $ans;
  427. } else if (strpos($answer, ':') > 0) { // standard [answer]:[errormargin] format
  428. $marker = strpos($answer, ':');
  429. $tol = trim(substr($answer, $marker+1));
  430. $ans = trim(substr($answer, 0, $marker));
  431. } else { // only one valid answer (zero errormargin)
  432. $tol = 0;
  433. $ans = trim($answer);
  434. }
  435. if (!(is_numeric($ans) || $ans = '*') || !is_numeric($tol)) {
  436. $errornotnumbers = get_string('errornotnumbers');
  437. $this->error($errornotnumbers, $text);
  438. return false;
  439. }
  440. // store results
  441. $question->answer[$key] = $ans;
  442. $question->tolerance[$key] = $tol;
  443. }
  444. if ($wrongfeedback) {
  445. $key += 1;
  446. $question->fraction[$key] = 0;
  447. list($notused, $question->feedback[$key]) = $this->commentparser(
  448. $wrongfeedback, $question->questiontextformat);
  449. $question->answer[$key] = '*';
  450. $question->tolerance[$key] = '';
  451. }
  452. return $question;
  453. default:
  454. $this->error(get_string('giftnovalidquestion', 'qformat_gift'), $text);
  455. return false;
  456. }
  457. }
  458. protected function repchar($text, $notused = 0) {
  459. // Escapes 'reserved' characters # = ~ {) :
  460. // Removes new lines
  461. $reserved = array( '\\', '#', '=', '~', '{', '}', ':', "\n", "\r");
  462. $escaped = array('\\\\', '\#','\=','\~','\{','\}','\:', '\n', '' );
  463. $newtext = str_replace($reserved, $escaped, $text);
  464. return $newtext;
  465. }
  466. /**
  467. * @param int $format one of the FORMAT_ constants.
  468. * @return string the corresponding name.
  469. */
  470. protected function format_const_to_name($format) {
  471. if ($format == FORMAT_MOODLE) {
  472. return 'moodle';
  473. } else if ($format == FORMAT_HTML) {
  474. return 'html';
  475. } else if ($format == FORMAT_PLAIN) {
  476. return 'plain';
  477. } else if ($format == FORMAT_MARKDOWN) {
  478. return 'markdown';
  479. } else {
  480. return 'moodle';
  481. }
  482. }
  483. /**
  484. * @param int $format one of the FORMAT_ constants.
  485. * @return string the corresponding name.
  486. */
  487. protected function format_name_to_const($format) {
  488. if ($format == 'moodle') {
  489. return FORMAT_MOODLE;
  490. } else if ($format == 'html') {
  491. return FORMAT_HTML;
  492. } else if ($format == 'plain') {
  493. return FORMAT_PLAIN;
  494. } else if ($format == 'markdown') {
  495. return FORMAT_MARKDOWN;
  496. } else {
  497. return -1;
  498. }
  499. }
  500. public function write_name($name) {
  501. return '::' . $this->repchar($name) . '::';
  502. }
  503. public function write_questiontext($text, $format, $defaultformat = FORMAT_MOODLE) {
  504. $output = '';
  505. if ($text != '' && $format != $defaultformat) {
  506. $output .= '[' . $this->format_const_to_name($format) . ']';
  507. }
  508. $output .= $this->repchar($text, $format);
  509. return $output;
  510. }
  511. /**
  512. * Outputs the general feedback for the question, if any. This needs to be the
  513. * last thing before the }.
  514. * @param object $question the question data.
  515. * @param string $indent to put before the general feedback. Defaults to a tab.
  516. * If this is not blank, a newline is added after the line.
  517. */
  518. public function write_general_feedback($question, $indent = "\t") {
  519. $generalfeedback = $this->write_questiontext($question->generalfeedback,
  520. $question->generalfeedbackformat, $question->questiontextformat);
  521. if ($generalfeedback) {
  522. $generalfeedback = '####' . $generalfeedback;
  523. if ($indent) {
  524. $generalfeedback = $indent . $generalfeedback . "\n";
  525. }
  526. }
  527. return $generalfeedback;
  528. }
  529. public function writequestion($question) {
  530. global $OUTPUT;
  531. // Start with a comment
  532. $expout = "// question: $question->id name: $question->name\n";
  533. // output depends on question type
  534. switch($question->qtype) {
  535. case 'category':
  536. // not a real question, used to insert category switch
  537. $expout .= "\$CATEGORY: $question->category\n";
  538. break;
  539. case 'description':
  540. $expout .= $this->write_name($question->name);
  541. $expout .= $this->write_questiontext($question->questiontext, $question->questiontextformat);
  542. break;
  543. case 'essay':
  544. $expout .= $this->write_name($question->name);
  545. $expout .= $this->write_questiontext($question->questiontext, $question->questiontextformat);
  546. $expout .= "{";
  547. $expout .= $this->write_general_feedback($question, '');
  548. $expout .= "}\n";
  549. break;
  550. case 'truefalse':
  551. $trueanswer = $question->options->answers[$question->options->trueanswer];
  552. $falseanswer = $question->options->answers[$question->options->falseanswer];
  553. if ($trueanswer->fraction == 1) {
  554. $answertext = 'TRUE';
  555. $rightfeedback = $this->write_questiontext($trueanswer->feedback,
  556. $trueanswer->feedbackformat, $question->questiontextformat);
  557. $wrongfeedback = $this->write_questiontext($falseanswer->feedback,
  558. $falseanswer->feedbackformat, $question->questiontextformat);
  559. } else {
  560. $answertext = 'FALSE';
  561. $rightfeedback = $this->write_questiontext($falseanswer->feedback,
  562. $falseanswer->feedbackformat, $question->questiontextformat);
  563. $wrongfeedback = $this->write_questiontext($trueanswer->feedback,
  564. $trueanswer->feedbackformat, $question->questiontextformat);
  565. }
  566. $expout .= $this->write_name($question->name);
  567. $expout .= $this->write_questiontext($question->questiontext, $question->questiontextformat);
  568. $expout .= '{' . $this->repchar($answertext);
  569. if ($wrongfeedback) {
  570. $expout .= '#' . $wrongfeedback;
  571. } else if ($rightfeedback) {
  572. $expout .= '#';
  573. }
  574. if ($rightfeedback) {
  575. $expout .= '#' . $rightfeedback;
  576. }
  577. $expout .= $this->write_general_feedback($question, '');
  578. $expout .= "}\n";
  579. break;
  580. case 'multichoice':
  581. $expout .= $this->write_name($question->name);
  582. $expout .= $this->write_questiontext($question->questiontext, $question->questiontextformat);
  583. $expout .= "{\n";
  584. foreach($question->options->answers as $answer) {
  585. if ($answer->fraction == 1) {
  586. $answertext = '=';
  587. } else if ($answer->fraction == 0) {
  588. $answertext = '~';
  589. } else {
  590. $weight = $answer->fraction * 100;
  591. $answertext = '~%' . $weight . '%';
  592. }
  593. $expout .= "\t" . $answertext . $this->write_questiontext($answer->answer,
  594. $answer->answerformat, $question->questiontextformat);
  595. if ($answer->feedback != '') {
  596. $expout .= '#' . $this->write_questiontext($answer->feedback,
  597. $answer->feedbackformat, $question->questiontextformat);
  598. }
  599. $expout .= "\n";
  600. }
  601. $expout .= $this->write_general_feedback($question);
  602. $expout .= "}\n";
  603. break;
  604. case 'shortanswer':
  605. $expout .= $this->write_name($question->name);
  606. $expout .= $this->write_questiontext($question->questiontext, $question->questiontextformat);
  607. $expout .= "{\n";
  608. foreach($question->options->answers as $answer) {
  609. $weight = 100 * $answer->fraction;
  610. $expout .= "\t=%" . $weight . '%' . $this->repchar($answer->answer) .
  611. '#' . $this->write_questiontext($answer->feedback,
  612. $answer->feedbackformat, $question->questiontextformat) . "\n";
  613. }
  614. $expout .= $this->write_general_feedback($question);
  615. $expout .= "}\n";
  616. break;
  617. case 'numerical':
  618. $expout .= $this->write_name($question->name);
  619. $expout .= $this->write_questiontext($question->questiontext, $question->questiontextformat);
  620. $expout .= "{#\n";
  621. foreach ($question->options->answers as $answer) {
  622. if ($answer->answer != '' && $answer->answer != '*') {
  623. $weight = 100 * $answer->fraction;
  624. $expout .= "\t=%" . $weight . '%' . $answer->answer . ':' .
  625. (float)$answer->tolerance . '#' . $this->write_questiontext($answer->feedback,
  626. $answer->feedbackformat, $question->questiontextformat) . "\n";
  627. } else {
  628. $expout .= "\t~#" . $this->write_questiontext($answer->feedback,
  629. $answer->feedbackformat, $question->questiontextformat) . "\n";
  630. }
  631. }
  632. $expout .= $this->write_general_feedback($question);
  633. $expout .= "}\n";
  634. break;
  635. case 'match':
  636. $expout .= $this->write_name($question->name);
  637. $expout .= $this->write_questiontext($question->questiontext, $question->questiontextformat);
  638. $expout .= "{\n";
  639. foreach($question->options->subquestions as $subquestion) {
  640. $expout .= "\t=" . $this->write_questiontext($subquestion->questiontext,
  641. $subquestion->questiontextformat, $question->questiontextformat) .
  642. ' -> ' . $this->repchar($subquestion->answertext) . "\n";
  643. }
  644. $expout .= $this->write_general_feedback($question);
  645. $expout .= "}\n";
  646. break;
  647. default:
  648. // Check for plugins
  649. if ($out = $this->try_exporting_using_qtypes($question->qtype, $question)) {
  650. $expout .= $out;
  651. } else {
  652. $expout .= "Question type $question->qtype is not supported\n";
  653. echo $OUTPUT->notification(get_string('nohandler', 'qformat_gift',
  654. question_bank::get_qtype_name($question->qtype)));
  655. }
  656. }
  657. // Add empty line to delimit questions
  658. $expout .= "\n";
  659. return $expout;
  660. }
  661. }