2 /************************************************************************/
4 /************************************************************************/
5 /* Copyright (c) 2010 */
6 /* Inclusive Design Institute */
8 /* This program is free software. You can redistribute it and/or */
9 /* modify it under the terms of the GNU General Public License */
10 /* as published by the Free Software Foundation. */
11 /************************************************************************/
13 require_once(TR_INCLUDE_PATH.'lib/test_question_queries.inc.php');
16 * Steps to follow when adding a new question type:
18 * 1 - Create a class extending AbstractQuestion or extend an
19 * existing question class.
20 * Define $sPrefix and $sNameVar appropriately.
21 * Implement the following methods, which set template variables:
23 * assignQTIVariables()
24 * assignDisplayResultVariables()
25 * assignDisplayVariables()
26 * assignDisplayStatisticsVariables()
29 * And implement mark() which is used for marking the result.
31 * 2 - Add the new class name to $question_classes in test_question_factory()
33 * 3 - Add $sNameVar to the language database.
35 * 4 - Create the following files for creating and editing the question,
36 * where "{PREFIX}" is the value defined by $sPrefix:
38 * /tools/tests/create_question_{PREFIX}.php
39 * /tools/tests/edit_question_{PREFIX}.php
41 * 5 - Add those two newly created pages to
44 * 6 - Create the following template files:
46 * /themes/default/tests/test_questions/{PREFIX}.tmpl.php
47 * /themes/default/tests/test_questions/{PREFIX}_qti_2p1.tmpl.php
48 * /themes/default/tests/test_questions/{PREFIX}_result.tmpl.php
49 * /themes/default/tests/test_questions/{PREFIX}_stats.tmpl.php
51 * 7 - Add the new question type to qti import/export tools,
52 * Implement the following methods, which set template variables:
54 * include/classes/QTI/QTIParser.class.php
60 // returns array of prefix => name, sorted!
61 /*static */function getQuestionPrefixNames() {
62 $question_prefix_names = array(); // prefix => name
63 $questions = TestQuestions::getQuestionClasses();
64 foreach ($questions as $type => $question) {
65 $o = TestQuestions::getQuestion($type);
66 $question_prefix_names[$o->getPrefix()] = $o->getName();
68 asort($question_prefix_names);
69 return $question_prefix_names;
72 /*static */function getQuestionClasses() {
73 /** NOTE: The indices are CONSTANTS. Do NOT change!! **/
74 $question_classes = array(); // type ID => class name
75 $question_classes[1] = 'MultichoiceQuestion';
76 $question_classes[2] = 'TruefalseQuestion';
77 $question_classes[3] = 'LongQuestion';
78 $question_classes[4] = 'LikertQuestion';
79 $question_classes[5] = 'MatchingQuestion';
80 $question_classes[6] = 'OrderingQuestion';
81 $question_classes[7] = 'MultianswerQuestion';
82 $question_classes[8] = 'MatchingddQuestion';
84 return $question_classes;
88 * Used to create question objects based on $question_type.
89 * A singleton that creates one obj per question since
90 * questions are all stateless.
91 * Returns a reference to the question object.
93 /*static */function & getQuestion($question_type) {
94 static $objs, $question_classes;
96 if (isset($objs[$question_type])) {
97 return $objs[$question_type];
100 $question_classes = TestQuestions::getQuestionClasses();
102 if (isset($question_classes[$question_type])) {
104 $objs[$question_type] = new $question_classes[$question_type]($savant);
109 return $objs[$question_type];
114 * Export test questions
115 * @param array an array consist of all the ids of the questions in which we desired to export.
117 function test_question_qti_export_v2p1($question_ids) {
120 require_once(TR_INCLUDE_PATH.'classes/DAO/CoursesDAO.class.php');
121 require_once(TR_INCLUDE_PATH.'classes/DAO/TestsQuestionsDAO.class.php');
122 require_once(TR_INCLUDE_PATH.'classes/zipfile.class.php'); // for zipfile
123 require_once(TR_INCLUDE_PATH.'lib/html_resource_parser.inc.php'); // for get_html_resources()
124 require_once(TR_INCLUDE_PATH.'classes/XML/XML_HTMLSax/XML_HTMLSax.php'); // for XML_HTMLSax
126 global $savant, $db, $languageManager;
128 $coursesDAO = new CoursesDAO();
129 $course_row = $coursesDAO->get($_course_id);
130 $course_language = $course_row['primary_language'];
131 $courseLanguage =& $languageManager->getLanguage($course_language);
132 $course_language_charset = $courseLanguage->getCharacterSet();
134 $zipfile = new zipfile();
135 $zipfile->create_dir('resources/'); // for all the dependency files
136 $resources = array();
137 $dependencies = array();
139 asort($question_ids);
141 $testsQuestionsDAO = new TestsQuestionsDAO();
142 $rows = $testsQuestionsDAO->getByQuestionIDs($question_ids);
143 if (is_array($rows)) {
144 foreach ($rows as $row) {
145 $obj = TestQuestions::getQuestion($row['type']);
146 $xml = $obj->exportQTI($row, $course_language_charset, '2.1');
147 $local_dependencies = array();
149 $text_blob = implode(' ', $row);
150 $local_dependencies = get_html_resources($text_blob);
151 $dependencies = array_merge($dependencies, $local_dependencies);
153 $resources[] = array('href' => 'question_'.$row['question_id'].'.xml',
154 'dependencies' => array_keys($local_dependencies));
157 $savant->assign('xml_content', $xml);
158 $savant->assign('title', $row['question']);
159 $xml = $savant->fetch('tests/test_questions/wrapper.tmpl.php');
161 $zipfile->add_file($xml, 'question_'.$row['question_id'].'.xml');
165 // add any dependency files:
166 foreach ($dependencies as $resource => $resource_server_path) {
167 $zipfile->add_file(@file_get_contents($resource_server_path), 'resources/' . $resource, filemtime($resource_server_path));
170 // construct the manifest xml
171 $savant->assign('resources', $resources);
172 $savant->assign('dependencies', array_keys($dependencies));
173 $savant->assign('encoding', $course_language_charset);
174 $manifest_xml = $savant->fetch('tests/test_questions/manifest_qti_2p1.tmpl.php');
176 $zipfile->add_file($manifest_xml, 'imsmanifest.xml');
180 $filename = str_replace(array(' ', ':'), '_', $course_row['title'].'-'._AT('question_database').'-'.date('Ymd'));
181 $zipfile->send_file($filename);
187 * Export test questions
188 * @param array an array consist of all the ids of the questions in which we desired to export.
190 function test_question_qti_export($question_ids) {
193 require_once(TR_INCLUDE_PATH.'classes/DAO/CoursesDAO.class.php');
194 require_once(TR_INCLUDE_PATH.'classes/DAO/TestsQuestionsDAO.class.php');
195 require_once(TR_INCLUDE_PATH.'classes/zipfile.class.php'); // for zipfile
196 require_once(TR_INCLUDE_PATH.'lib/html_resource_parser.inc.php'); // for get_html_resources()
197 require_once(TR_INCLUDE_PATH.'classes/XML/XML_HTMLSax/XML_HTMLSax.php'); // for XML_HTMLSax
199 global $savant, $db, $languageManager;
201 $coursesDAO = new CoursesDAO();
202 $course_row = $coursesDAO->get($_course_id);
203 $course_language = $course_row['primary_language'];
204 $courseLanguage =& $languageManager->getLanguage($course_language);
205 $course_language_charset = $courseLanguage->getCharacterSet();
207 $zipfile = new zipfile();
208 $zipfile->create_dir('resources/'); // for all the dependency files
209 $resources = array();
210 $dependencies = array();
212 asort($question_ids);
214 $testsQuestionsDAO = new TestsQuestionsDAO();
215 $rows = $testsQuestionsDAO->getByQuestionIDs($question_ids);
216 if (is_array($rows)) {
217 foreach ($rows as $row) {
218 $obj = TestQuestions::getQuestion($row['type']);
220 $local_xml = $obj->exportQTI($row, $course_language_charset, '1.2.1');
221 $local_dependencies = array();
223 $text_blob = implode(' ', $row);
224 $local_dependencies = get_html_resources($text_blob);
225 $dependencies = array_merge($dependencies, $local_dependencies);
227 // $resources[] = array('href' => 'question_'.$row['question_id'].'.xml',
228 // 'dependencies' => array_keys($local_dependencies));
230 $xml = $xml . "\n\n" . $local_xml;
236 $savant->assign('xml_content', $xml);
237 $savant->assign('title', $row['question']);
238 $xml = $savant->fetch('tests/test_questions/wrapper.tmpl.php');
240 $xml_filename = 'atutor_questions.xml';
241 $zipfile->add_file($xml, $xml_filename);
243 // add any dependency files:
244 foreach ($dependencies as $resource => $resource_server_path) {
245 $zipfile->add_file(@file_get_contents($resource_server_path), 'resources/' . $resource, filemtime($resource_server_path));
248 // construct the manifest xml
249 // $savant->assign('resources', $resources);
250 $savant->assign('dependencies', array_keys($dependencies));
251 $savant->assign('encoding', $course_language_charset);
252 $savant->assign('xml_filename', $xml_filename);
253 // $manifest_xml = $savant->fetch('tests/test_questions/manifest_qti_2p1.tmpl.php');
254 $manifest_xml = $savant->fetch('tests/test_questions/manifest_qti_1p2.tmpl.php');
256 $zipfile->add_file($manifest_xml, 'imsmanifest.xml');
260 $filename = str_replace(array(' ', ':'), '_', $course_row['title'].'-'._AT('question_database').'-'.date('Ymd'));
261 $zipfile->send_file($filename);
269 * @param string the test title
270 * @param ref [OPTIONAL] zip object reference
271 * @param array [OPTIONAL] list of already added files.
273 function test_qti_export($tid, $test_title='', $zipfile = null){
276 require_once(TR_INCLUDE_PATH.'classes/DAO/CoursesDAO.class.php');
277 require_once(TR_INCLUDE_PATH.'classes/DAO/TestsDAO.class.php');
278 require_once(TR_INCLUDE_PATH.'classes/DAO/TestsQuestionsAssocDAO.class.php');
279 require_once(TR_INCLUDE_PATH.'classes/zipfile.class.php'); // for zipfile
280 require_once(TR_INCLUDE_PATH.'classes/XML/XML_HTMLSax/XML_HTMLSax.php'); // for XML_HTMLSax
281 require_once(TR_INCLUDE_PATH.'lib/html_resource_parser.inc.php'); // for get_html_resources()
282 global $savant, $db, $languageManager, $test_zipped_files, $test_files, $use_cc;
285 $coursesDAO = new CoursesDAO();
286 $course_row = $coursesDAO->get($_course_id);
287 $course_language = $course_row['primary_language'];
288 $courseLanguage =& $languageManager->getLanguage($course_language);
289 $course_language_charset = $courseLanguage->getCharacterSet();
297 if ($test_zipped_files == null){
298 $test_zipped_files = array();
302 $zipfile = new zipfile();
303 $zipfile->create_dir('resources/'); // for all the dependency files
305 $resources = array();
306 $dependencies = array();
308 // don't want to sort it, i want the same order out.
309 // asort($question_ids);
311 //TODO: Merge the following 2 sqls together.
312 //Randomized or not, export all the questions that are associated with it.
313 // $sql = "SELECT TQ.question_id, TQA.weight FROM ".TABLE_PREFIX."tests_questions TQ INNER JOIN ".TABLE_PREFIX."tests_questions_assoc TQA USING (question_id) WHERE TQ.course_id=$_SESSION[course_id] AND TQA.test_id=$tid ORDER BY TQA.ordering, TQA.question_id";
314 // $result = mysql_query($sql, $db);
315 // $question_ids = array();
317 // if (is_array($question_rows)){
318 // foreach ($question_rows as $question_row) $question_ids[] = $question_row['question_id'];
321 // //No questions in the test
322 // if (sizeof($question_ids)==0){
326 // $question_ids_delim = implode(',',$question_ids);
328 // //$sql = "SELECT * FROM ".TABLE_PREFIX."tests_questions WHERE course_id=$_SESSION[course_id] AND question_id IN($question_ids_delim)";
329 // $sql = "SELECT TQ.*, TQA.weight, TQA.test_id FROM ".TABLE_PREFIX."tests_questions TQ INNER JOIN ".TABLE_PREFIX."tests_questions_assoc TQA USING (question_id) WHERE TQA.test_id=$tid AND TQ.question_id IN($question_ids_delim) ORDER BY TQA.ordering, TQA.question_id";
331 // $result = mysql_query($sql, $db);
332 // while ($row = mysql_fetch_assoc($result)) {
333 $testsQuestionsAssocDAO = new TestsQuestionsAssocDAO();
334 $question_rows = $testsQuestionsAssocDAO->getByTestID($tid);
336 if (!is_array($question_rows)) return false;
338 foreach ($question_rows as $row) {
339 $obj = TestQuestions::getQuestion($row['type']);
341 $local_xml = $obj->exportQTI($row, $course_language_charset, '1.2.1');
342 $local_dependencies = array();
344 $text_blob = implode(' ', $row);
345 $local_dependencies = get_html_resources($text_blob);
346 $dependencies = array_merge($dependencies, $local_dependencies);
348 $xml = $xml . "\n\n" . $local_xml;
352 //files that are found inside the test; used by print_organization(), to add all test files into QTI/ folder.
353 $test_files = $dependencies;
355 $resources[] = array('href' => 'tests_'.$tid.'.xml',
356 'dependencies' => array_keys($dependencies));
361 // $sql = "SELECT title FROM ".TABLE_PREFIX."tests WHERE test_id = $tid";
362 // $result = mysql_query($sql, $db);
363 // $row = mysql_fetch_array($result);
364 $testsDAO = new TestsDAO();
365 $row = $testsDAO->get($tid);
367 //TODO: wrap around xml now
368 $savant->assign('xml_content', $xml);
369 $savant->assign('title', htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8'));
370 $savant->assign('num_takes', $row['num_takes']);
371 $savant->assign('use_cc', $use_cc);
372 $xml = $savant->fetch('tests/test_questions/wrapper.tmpl.php');
374 $xml_filename = 'tests_'.$tid.'.xml';
376 $zipfile->add_file($xml, $xml_filename);
378 $zipfile->add_file($xml, 'QTI/'.$xml_filename);
381 // add any dependency files:
383 foreach ($dependencies as $resource => $resource_server_path) {
384 //add this file in if it's not already in the zip package
385 if (!in_array($resource_server_path, $test_zipped_files)){
386 $zipfile->add_file(@file_get_contents($resource_server_path), 'resources/'.$resource, filemtime($resource_server_path));
387 $test_zipped_files[] = $resource_server_path;
393 // construct the manifest xml
394 $savant->assign('resources', $resources);
395 $savant->assign('dependencies', array_keys($dependencies));
396 $savant->assign('encoding', $course_language_charset);
397 $savant->assign('title', $test_title);
398 $savant->assign('xml_filename', $xml_filename);
400 $manifest_xml = $savant->fetch('tests/test_questions/manifest_qti_1p2.tmpl.php');
401 $zipfile->add_file($manifest_xml, 'imsmanifest.xml');
405 $filename = str_replace(array(' ', ':'), '_', $course_row['title'].'-'.$test_title.'-'.date('Ymd'));
406 $zipfile->send_file($filename);
410 $return_array[$xml_filename] = array_keys($dependencies);
411 return $return_array;
417 * Recursively create folders
418 * For the purpose of this webapp only. All the paths are seperated by a /
419 * And thus this function will loop through each directory and create them on the way
420 * if it doesn't exist.
423 function recursive_mkdir($path, $mode = 0700) {
424 $dirs = explode(DIRECTORY_SEPARATOR , $path);
425 $count = count($dirs);
427 for ($i = 0; $i < $count; ++$i) {
428 $path .= $dirs[$i].DIRECTORY_SEPARATOR;
429 //If the directory has not been created, create it and return error on failure
430 if (!is_dir($path) && !mkdir($path, $mode)) {
439 * keeps count of the question number (when displaying the question)
440 * need this function because PHP 4 doesn't support static members
442 function TestQuestionCounter($increment = FALSE) {
445 if (!isset($count)) {
459 * Note that all PHP 5 OO declarations and signatures are commented out to be
460 * backwards compatible with PHP 4.
463 /*abstract */ class AbstractTestQuestion {
465 * Savant2 $savant - refrence to the savant obj
467 /*protected */ var $savant;
470 * Constructor method. Initialises variables.
472 function AbstractTestQuestion(&$savant) { $this->savant =& $savant; }
477 /*final public */function seed($salt) {
479 * by controlling the seed before calling array_rand() we insure that
480 * we can un-randomize the order for marking.
481 * used with ordering type questions only.
483 srand($salt + $_SESSION['user_id']);
489 /*final public */function unseed() {
490 // To fix http://www.atutor.ca/atutor/mantis/view.php?id=3167
491 // Disturb the seed for ordering questions after mark to avoid the deterioration
492 // of the random distribution due to a repeated initialization of the same random seed
493 list($usec, $sec) = explode(" ", microtime());
494 srand((int)($usec*10));
499 * Prints the name of this question
501 /*final public */function printName() { echo $this->getName(); }
505 * Prints the name of this question
507 /*final public */function getName() { return _AT($this->sNameVar); }
511 * Returns the prefix string (used for file names)
513 /*final public */function getPrefix() { return $this->sPrefix; }
516 * Display the current question (for taking or previewing a test/question)
518 /*final public */function display($row, $response = '') {
519 // print the generic question header
520 require_once(TR_INCLUDE_PATH.'../home/classes/ContentUtility.class.php');
521 $this->displayHeader($row['weight']);
523 // print the question specific template
524 $row['question'] = ContentUtility::formatContent($row['question'], 1);
525 $this->assignDisplayVariables($row, $response);
526 $this->savant->display('tests/test_questions/' . $this->sPrefix . '.tmpl.php');
528 // print the generic question footer
529 $this->displayFooter();
533 * Display the result for the current question
535 /*final public */function displayResult($row, $answer_row, $editable = FALSE) {
536 // print the generic question header
537 $this->displayHeader($row['weight'], $answer_row['score'], $editable ? $row['question_id'] : FALSE);
539 // print the question specific template
540 $this->assignDisplayResultVariables($row, $answer_row);
541 $this->savant->display('tests/test_questions/' . $this->sPrefix . '_result.tmpl.php');
543 // print the generic question footer
544 $this->displayFooter();
549 * print the question template header
551 /*final public */function displayResultStatistics($row, $answers) {
552 TestQuestionCounter(TRUE);
553 $this->assignDisplayStatisticsVariables($row, $answers);
554 $this->savant->display('tests/test_questions/' . $this->sPrefix . '_stats.tmpl.php');
557 /*final public */function exportQTI($row, $encoding, $version) {
558 $this->savant->assign('encoding', $encoding);
559 $this->savant->assign('weight', $row['weight']);
560 //Convert all row values to html entities
561 foreach ($row as $k=>$v){
562 $row[$k] = htmlspecialchars($v, ENT_QUOTES, 'UTF-8'); //not using htmlentities cause it changes some languages falsely.
564 $this->assignQTIVariables($row);
565 if ($version=='2.1') {
566 $xml = $this->savant->fetch('tests/test_questions/'. $this->sPrefix . '_qti_2p1.tmpl.php');
568 $xml = $this->savant->fetch('tests/test_questions/'. $this->sPrefix . '_qti_1p2.tmpl.php');
574 * print the question template header
576 /*final private */function displayHeader($weight, $score = FALSE, $question_id = FALSE) {
577 TestQuestionCounter(TRUE);
579 if ($score) $score = intval($score);
580 $this->savant->assign('question_id', $question_id);
581 $this->savant->assign('score', $score);
582 $this->savant->assign('weight', $weight);
583 $this->savant->assign('type', _AT($this->sNameVar));
584 $this->savant->assign('number', TestQuestionCounter());
585 $this->savant->display('tests/test_questions/header.tmpl.php');
589 * print the question template footer
591 /*final private */function displayFooter() {
592 $this->savant->display('tests/test_questions/footer.tmpl.php');
596 * return only the non-empty choices from $row.
597 * assumes choices are sequential.
599 /*protected */function getChoices($row) {
601 for ($i=0; $i < 10; $i++) {
602 if ($row['choice_'.$i] != '') {
604 $choices[] = $row['choice_'.$i];
617 class OrderingQuestion extends AbstractTestQuestion {
618 /*protected */ var $sNameVar = 'test_ordering';
619 /*protected */ var $sPrefix = 'ordering';
621 /*protected */function assignDisplayResultVariables($row, $answer_row) {
622 $answers = explode('|', $answer_row['answer']);
624 $num_choices = count($this->getChoices($row));
626 $this->savant->assign('base_href', TR_BASE_HREF);
627 $this->savant->assign('num_choices', $num_choices);
628 $this->savant->assign('answers', $answers);
629 $this->savant->assign('row', $row);
632 /*protected */function assignQTIVariables($row) {
633 $choices = $this->getChoices($row);
634 $num_choices = count($choices);
636 $this->savant->assign('num_choices', $num_choices);
637 $this->savant->assign('row', $row);
640 /*protected */function assignDisplayVariables($row, $response) {
641 // determine the number of choices this question has
642 // and save those choices to be re-assigned back to $row
643 // in the randomized order.
644 $choices = $this->getChoices($row);
645 $num_choices = count($choices);
647 // response from the test_answers table is in the correct order
648 // so, they have to be re-randomized in the same order as the
649 // choices are. this is only possible because of the seed() method.
650 $response = explode('|', $response);
651 $new_response = array();
653 // randomize the order of choices and re-assign to $row
654 $this->seed($row['question_id']);
655 $rand = array_rand($choices, $num_choices);
656 for ($i=0; $i < 10; $i++) {
657 $row['choice_'.$i] = $choices[$rand[$i]];
658 $new_response[$i] = $response[$rand[$i]];
661 $this->savant->assign('num_choices', $num_choices);
662 $this->savant->assign('row', $row);
664 $this->savant->assign('response', $new_response);
667 /*protected */function assignDisplayStatisticsVariables($row, $answers) {
669 foreach ($answers as $answer) {
670 $num_results += $answer['count'];
673 $choices = $this->getChoices($row);
674 $num_choices = count($choices);
676 $final_answers = array(); // assoc array of # of times that key was used correctly 0, 1, ... $num -1
677 foreach ($answers as $key => $value) {
678 $values = explode('|', $key);
679 // we assume $values is never empty and contains $num number of answers
680 for ($i=0; $i<=$num_choices; $i++) {
681 if ($values[$i] == $i) {
682 $final_answers[$i] += $answers[$key]['count'];
687 $this->savant->assign('num_results', $num_results);
688 $this->savant->assign('num_choices', $num_choices);
689 $this->savant->assign('answers', $final_answers);
690 $this->savant->assign('row', $row);
693 /*public */function mark($row) {
694 $this->seed($row['question_id']);
695 $num_choices = count($_POST['answers'][$row['question_id']]);
696 $answers = range(0, $num_choices-1);
697 $answers = array_rand($answers, $num_choices);
699 // Disturb the seed for ordering questions after mark to avoid the deterioration
700 // of the random distribution due to a repeated initialization of the same random seed
703 $num_answer_correct = 0;
705 $ordered_answers = array();
707 for ($i = 0; $i < $num_choices ; $i++) {
708 $_POST['answers'][$row['question_id']][$i] = intval($_POST['answers'][$row['question_id']][$i]);
710 if ($_POST['answers'][$row['question_id']][$i] == -1) {
711 // nothing to do. it was left blank
712 } else if ($_POST['answers'][$row['question_id']][$i] == $answers[$i]) {
713 $num_answer_correct++;
715 $ordered_answers[$answers[$i]] = $_POST['answers'][$row['question_id']][$i];
717 ksort($ordered_answers);
721 // to avoid roundoff errors:
722 if ($num_answer_correct == $num_choices) {
723 $score = $row['weight'];
724 } else if ($num_answer_correct > 0) {
725 $score = number_format($row['weight'] / $num_choices * $num_answer_correct, 2);
726 if ( (float) (int) $score == $score) {
727 $score = (int) $score; // a whole number with decimals, eg. "2.00"
729 $score = trim($score, '0'); // remove trailing zeros, if any, eg. "2.50"
733 $_POST['answers'][$row['question_id']] = implode('|', $ordered_answers);
738 //QTI Import Ordering Question
739 function importQTI($_POST){
742 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
743 require_once(TR_INCLUDE_PATH.'classes/Utility.class.php');
746 if ($_POST['question'] == ''){
747 $missing_fields[] = _AT('question');
750 if (trim($_POST['choice'][0]) == '') {
751 $missing_fields[] = _AT('item').' 1';
753 if (trim($_POST['choice'][1]) == '') {
754 $missing_fields[] = _AT('item').' 2';
757 if ($missing_fields) {
758 $missing_fields = implode(', ', $missing_fields);
759 $msg->addError(array('EMPTY_FIELDS', $missing_fields));
762 if (!$msg->containsErrors()) {
763 $choice_new = array(); // stores the non-blank choices
764 $answer_new = array(); // stores the non-blank answers
765 $order = 0; // order count
766 for ($i=0; $i<10; $i++) {
768 * Db defined it to be 255 length, chop strings off it it's less than that
771 $_POST['choice'][$i] = Utility::validateLength($_POST['choice'][$i], 255);
772 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
774 if ($_POST['choice'][$i] != '') {
775 /* filter out empty choices/ remove gaps */
776 $choice_new[] = $_POST['choice'][$i];
777 $answer_new[] = $order++;
781 $_POST['choice'] = array_pad($choice_new, 10, '');
782 $answer_new = array_pad($answer_new, 10, 0);
783 // $_POST['feedback'] = $addslashes($_POST['feedback']);
784 // $_POST['question'] = $addslashes($_POST['question']);
786 $sql_params = array( $_POST['category_id'],
811 $sql = vsprintf(TR_SQL_QUESTION_ORDERING, $sql_params);
813 // $result = mysql_query($sql, $db);
814 // if ($result==true){
816 if ($dao->execute($sql)) {
817 return mysql_insert_id();
827 class TruefalseQuestion extends AbstracttestQuestion {
828 /*protected */ var $sPrefix = 'truefalse';
829 /*protected */ var $sNameVar = 'test_tf';
831 /*protected */function assignQTIVariables($row) {
832 $this->savant->assign('row', $row);
835 /*protected */function assignDisplayResultVariables($row, $answer_row) {
837 $this->savant->assign('base_href', TR_BASE_HREF);
838 $this->savant->assign('answers', $answer_row['answer']);
839 $this->savant->assign('row', $row);
842 /*protected */function assignDisplayVariables($row, $response) {
843 $this->savant->assign('row', $row);
844 $this->savant->assign('response', $response);
847 /*protected */function assignDisplayStatisticsVariables($row, $answers) {
849 foreach ($answers as $answer) {
850 $num_results += $answer['count'];
853 $this->savant->assign('num_results', $num_results);
854 $this->savant->assign('num_blanks', (int) $answers['-1']['count']);
855 $this->savant->assign('num_true', (int) $answers['1']['count']);
856 $this->savant->assign('num_false', (int) $answers['2']['count']);
857 $this->savant->assign('row', $row);
860 /*public */function mark($row) {
861 $_POST['answers'][$row['question_id']] = intval($_POST['answers'][$row['question_id']]);
863 if ($row['answer_0'] == $_POST['answers'][$row['question_id']]) {
864 return (int) $row['weight'];
869 //QTI Import True/False Question
870 function importQTI($_POST){
871 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
872 global $msg, $db, $_course_id;
874 if ($_POST['question'] == ''){
875 $msg->addError(array('EMPTY_FIELDS', _AT('statement')));
878 //assign true answer to 1, false answer to 2, idk to 3, for ATutor
879 if ($_POST['answer'] == 'ChoiceT'){
880 $_POST['answer'] = 1;
882 $_POST['answer'] = 2;
885 if (!$msg->containsErrors()) {
886 // $_POST['feedback'] = $addslashes($_POST['feedback']);
887 // $_POST['question'] = $addslashes($_POST['question']);
890 $sql_params = array( $_POST['category_id'],
896 $sql = vsprintf(TR_SQL_QUESTION_TRUEFALSE, $sql_params);
897 // $result = mysql_query($sql, $db);
898 // if ($result==true){
900 if ($dao->execute($sql)) {
901 return mysql_insert_id();
911 class LikertQuestion extends AbstracttestQuestion {
912 /*protected */ var $sPrefix = 'likert';
913 /*protected */ var $sNameVar = 'test_lk';
915 /*protected */function assignQTIVariables($row) {
916 $choices = $this->getChoices($row);
917 $num_choices = count($choices);
919 $this->savant->assign('num_choices', $num_choices);
920 $this->savant->assign('row', $row);
923 /*protected */function assignDisplayResultVariables($row, $answer_row) {
924 $this->savant->assign('answer', $answer_row['answer']);
925 $this->savant->assign('row', $row);
928 /*protected */function assignDisplayVariables($row, $response) {
929 $choices = $this->getChoices($row);
930 $num_choices = count($choices);
932 $this->savant->assign('num_choices', $num_choices);
933 $this->savant->assign('row', $row);
935 if (empty($response)) {
938 $this->savant->assign('response', $response);
941 /*protected */function assignDisplayStatisticsVariables($row, $answers) {
943 foreach ($answers as $answer) {
944 $num_results += $answer['count'];
947 $choices = $this->getChoices($row);
948 $num_choices = count($choices);
951 for ($i=0; $i<$num_choices; $i++) {
952 $sum += ($i+1) * $answers[$i]['count'];
954 $average = round($sum/$num_results, 1);
956 $this->savant->assign('num_results', $num_results);
957 $this->savant->assign('average', $average);
958 $this->savant->assign('num_choices', $num_choices);
959 $this->savant->assign('num_blanks', (int) $answers['-1']['count']);
960 $this->savant->assign('answers', $answers);
961 $this->savant->assign('row', $row);
964 /*public */function mark($row) {
965 $_POST['answers'][$row['question_id']] = intval($_POST['answers'][$row['question_id']]);
969 //QTI Import Likert Question
970 function importQTI($_POST){
971 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
972 global $msg, $db, $_course_id;
973 // $_POST = $this->_POST;
975 $empty_fields = array();
976 if ($_POST['question'] == ''){
977 $empty_fields[] = _AT('question');
979 if ($_POST['choice'][0] == '') {
980 $empty_fields[] = _AT('choice').' 1';
983 if ($_POST['choice'][1] == '') {
984 $empty_fields[] = _AT('choice').' 2';
987 if (!empty($empty_fields)) {
988 // $msg->addError(array('EMPTY_FIELDS', implode(', ', $empty_fields)));
991 if (!$msg->containsErrors()) {
992 $_POST['feedback'] = '';
993 // $_POST['question'] = $addslashes($_POST['question']);
995 for ($i=0; $i<10; $i++) {
996 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
997 $_POST['answer'][$i] = intval($_POST['answer'][$i]);
999 if ($_POST['choice'][$i] == '') {
1000 /* an empty option can't be correct */
1001 $_POST['answer'][$i] = 0;
1005 $sql_params = array( $_POST['category_id'],
1009 $_POST['choice'][0],
1010 $_POST['choice'][1],
1011 $_POST['choice'][2],
1012 $_POST['choice'][3],
1013 $_POST['choice'][4],
1014 $_POST['choice'][5],
1015 $_POST['choice'][6],
1016 $_POST['choice'][7],
1017 $_POST['choice'][8],
1018 $_POST['choice'][9],
1019 $_POST['answer'][0],
1020 $_POST['answer'][1],
1021 $_POST['answer'][2],
1022 $_POST['answer'][3],
1023 $_POST['answer'][4],
1024 $_POST['answer'][5],
1025 $_POST['answer'][6],
1026 $_POST['answer'][7],
1027 $_POST['answer'][8],
1028 $_POST['answer'][9]);
1030 $sql = vsprintf(TR_SQL_QUESTION_LIKERT, $sql_params);
1031 // $result = mysql_query($sql, $db);
1032 // if ($result==true){
1034 if ($dao->execute($sql)) {
1035 return mysql_insert_id();
1045 class LongQuestion extends AbstracttestQuestion {
1046 /*protected */ var $sPrefix = 'long';
1047 /*protected */ var $sNameVar = 'test_open';
1049 /*protected */function assignQTIVariables($row) {
1050 $this->savant->assign('row', $row);
1053 /*protected */function assignDisplayResultVariables($row, $answer_row) {
1054 $this->savant->assign('answer', $answer_row['answer']);
1055 $this->savant->assign('row', $row);
1058 /*protected */function assignDisplayVariables($row, $response) {
1059 $this->savant->assign('row', $row);
1060 $this->savant->assign('response', $response);
1063 /*protected */function assignDisplayStatisticsVariables($row, $answers) {
1065 foreach ($answers as $answer) {
1066 $num_results += $answer['count'];
1069 $this->savant->assign('num_results', $num_results);
1070 $this->savant->assign('num_blanks', (int) $answers['']['count']);
1071 $this->savant->assign('answers', $answers);
1072 $this->savant->assign('row', $row);
1075 /*public */function mark($row) {
1077 $_POST['answers'][$row['question_id']] = $addslashes($_POST['answers'][$row['question_id']]);
1081 //QTI Import Open end/long Question
1082 function importQTI($_POST){
1083 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
1084 global $msg, $db, $_course_id;
1085 // $_POST = $this->_POST;
1087 if ($_POST['question'] == ''){
1088 // $msg->addError(array('EMPTY_FIELDS', _AT('question')));
1091 if (!$msg->containsErrors()) {
1092 // $_POST['feedback'] = $addslashes($_POST['feedback']);
1093 // $_POST['question'] = $addslashes($_POST['question']);
1095 if ($_POST['property']==''){
1096 $_POST['property'] = 4; //essay
1099 $sql_params = array( $_POST['category_id'],
1103 $_POST['property']);
1105 $sql = vsprintf(TR_SQL_QUESTION_LONG, $sql_params);
1107 // $result = mysql_query($sql, $db);
1108 // if ($result==true){
1110 if ($dao->execute($sql)) {
1111 return mysql_insert_id();
1121 class MatchingQuestion extends AbstracttestQuestion {
1122 /*protected */ var $sPrefix = 'matching';
1123 /*protected */ var $sNameVar = 'test_matching';
1125 /*protected */function assignQTIVariables($row) {
1126 $choices = $this->getChoices($row);
1127 $num_choices = count($choices);
1130 for ($i=0; $i < 10; $i++) {
1131 if ($row['option_'. $i] != '') {
1136 $this->savant->assign('num_choices', $num_choices);
1137 $this->savant->assign('num_options', $num_options);
1138 $this->savant->assign('row', $row);
1141 /*protected */function assignDisplayResultVariables($row, $answer_row) {
1143 for ($i=0; $i < 10; $i++) {
1144 if ($row['option_'. $i] != '') {
1149 $answer_row['answer'] = explode('|', $answer_row['answer']);
1153 $this->savant->assign('base_href', TR_BASE_HREF);
1154 $this->savant->assign('answers', $answer_row['answer']);
1155 $this->savant->assign('letters', $_letters);
1156 $this->savant->assign('num_options', $num_options);
1157 $this->savant->assign('row', $row);
1160 /*protected */function assignDisplayVariables($row, $response) {
1161 $choices = $this->getChoices($row);
1162 $num_choices = count($choices);
1164 if (empty($response)) {
1165 $response = array_fill(0, $num_choices, -1);
1167 $response = explode('|', $response);
1171 for ($i=0; $i < 10; $i++) {
1172 if ($row['option_'. $i] != '') {
1179 $this->savant->assign('num_choices', $num_choices);
1180 $this->savant->assign('base_href', TR_BASE_HREF);
1181 $this->savant->assign('letters', $_letters);
1182 $this->savant->assign('num_options', $num_options);
1183 $this->savant->assign('row', $row);
1185 $this->savant->assign('response', $response);
1188 /*protected */function assignDisplayStatisticsVariables($row, $answers) {
1189 $choices = $this->getChoices($row);
1190 $num_choices = count($choices);
1193 foreach ($answers as $answer) {
1194 $num_results += $answer['count'];
1197 foreach ($answers as $key => $value) {
1198 $values = explode('|', $key);
1199 if (count($values) > 1) {
1200 for ($i=0; $i<count($values); $i++) {
1201 $answers[$values[$i]]['count']++;
1206 $this->savant->assign('num_choices', $num_choices);
1207 $this->savant->assign('num_results', $num_results);
1208 $this->savant->assign('answers', $answers);
1209 $this->savant->assign('row', $row);
1212 /*public */function mark($row) {
1213 $num_choices = count($_POST['answers'][$row['question_id']]);
1214 $num_answer_correct = 0;
1215 foreach ($_POST['answers'][$row['question_id']] as $item_id => $response) {
1216 if ($row['answer_' . $item_id] == $response) {
1217 $num_answer_correct++;
1219 $_POST['answers'][$row['question_id']][$item_id] = intval($_POST['answers'][$row['question_id']][$item_id]);
1223 // to avoid roundoff errors:
1224 if ($num_answer_correct == $num_choices) {
1225 $score = $row['weight'];
1226 } else if ($num_answer_correct > 0) {
1227 $score = number_format($row['weight'] / $num_choices * $num_answer_correct, 2);
1228 if ( (float) (int) $score == $score) {
1229 $score = (int) $score; // a whole number with decimals, eg. "2.00"
1231 $score = trim($score, '0'); // remove trailing zeros, if any
1235 $_POST['answers'][$row['question_id']] = implode('|', $_POST['answers'][$row['question_id']]);
1240 //QTI Import Matching Question
1241 function importQTI($_POST){
1242 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
1243 global $msg, $db, $_course_id;
1244 // $_POST = $this->_POST;
1246 if (!is_array($_POST['answer'])){
1247 $temp = $_POST['answer'];
1248 $_POST['answer'] = array();
1249 $_POST['answer'][0] = $temp;
1251 ksort($_POST['answer']); //array_pad returns an array disregard of the array keys
1252 //default for matching is '-'
1253 $_POST['answer']= array_pad($_POST['answer'], 10, -1);
1255 for ($i = 0 ; $i < 10; $i++) {
1256 $_POST['groups'][$i] = trim($_POST['groups'][$i]);
1257 $_POST['answer'][$i] = (int) $_POST['answer'][$i];
1258 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
1261 if (!$_POST['groups'][0]
1262 || !$_POST['groups'][1]
1263 || !$_POST['choice'][0]
1264 || !$_POST['choice'][1]) {
1265 // $msg->addError('QUESTION_EMPTY');
1268 if (!$msg->containsErrors()) {
1269 // $_POST['feedback'] = $addslashes($_POST['feedback']);
1270 // $_POST['instructions'] = $addslashes($_POST['instructions']);
1272 $sql_params = array( $_POST['category_id'],
1276 $_POST['groups'][0],
1277 $_POST['groups'][1],
1278 $_POST['groups'][2],
1279 $_POST['groups'][3],
1280 $_POST['groups'][4],
1281 $_POST['groups'][5],
1282 $_POST['groups'][6],
1283 $_POST['groups'][7],
1284 $_POST['groups'][8],
1285 $_POST['groups'][9],
1286 $_POST['answer'][0],
1287 $_POST['answer'][1],
1288 $_POST['answer'][2],
1289 $_POST['answer'][3],
1290 $_POST['answer'][4],
1291 $_POST['answer'][5],
1292 $_POST['answer'][6],
1293 $_POST['answer'][7],
1294 $_POST['answer'][8],
1295 $_POST['answer'][9],
1296 $_POST['choice'][0],
1297 $_POST['choice'][1],
1298 $_POST['choice'][2],
1299 $_POST['choice'][3],
1300 $_POST['choice'][4],
1301 $_POST['choice'][5],
1302 $_POST['choice'][6],
1303 $_POST['choice'][7],
1304 $_POST['choice'][8],
1305 $_POST['choice'][9]);
1307 $sql = vsprintf(TR_SQL_QUESTION_MATCHINGDD, $sql_params);
1310 if ($dao->execute($sql)) {
1311 return mysql_insert_id();
1318 * matchingddQuestion
1321 class MatchingddQuestion extends MatchingQuestion {
1322 /*protected */ var $sPrefix = 'matchingdd';
1323 /*protected */ var $sNameVar = 'test_matchingdd';
1327 * multichoiceQuestion
1330 class MultichoiceQuestion extends AbstracttestQuestion {
1331 /*protected */ var $sPrefix = 'multichoice';
1332 /*protected */var $sNameVar = 'test_mc';
1334 /*protected */function assignQTIVariables($row) {
1335 $choices = $this->getChoices($row);
1336 $num_choices = count($choices);
1338 $this->savant->assign('num_choices', $num_choices);
1339 $this->savant->assign('row', $row);
1342 /*protected */function assignDisplayResultVariables($row, $answer_row) {
1343 if (strpos($answer_row['answer'], '|') !== false) {
1344 $answer_row['answer'] = explode('|', $answer_row['answer']);
1346 $answer_row['answer'] = array($answer_row['answer']);
1349 $this->savant->assign('base_href', TR_BASE_HREF);
1350 $this->savant->assign('answers', $answer_row['answer']);
1351 $this->savant->assign('row', $row);
1354 /*protected */function assignDisplayVariables($row, $response) {
1355 $choices = $this->getChoices($row);
1356 $num_choices = count($choices);
1358 if ($response == '') {
1361 $response = explode('|', $response);
1362 $this->savant->assign('response', $response);
1364 $this->savant->assign('num_choices', $num_choices);
1365 $this->savant->assign('row', $row);
1368 /*protected */function assignDisplayStatisticsVariables($row, $answers) {
1369 $choices = $this->getChoices($row);
1370 $num_choices = count($choices);
1373 foreach ($answers as $answer) {
1374 $num_results += $answer['count'];
1377 foreach ($answers as $key => $value) {
1378 $values = explode('|', $key);
1379 if (count($values) > 1) {
1380 for ($i=0; $i<count($values); $i++) {
1381 $answers[$values[$i]]['count']++;
1386 $this->savant->assign('num_choices', $num_choices);
1387 $this->savant->assign('num_results', $num_results);
1388 $this->savant->assign('num_blanks', (int) $answers['-1']['count']);
1389 $this->savant->assign('answers', $answers);
1390 $this->savant->assign('row', $row);
1393 /*public */function mark($row) {
1395 $_POST['answers'][$row['question_id']] = intval($_POST['answers'][$row['question_id']]);
1396 if ($row['answer_' . $_POST['answers'][$row['question_id']]]) {
1397 $score = $row['weight'];
1398 } else if ($_POST['answers'][$row['question_id']] == -1) {
1400 for($i=0; $i<10; $i++) {
1401 $has_answer += $row['answer_'.$i];
1403 if (!$has_answer && $row['weight']) {
1404 // If MC has no answer and user answered "leave blank"
1405 $score = $row['weight'];
1411 //QTI Import Multiple Choice Question
1412 function importQTI($_POST){
1413 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
1414 global $msg, $db, $_course_id;
1415 // $_POST = $this->_POST;
1416 if ($_POST['question'] == ''){
1417 $msg->addError(array('EMPTY_FIELDS', _AT('question')));
1420 if (!$msg->containsErrors()) {
1421 // $_POST['question'] = $addslashes($_POST['question']);
1423 for ($i=0; $i<10; $i++) {
1424 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
1427 $answers = array_fill(0, 10, 0);
1428 if (is_array($_POST['answer'])){
1429 $answers[0] = 1; //default the first to be the right answer. TODO, use summation of points.
1431 $answers[$_POST['answer']] = 1;
1434 $sql_params = array( $_POST['category_id'],
1438 $_POST['choice'][0],
1439 $_POST['choice'][1],
1440 $_POST['choice'][2],
1441 $_POST['choice'][3],
1442 $_POST['choice'][4],
1443 $_POST['choice'][5],
1444 $_POST['choice'][6],
1445 $_POST['choice'][7],
1446 $_POST['choice'][8],
1447 $_POST['choice'][9],
1459 $sql = vsprintf(TR_SQL_QUESTION_MULTI, $sql_params);
1460 // $result = mysql_query($sql, $db);
1461 // if ($result==true){
1463 if ($dao->execute($sql)) {
1464 return mysql_insert_id();
1471 * multianswerQuestion
1474 class MultianswerQuestion extends MultichoiceQuestion {
1475 /*protected */ var $sPrefix = 'multianswer';
1476 /*protected */ var $sNameVar = 'test_ma';
1478 /*public */function mark($row) {
1479 $num_correct = array_sum(array_slice($row, 3));
1481 if (is_array($_POST['answers'][$row['question_id']]) && count($_POST['answers'][$row['question_id']]) > 1) {
1482 if (($i = array_search('-1', $_POST['answers'][$row['question_id']])) !== FALSE) {
1483 unset($_POST['answers'][$row['question_id']][$i]);
1485 $num_answer_correct = 0;
1486 foreach ($_POST['answers'][$row['question_id']] as $item_id => $answer) {
1487 if ($row['answer_' . $answer]) {
1489 $num_answer_correct++;
1492 $num_answer_correct--;
1494 $_POST['answers'][$row['question_id']][$item_id] = intval($_POST['answers'][$row['question_id']][$item_id]);
1496 if ($num_answer_correct == $num_correct) {
1497 $score = $row['weight'];
1501 $_POST['answers'][$row['question_id']] = implode('|', $_POST['answers'][$row['question_id']]);
1504 $_POST['answers'][$row['question_id']] = '-1'; // left blank
1510 //QTI Import multianswer Question
1511 function importQTI($_POST){
1512 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
1513 require_once(TR_INCLUDE_PATH.'classes/Utility.class.php');
1514 global $msg, $db, $_course_id;
1515 // $_POST = $this->_POST;
1517 if ($_POST['question'] == ''){
1518 $msg->addError(array('EMPTY_FIELDS', _AT('question')));
1521 //Multiple answer can have 0+ answers, in the QTIImport.class, if size(answer) < 2, answer will be came a scalar.
1522 //The following code will change $_POST[answer] back to a vector.
1523 $_POST['answer'] = $_POST['answers'];
1525 if (!$msg->containsErrors()) {
1526 $choice_new = array(); // stores the non-blank choices
1527 $answer_new = array(); // stores the associated "answer" for the choices
1529 foreach ($_POST['choice'] as $choiceNum=>$choiceOpt) {
1530 $choiceOpt = Utility::validateLength($choiceOpt, 255);
1531 $choiceOpt = trim($choiceOpt);
1532 $_POST['answer'][$choiceNum] = intval($_POST['answer'][$choiceNum]);
1533 if ($choiceOpt == '') {
1534 /* an empty option can't be correct */
1535 $_POST['answer'][$choiceNum] = 0;
1537 /* filter out empty choices/ remove gaps */
1538 $choice_new[] = $choiceOpt;
1539 if (in_array($choiceNum, $_POST['answer'])){
1545 if ($_POST['answer'][$choiceNum] != 0)
1550 if ($has_answer != TRUE) {
1552 $hidden_vars['required'] = htmlspecialchars($_POST['required']);
1553 $hidden_vars['feedback'] = htmlspecialchars($_POST['feedback']);
1554 $hidden_vars['question'] = htmlspecialchars($_POST['question']);
1555 $hidden_vars['category_id'] = htmlspecialchars($_POST['category_id']);
1557 for ($i = 0; $i < count($choice_new); $i++) {
1558 $hidden_vars['answer['.$i.']'] = htmlspecialchars($answer_new[$i]);
1559 $hidden_vars['choice['.$i.']'] = htmlspecialchars($choice_new[$i]);
1562 $msg->addConfirm('NO_ANSWER', $hidden_vars);
1564 //add slahes throughout - does that fix it?
1565 $_POST['answer'] = $answer_new;
1566 $_POST['choice'] = $choice_new;
1567 $_POST['answer'] = array_pad($_POST['answer'], 10, 0);
1568 $_POST['choice'] = array_pad($_POST['choice'], 10, '');
1570 // $_POST['feedback'] = $addslashes($_POST['feedback']);
1571 // $_POST['question'] = $addslashes($_POST['question']);
1573 $sql_params = array( $_POST['category_id'],
1577 $_POST['choice'][0],
1578 $_POST['choice'][1],
1579 $_POST['choice'][2],
1580 $_POST['choice'][3],
1581 $_POST['choice'][4],
1582 $_POST['choice'][5],
1583 $_POST['choice'][6],
1584 $_POST['choice'][7],
1585 $_POST['choice'][8],
1586 $_POST['choice'][9],
1587 $_POST['answer'][0],
1588 $_POST['answer'][1],
1589 $_POST['answer'][2],
1590 $_POST['answer'][3],
1591 $_POST['answer'][4],
1592 $_POST['answer'][5],
1593 $_POST['answer'][6],
1594 $_POST['answer'][7],
1595 $_POST['answer'][8],
1596 $_POST['answer'][9]);
1598 $sql = vsprintf(TR_SQL_QUESTION_MULTIANSWER, $sql_params);
1600 // $result = mysql_query($sql, $db);
1601 // if ($result==true){
1603 if ($dao->execute($sql)) {
1604 return mysql_insert_id();