PageRenderTime 58ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/question/format.php

https://bitbucket.org/ceu/moodle_demo
PHP | 879 lines | 480 code | 116 blank | 283 comment | 76 complexity | 35733478a44be4d180bd5063e4125f22 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.0, LGPL-2.1
  1. <?php // $Id: format.php,v 1.35.2.18 2011/08/18 00:53:35 moodlerobot Exp $
  2. /**
  3. * Base class for question import and export formats.
  4. *
  5. * @author Martin Dougiamas, Howard Miller, and many others.
  6. * {@link http://moodle.org}
  7. * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  8. * @package questionbank
  9. * @subpackage importexport
  10. */
  11. class qformat_default {
  12. var $displayerrors = true;
  13. var $category = NULL;
  14. var $questions = array();
  15. var $course = NULL;
  16. var $filename = '';
  17. var $realfilename = '';
  18. var $matchgrades = 'error';
  19. var $catfromfile = 0;
  20. var $contextfromfile = 0;
  21. var $cattofile = 0;
  22. var $contexttofile = 0;
  23. var $questionids = array();
  24. var $importerrors = 0;
  25. var $stoponerror = true;
  26. var $translator = null;
  27. var $canaccessbackupdata = true;
  28. // functions to indicate import/export functionality
  29. // override to return true if implemented
  30. function provide_import() {
  31. return false;
  32. }
  33. function provide_export() {
  34. return false;
  35. }
  36. // Accessor methods
  37. /**
  38. * set the category
  39. * @param object category the category object
  40. */
  41. function setCategory( $category ) {
  42. if (count($this->questions)){
  43. debugging('You shouldn\'t call setCategory after setQuestions');
  44. }
  45. $this->category = $category;
  46. }
  47. /**
  48. * Set the specific questions to export. Should not include questions with
  49. * parents (sub questions of cloze question type).
  50. * Only used for question export.
  51. * @param array of question objects
  52. */
  53. function setQuestions( $questions ) {
  54. if ($this->category !== null){
  55. debugging('You shouldn\'t call setQuestions after setCategory');
  56. }
  57. $this->questions = $questions;
  58. }
  59. /**
  60. * set the course class variable
  61. * @param course object Moodle course variable
  62. */
  63. function setCourse( $course ) {
  64. $this->course = $course;
  65. }
  66. /**
  67. * set an array of contexts.
  68. * @param array $contexts Moodle course variable
  69. */
  70. function setContexts($contexts) {
  71. $this->contexts = $contexts;
  72. $this->translator = new context_to_string_translator($this->contexts);
  73. }
  74. /**
  75. * set the filename
  76. * @param string filename name of file to import/export
  77. */
  78. function setFilename( $filename ) {
  79. $this->filename = $filename;
  80. }
  81. /**
  82. * set the "real" filename
  83. * (this is what the user typed, regardless of wha happened next)
  84. * @param string realfilename name of file as typed by user
  85. */
  86. function setRealfilename( $realfilename ) {
  87. $this->realfilename = $realfilename;
  88. }
  89. /**
  90. * set matchgrades
  91. * @param string matchgrades error or nearest for grades
  92. */
  93. function setMatchgrades( $matchgrades ) {
  94. $this->matchgrades = $matchgrades;
  95. }
  96. /**
  97. * set catfromfile
  98. * @param bool catfromfile allow categories embedded in import file
  99. */
  100. function setCatfromfile( $catfromfile ) {
  101. $this->catfromfile = $catfromfile;
  102. }
  103. /**
  104. * set contextfromfile
  105. * @param bool $contextfromfile allow contexts embedded in import file
  106. */
  107. function setContextfromfile($contextfromfile) {
  108. $this->contextfromfile = $contextfromfile;
  109. }
  110. /**
  111. * set cattofile
  112. * @param bool cattofile exports categories within export file
  113. */
  114. function setCattofile( $cattofile ) {
  115. $this->cattofile = $cattofile;
  116. }
  117. /**
  118. * set contexttofile
  119. * @param bool cattofile exports categories within export file
  120. */
  121. function setContexttofile($contexttofile) {
  122. $this->contexttofile = $contexttofile;
  123. }
  124. /**
  125. * set stoponerror
  126. * @param bool stoponerror stops database write if any errors reported
  127. */
  128. function setStoponerror( $stoponerror ) {
  129. $this->stoponerror = $stoponerror;
  130. }
  131. /**
  132. * @param boolean $canaccess Whether the current use can access the backup data folder. Determines
  133. * where export files are saved.
  134. */
  135. function set_can_access_backupdata($canaccess) {
  136. $this->canaccessbackupdata = $canaccess;
  137. }
  138. /***********************
  139. * IMPORTING FUNCTIONS
  140. ***********************/
  141. /**
  142. * Handle parsing error
  143. */
  144. function error( $message, $text='', $questionname='' ) {
  145. $importerrorquestion = get_string('importerrorquestion','quiz');
  146. echo "<div class=\"importerror\">\n";
  147. echo "<strong>$importerrorquestion $questionname</strong>";
  148. if (!empty($text)) {
  149. $text = s($text);
  150. echo "<blockquote>$text</blockquote>\n";
  151. }
  152. echo "<strong>$message</strong>\n";
  153. echo "</div>";
  154. $this->importerrors++;
  155. }
  156. /**
  157. * Import for questiontype plugins
  158. * Do not override.
  159. * @param data mixed The segment of data containing the question
  160. * @param question object processed (so far) by standard import code if appropriate
  161. * @param extra mixed any additional format specific data that may be passed by the format
  162. * @param qtypehint hint about a question type from format
  163. * @return object question object suitable for save_options() or false if cannot handle
  164. */
  165. function try_importing_using_qtypes( $data, $question=null, $extra=null, $qtypehint='') {
  166. global $QTYPES;
  167. // work out what format we are using
  168. $formatname = substr(get_class($this), strlen('qformat_'));
  169. $methodname = "import_from_$formatname";
  170. //first try importing using a hint from format
  171. if (!empty($qtypehint)) {
  172. $qtype = $QTYPES[$qtypehint];
  173. if (is_object($qtype) && method_exists($qtype, $methodname)) {
  174. $question = $qtype->$methodname($data, $question, $this, $extra);
  175. if ($question) {
  176. return $question;
  177. }
  178. }
  179. }
  180. // loop through installed questiontypes checking for
  181. // function to handle this question
  182. foreach ($QTYPES as $qtype) {
  183. if (method_exists( $qtype, $methodname)) {
  184. if ($question = $qtype->$methodname( $data, $question, $this, $extra )) {
  185. return $question;
  186. }
  187. }
  188. }
  189. return false;
  190. }
  191. /**
  192. * Perform any required pre-processing
  193. * @return boolean success
  194. */
  195. function importpreprocess() {
  196. return true;
  197. }
  198. /**
  199. * Process the file
  200. * This method should not normally be overidden
  201. * @return boolean success
  202. */
  203. function importprocess() {
  204. global $USER;
  205. // reset the timer in case file upload was slow
  206. @set_time_limit();
  207. // STAGE 1: Parse the file
  208. notify( get_string('parsingquestions','quiz') );
  209. if (! $lines = $this->readdata($this->filename)) {
  210. notify( get_string('cannotread','quiz') );
  211. return false;
  212. }
  213. if (! $questions = $this->readquestions($lines)) { // Extract all the questions
  214. notify( get_string('noquestionsinfile','quiz') );
  215. return false;
  216. }
  217. // STAGE 2: Write data to database
  218. notify( get_string('importingquestions','quiz',$this->count_questions($questions)) );
  219. // check for errors before we continue
  220. if ($this->stoponerror and ($this->importerrors>0)) {
  221. notify( get_string('importparseerror','quiz') );
  222. return true;
  223. }
  224. // get list of valid answer grades
  225. $grades = get_grade_options();
  226. $gradeoptionsfull = $grades->gradeoptionsfull;
  227. // check answer grades are valid
  228. // (now need to do this here because of 'stop on error': MDL-10689)
  229. $gradeerrors = 0;
  230. $goodquestions = array();
  231. foreach ($questions as $question) {
  232. if (!empty($question->fraction) and (is_array($question->fraction))) {
  233. $fractions = $question->fraction;
  234. $answersvalid = true; // in case they are!
  235. foreach ($fractions as $key => $fraction) {
  236. $newfraction = match_grade_options($gradeoptionsfull, $fraction, $this->matchgrades);
  237. if ($newfraction===false) {
  238. $answersvalid = false;
  239. }
  240. else {
  241. $fractions[$key] = $newfraction;
  242. }
  243. }
  244. if (!$answersvalid) {
  245. notify(get_string('matcherror', 'quiz'));
  246. ++$gradeerrors;
  247. continue;
  248. }
  249. else {
  250. $question->fraction = $fractions;
  251. }
  252. }
  253. $goodquestions[] = $question;
  254. }
  255. $questions = $goodquestions;
  256. // check for errors before we continue
  257. if ($this->stoponerror and ($gradeerrors>0)) {
  258. return false;
  259. }
  260. // count number of questions processed
  261. $count = 0;
  262. foreach ($questions as $question) { // Process and store each question
  263. // reset the php timeout
  264. @set_time_limit();
  265. // check for category modifiers
  266. if ($question->qtype=='category') {
  267. if ($this->catfromfile) {
  268. // find/create category object
  269. $catpath = $question->category;
  270. $newcategory = $this->create_category_path($catpath);
  271. if (!empty($newcategory)) {
  272. $this->category = $newcategory;
  273. }
  274. }
  275. continue;
  276. }
  277. $count++;
  278. echo "<hr /><p><b>$count</b>. ".$this->format_question_text($question)."</p>";
  279. $question->category = $this->category->id;
  280. $question->stamp = make_unique_id_code(); // Set the unique code (not to be changed)
  281. $question->createdby = $USER->id;
  282. $question->timecreated = time();
  283. $question->modifiedby = $USER->id;
  284. $question->timemodified = time();
  285. if (!$question->id = insert_record("question", $question)) {
  286. error( get_string('cannotinsert','quiz') );
  287. }
  288. $this->questionids[] = $question->id;
  289. // Now to save all the answers and type-specific options
  290. global $QTYPES;
  291. $result = $QTYPES[$question->qtype]
  292. ->save_question_options($question);
  293. if (!empty($result->error)) {
  294. notify($result->error);
  295. return false;
  296. }
  297. if (!empty($result->notice)) {
  298. notify($result->notice);
  299. return true;
  300. }
  301. // Give the question a unique version stamp determined by question_hash()
  302. set_field('question', 'version', question_hash($question), 'id', $question->id);
  303. }
  304. return true;
  305. }
  306. /**
  307. * Count all non-category questions in the questions array.
  308. *
  309. * @param array questions An array of question objects.
  310. * @return int The count.
  311. *
  312. */
  313. function count_questions($questions) {
  314. $count = 0;
  315. if (!is_array($questions)) {
  316. return $count;
  317. }
  318. foreach ($questions as $question) {
  319. if (!is_object($question) || !isset($question->qtype) || ($question->qtype == 'category')) {
  320. continue;
  321. }
  322. $count++;
  323. }
  324. return $count;
  325. }
  326. /**
  327. * find and/or create the category described by a delimited list
  328. * e.g. $course$/tom/dick/harry or tom/dick/harry
  329. *
  330. * removes any context string no matter whether $getcontext is set
  331. * but if $getcontext is set then ignore the context and use selected category context.
  332. *
  333. * @param string catpath delimited category path
  334. * @param int courseid course to search for categories
  335. * @return mixed category object or null if fails
  336. */
  337. function create_category_path($catpath) {
  338. $catnames = $this->split_category_path($catpath);
  339. $parent = 0;
  340. $category = null;
  341. // check for context id in path, it might not be there in pre 1.9 exports
  342. $matchcount = preg_match('/^\$([a-z]+)\$$/', $catnames[0], $matches);
  343. if ($matchcount == 1) {
  344. $contextid = $this->translator->string_to_context($matches[1]);
  345. array_shift($catnames);
  346. } else {
  347. $contextid = false;
  348. }
  349. if ($this->contextfromfile && $contextid !== false) {
  350. $context = get_context_instance_by_id($contextid);
  351. require_capability('moodle/question:add', $context);
  352. } else {
  353. $context = get_context_instance_by_id($this->category->contextid);
  354. }
  355. // Now create any categories that need to be created.
  356. foreach ($catnames as $catname) {
  357. if ($category = get_record('question_categories', 'name', $catname, 'contextid', $context->id, 'parent', $parent)) {
  358. $parent = $category->id;
  359. } else {
  360. require_capability('moodle/question:managecategory', $context);
  361. // create the new category
  362. $category = new object;
  363. $category->contextid = $context->id;
  364. $category->name = $catname;
  365. $category->info = '';
  366. $category->parent = $parent;
  367. $category->sortorder = 999;
  368. $category->stamp = make_unique_id_code();
  369. if (!($id = insert_record('question_categories', $category))) {
  370. error( "cannot create new category - $catname" );
  371. }
  372. $category->id = $id;
  373. $parent = $id;
  374. }
  375. }
  376. return $category;
  377. }
  378. /**
  379. * Return complete file within an array, one item per line
  380. * @param string filename name of file
  381. * @return mixed contents array or false on failure
  382. */
  383. function readdata($filename) {
  384. if (is_readable($filename)) {
  385. $filearray = file($filename);
  386. /// Check for Macintosh OS line returns (ie file on one line), and fix
  387. if (ereg("\r", $filearray[0]) AND !ereg("\n", $filearray[0])) {
  388. return explode("\r", $filearray[0]);
  389. } else {
  390. return $filearray;
  391. }
  392. }
  393. return false;
  394. }
  395. /**
  396. * Parses an array of lines into an array of questions,
  397. * where each item is a question object as defined by
  398. * readquestion(). Questions are defined as anything
  399. * between blank lines.
  400. *
  401. * If your format does not use blank lines as a delimiter
  402. * then you will need to override this method. Even then
  403. * try to use readquestion for each question
  404. * @param array lines array of lines from readdata
  405. * @return array array of question objects
  406. */
  407. function readquestions($lines) {
  408. $questions = array();
  409. $currentquestion = array();
  410. foreach ($lines as $line) {
  411. $line = trim($line);
  412. if (empty($line)) {
  413. if (!empty($currentquestion)) {
  414. if ($question = $this->readquestion($currentquestion)) {
  415. $questions[] = $question;
  416. }
  417. $currentquestion = array();
  418. }
  419. } else {
  420. $currentquestion[] = $line;
  421. }
  422. }
  423. if (!empty($currentquestion)) { // There may be a final question
  424. if ($question = $this->readquestion($currentquestion)) {
  425. $questions[] = $question;
  426. }
  427. }
  428. return $questions;
  429. }
  430. /**
  431. * return an "empty" question
  432. * Somewhere to specify question parameters that are not handled
  433. * by import but are required db fields.
  434. * This should not be overridden.
  435. * @return object default question
  436. */
  437. function defaultquestion() {
  438. global $CFG;
  439. $question = new stdClass();
  440. $question->shuffleanswers = $CFG->quiz_shuffleanswers;
  441. $question->defaultgrade = 1;
  442. $question->image = "";
  443. $question->usecase = 0;
  444. $question->multiplier = array();
  445. $question->generalfeedback = '';
  446. $question->correctfeedback = '';
  447. $question->partiallycorrectfeedback = '';
  448. $question->incorrectfeedback = '';
  449. $question->answernumbering = 'abc';
  450. $question->penalty = 0.1;
  451. $question->length = 1;
  452. // this option in case the questiontypes class wants
  453. // to know where the data came from
  454. $question->export_process = true;
  455. $question->import_process = true;
  456. return $question;
  457. }
  458. /**
  459. * Given the data known to define a question in
  460. * this format, this function converts it into a question
  461. * object suitable for processing and insertion into Moodle.
  462. *
  463. * If your format does not use blank lines to delimit questions
  464. * (e.g. an XML format) you must override 'readquestions' too
  465. * @param $lines mixed data that represents question
  466. * @return object question object
  467. */
  468. function readquestion($lines) {
  469. $formatnotimplemented = get_string( 'formatnotimplemented','quiz' );
  470. echo "<p>$formatnotimplemented</p>";
  471. return NULL;
  472. }
  473. /**
  474. * Override if any post-processing is required
  475. * @return boolean success
  476. */
  477. function importpostprocess() {
  478. return true;
  479. }
  480. /**
  481. * Import an image file encoded in base64 format
  482. * @param string path path (in course data) to store picture
  483. * @param string base64 encoded picture
  484. * @return string filename (nb. collisions are handled)
  485. */
  486. function importimagefile( $path, $base64 ) {
  487. global $CFG;
  488. // all this to get the destination directory
  489. // and filename!
  490. $fullpath = "{$CFG->dataroot}/{$this->course->id}/$path";
  491. $path_parts = pathinfo( $fullpath );
  492. $destination = $path_parts['dirname'];
  493. $file = clean_filename( $path_parts['basename'] );
  494. // check if path exists
  495. check_dir_exists($destination, true, true );
  496. // detect and fix any filename collision - get unique filename
  497. $newfiles = resolve_filename_collisions( $destination, array($file) );
  498. $newfile = $newfiles[0];
  499. // convert and save file contents
  500. if (!$content = base64_decode( $base64 )) {
  501. return '';
  502. }
  503. $newfullpath = "$destination/$newfile";
  504. if (!$fh = fopen( $newfullpath, 'w' )) {
  505. return '';
  506. }
  507. if (!fwrite( $fh, $content )) {
  508. return '';
  509. }
  510. fclose( $fh );
  511. // return the (possibly) new filename
  512. $newfile = ereg_replace("{$CFG->dataroot}/{$this->course->id}/", '',$newfullpath);
  513. return $newfile;
  514. }
  515. /*******************
  516. * EXPORT FUNCTIONS
  517. *******************/
  518. /**
  519. * Provide export functionality for plugin questiontypes
  520. * Do not override
  521. * @param name questiontype name
  522. * @param question object data to export
  523. * @param extra mixed any addition format specific data needed
  524. * @return string the data to append to export or false if error (or unhandled)
  525. */
  526. function try_exporting_using_qtypes( $name, $question, $extra=null ) {
  527. global $QTYPES;
  528. // work out the name of format in use
  529. $formatname = substr( get_class( $this ), strlen( 'qformat_' ));
  530. $methodname = "export_to_$formatname";
  531. if (array_key_exists( $name, $QTYPES )) {
  532. $qtype = $QTYPES[ $name ];
  533. if (method_exists( $qtype, $methodname )) {
  534. if ($data = $qtype->$methodname( $question, $this, $extra )) {
  535. return $data;
  536. }
  537. }
  538. }
  539. return false;
  540. }
  541. /**
  542. * Return the files extension appropriate for this type
  543. * override if you don't want .txt
  544. * @return string file extension
  545. */
  546. function export_file_extension() {
  547. return ".txt";
  548. }
  549. /**
  550. * Do any pre-processing that may be required
  551. * @param boolean success
  552. */
  553. function exportpreprocess() {
  554. return true;
  555. }
  556. /**
  557. * Enable any processing to be done on the content
  558. * just prior to the file being saved
  559. * default is to do nothing
  560. * @param string output text
  561. * @param string processed output text
  562. */
  563. function presave_process( $content ) {
  564. return $content;
  565. }
  566. /**
  567. * Do the export
  568. * For most types this should not need to be overrided
  569. * @return boolean success
  570. */
  571. function exportprocess() {
  572. global $CFG;
  573. // create a directory for the exports (if not already existing)
  574. if (! $export_dir = make_upload_directory($this->question_get_export_dir())) {
  575. error( get_string('cannotcreatepath','quiz',$export_dir) );
  576. }
  577. $path = $CFG->dataroot.'/'.$this->question_get_export_dir();
  578. // get the questions (from database) in this category
  579. // only get q's with no parents (no cloze subquestions specifically)
  580. if ($this->category){
  581. $questions = get_questions_category( $this->category, true );
  582. } else {
  583. $questions = $this->questions;
  584. }
  585. notify( get_string('exportingquestions','quiz') );
  586. $count = 0;
  587. // results are first written into string (and then to a file)
  588. // so create/initialize the string here
  589. $expout = "";
  590. // track which category questions are in
  591. // if it changes we will record the category change in the output
  592. // file if selected. 0 means that it will get printed before the 1st question
  593. $trackcategory = 0;
  594. // iterate through questions
  595. foreach($questions as $question) {
  596. // do not export hidden questions
  597. if (!empty($question->hidden)) {
  598. continue;
  599. }
  600. // do not export random questions
  601. if ($question->qtype==RANDOM) {
  602. continue;
  603. }
  604. // check if we need to record category change
  605. if ($this->cattofile) {
  606. if ($question->category != $trackcategory) {
  607. $trackcategory = $question->category;
  608. $categoryname = $this->get_category_path($trackcategory, $this->contexttofile);
  609. // create 'dummy' question for category export
  610. $dummyquestion = new object;
  611. $dummyquestion->qtype = 'category';
  612. $dummyquestion->category = $categoryname;
  613. $dummyquestion->name = 'Switch category to ' . $categoryname;
  614. $dummyquestion->id = 0;
  615. $dummyquestion->questiontextformat = '';
  616. $expout .= $this->writequestion($dummyquestion) . "\n";
  617. }
  618. }
  619. // export the question displaying message
  620. $count++;
  621. echo "<hr /><p><b>$count</b>. ".$this->format_question_text($question)."</p>";
  622. if (question_has_capability_on($question, 'view', $question->category)){
  623. $expout .= $this->writequestion( $question ) . "\n";
  624. }
  625. }
  626. // continue path for following error checks
  627. $course = $this->course;
  628. $continuepath = "$CFG->wwwroot/question/export.php?courseid=$course->id";
  629. // did we actually process anything
  630. if ($count==0) {
  631. print_error( 'noquestions','quiz',$continuepath );
  632. }
  633. // final pre-process on exported data
  634. $expout = $this->presave_process( $expout );
  635. // write file
  636. $filepath = $path."/".$this->filename . $this->export_file_extension();
  637. if (!$fh=fopen($filepath,"w")) {
  638. print_error( 'cannotopen','quiz',$continuepath,$filepath );
  639. }
  640. if (!fwrite($fh, $expout, strlen($expout) )) {
  641. print_error( 'cannotwrite','quiz',$continuepath,$filepath );
  642. }
  643. fclose($fh);
  644. return true;
  645. }
  646. /**
  647. * get the category as a path (e.g., tom/dick/harry)
  648. * @param int id the id of the most nested catgory
  649. * @return string the path
  650. */
  651. function get_category_path($id, $includecontext = true) {
  652. if (!$category = get_record('question_categories','id',$id)) {
  653. error('Error getting category record from db - ' . $id);
  654. }
  655. $contextstring = $this->translator->context_to_string($category->contextid);
  656. $pathsections = array();
  657. do {
  658. $pathsections[] = $category->name;
  659. $id = $category->parent;
  660. } while ($category = get_record('question_categories', 'id', $id));
  661. if ($includecontext){
  662. $pathsections[] = '$' . $contextstring . '$';
  663. }
  664. $path = $this->assemble_category_path(array_reverse($pathsections));
  665. return $path;
  666. }
  667. /**
  668. * Convert a list of category names, possibly preceeded by one of the
  669. * context tokens like $course$, into a string representation of the
  670. * category path.
  671. *
  672. * Names are separated by / delimiters. And /s in the name are replaced by //.
  673. *
  674. * To reverse the process and split the paths into names, use
  675. * {@link split_category_path()}.
  676. *
  677. * @param array $names
  678. * @return string
  679. */
  680. function assemble_category_path($names) {
  681. $escapednames = array();
  682. foreach ($names as $name) {
  683. $escapedname = str_replace('/', '//', $name);
  684. if (substr($escapedname, 0, 1) == '/') {
  685. $escapedname = ' ' . $escapedname;
  686. }
  687. if (substr($escapedname, -1) == '/') {
  688. $escapedname = $escapedname . ' ';
  689. }
  690. $escapednames[] = $escapedname;
  691. }
  692. return implode('/', $escapednames);
  693. }
  694. /**
  695. * Convert a string, as returned by {@link assemble_category_path()},
  696. * back into an array of category names.
  697. *
  698. * Each category name is cleaned by a call to clean_param(, PARAM_MULTILANG),
  699. * which matches the cleaning in question/category_form.php. Not that this
  700. * addslashes the names, ready for insertion into the database.
  701. *
  702. * @param string $path
  703. * @return array of category names.
  704. */
  705. function split_category_path($path) {
  706. $rawnames = preg_split('~(?<!/)/(?!/)~', $path);
  707. $names = array();
  708. foreach ($rawnames as $rawname) {
  709. $names[] = clean_param(trim(str_replace('//', '/', $rawname)), PARAM_MULTILANG);
  710. }
  711. return $names;
  712. }
  713. /**
  714. * Do an post-processing that may be required
  715. * @return boolean success
  716. */
  717. function exportpostprocess() {
  718. return true;
  719. }
  720. /**
  721. * convert a single question object into text output in the given
  722. * format.
  723. * This must be overriden
  724. * @param object question question object
  725. * @return mixed question export text or null if not implemented
  726. */
  727. function writequestion($question) {
  728. // if not overidden, then this is an error.
  729. $formatnotimplemented = get_string( 'formatnotimplemented','quiz' );
  730. echo "<p>$formatnotimplemented</p>";
  731. return NULL;
  732. }
  733. /**
  734. * get directory into which export is going
  735. * @return string file path
  736. */
  737. function question_get_export_dir() {
  738. global $USER;
  739. if ($this->canaccessbackupdata) {
  740. $dirname = get_string("exportfilename","quiz");
  741. $path = $this->course->id.'/backupdata/'.$dirname; // backupdata is protected directory
  742. } else {
  743. $path = 'temp/questionexport/' . $USER->id;
  744. }
  745. return $path;
  746. }
  747. /**
  748. * where question specifies a moodle (text) format this
  749. * performs the conversion.
  750. */
  751. function format_question_text($question) {
  752. $formatoptions = new stdClass;
  753. $formatoptions->noclean = true;
  754. $formatoptions->para = false;
  755. if (empty($question->questiontextformat)) {
  756. $format = FORMAT_MOODLE;
  757. } else {
  758. $format = $question->questiontextformat;
  759. }
  760. return format_text(stripslashes($question->questiontext), $format, $formatoptions);
  761. }
  762. }
  763. ?>