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 $xml = $savant->fetch('tests/test_questions/wrapper.tmpl.php');
372 $xml_filename = 'tests_'.$tid.'.xml';
374 $zipfile->add_file($xml, $xml_filename);
376 $zipfile->add_file($xml, 'QTI/'.$xml_filename);
379 // add any dependency files:
381 foreach ($dependencies as $resource => $resource_server_path) {
382 //add this file in if it's not already in the zip package
383 if (!in_array($resource_server_path, $test_zipped_files)){
384 $zipfile->add_file(@file_get_contents($resource_server_path), 'resources/'.$resource, filemtime($resource_server_path));
385 $test_zipped_files[] = $resource_server_path;
391 // construct the manifest xml
392 $savant->assign('resources', $resources);
393 $savant->assign('dependencies', array_keys($dependencies));
394 $savant->assign('encoding', $course_language_charset);
395 $savant->assign('title', $test_title);
396 $savant->assign('xml_filename', $xml_filename);
398 $manifest_xml = $savant->fetch('tests/test_questions/manifest_qti_1p2.tmpl.php');
399 $zipfile->add_file($manifest_xml, 'imsmanifest.xml');
403 $filename = str_replace(array(' ', ':'), '_', $course_row['title'].'-'.$test_title.'-'.date('Ymd'));
404 $zipfile->send_file($filename);
408 $return_array[$xml_filename] = array_keys($dependencies);
409 return $return_array;
415 * Recursively create folders
416 * For the purpose of this webapp only. All the paths are seperated by a /
417 * And thus this function will loop through each directory and create them on the way
418 * if it doesn't exist.
421 function recursive_mkdir($path, $mode = 0700) {
422 $dirs = explode(DIRECTORY_SEPARATOR , $path);
423 $count = count($dirs);
425 for ($i = 0; $i < $count; ++$i) {
426 $path .= $dirs[$i].DIRECTORY_SEPARATOR;
427 //If the directory has not been created, create it and return error on failure
428 if (!is_dir($path) && !mkdir($path, $mode)) {
437 * keeps count of the question number (when displaying the question)
438 * need this function because PHP 4 doesn't support static members
440 function TestQuestionCounter($increment = FALSE) {
443 if (!isset($count)) {
457 * Note that all PHP 5 OO declarations and signatures are commented out to be
458 * backwards compatible with PHP 4.
461 /*abstract */ class AbstractTestQuestion {
463 * Savant2 $savant - refrence to the savant obj
465 /*protected */ var $savant;
468 * Constructor method. Initialises variables.
470 function AbstractTestQuestion(&$savant) { $this->savant =& $savant; }
475 /*final public */function seed($salt) {
477 * by controlling the seed before calling array_rand() we insure that
478 * we can un-randomize the order for marking.
479 * used with ordering type questions only.
481 srand($salt + $_SESSION['user_id']);
487 /*final public */function unseed() {
488 // To fix http://www.atutor.ca/atutor/mantis/view.php?id=3167
489 // Disturb the seed for ordering questions after mark to avoid the deterioration
490 // of the random distribution due to a repeated initialization of the same random seed
491 list($usec, $sec) = explode(" ", microtime());
492 srand((int)($usec*10));
497 * Prints the name of this question
499 /*final public */function printName() { echo $this->getName(); }
503 * Prints the name of this question
505 /*final public */function getName() { return _AT($this->sNameVar); }
509 * Returns the prefix string (used for file names)
511 /*final public */function getPrefix() { return $this->sPrefix; }
514 * Display the current question (for taking or previewing a test/question)
516 /*final public */function display($row, $response = '') {
517 // print the generic question header
518 require_once(TR_INCLUDE_PATH.'../home/classes/ContentUtility.class.php');
519 $this->displayHeader($row['weight']);
521 // print the question specific template
522 $row['question'] = ContentUtility::formatContent($row['question'], 1);
523 $this->assignDisplayVariables($row, $response);
524 $this->savant->display('tests/test_questions/' . $this->sPrefix . '.tmpl.php');
526 // print the generic question footer
527 $this->displayFooter();
531 * Display the result for the current question
533 /*final public */function displayResult($row, $answer_row, $editable = FALSE) {
534 // print the generic question header
535 $this->displayHeader($row['weight'], $answer_row['score'], $editable ? $row['question_id'] : FALSE);
537 // print the question specific template
538 $this->assignDisplayResultVariables($row, $answer_row);
539 $this->savant->display('tests/test_questions/' . $this->sPrefix . '_result.tmpl.php');
541 // print the generic question footer
542 $this->displayFooter();
547 * print the question template header
549 /*final public */function displayResultStatistics($row, $answers) {
550 TestQuestionCounter(TRUE);
551 $this->assignDisplayStatisticsVariables($row, $answers);
552 $this->savant->display('tests/test_questions/' . $this->sPrefix . '_stats.tmpl.php');
555 /*final public */function exportQTI($row, $encoding, $version) {
556 $this->savant->assign('encoding', $encoding);
557 //Convert all row values to html entities
558 foreach ($row as $k=>$v){
559 $row[$k] = htmlspecialchars($v, ENT_QUOTES, 'UTF-8'); //not using htmlentities cause it changes some languages falsely.
561 $this->assignQTIVariables($row);
562 if ($version=='2.1') {
563 $xml = $this->savant->fetch('tests/test_questions/'. $this->sPrefix . '_qti_2p1.tmpl.php');
565 $xml = $this->savant->fetch('tests/test_questions/'. $this->sPrefix . '_qti_1p2.tmpl.php');
571 * print the question template header
573 /*final private */function displayHeader($weight, $score = FALSE, $question_id = FALSE) {
574 TestQuestionCounter(TRUE);
576 if ($score) $score = intval($score);
577 $this->savant->assign('question_id', $question_id);
578 $this->savant->assign('score', $score);
579 $this->savant->assign('weight', $weight);
580 $this->savant->assign('type', _AT($this->sNameVar));
581 $this->savant->assign('number', TestQuestionCounter());
582 $this->savant->display('tests/test_questions/header.tmpl.php');
586 * print the question template footer
588 /*final private */function displayFooter() {
589 $this->savant->display('tests/test_questions/footer.tmpl.php');
593 * return only the non-empty choices from $row.
594 * assumes choices are sequential.
596 /*protected */function getChoices($row) {
598 for ($i=0; $i < 10; $i++) {
599 if ($row['choice_'.$i] != '') {
601 $choices[] = $row['choice_'.$i];
614 class OrderingQuestion extends AbstractTestQuestion {
615 /*protected */ var $sNameVar = 'test_ordering';
616 /*protected */ var $sPrefix = 'ordering';
618 /*protected */function assignDisplayResultVariables($row, $answer_row) {
619 $answers = explode('|', $answer_row['answer']);
621 $num_choices = count($this->getChoices($row));
623 $this->savant->assign('base_href', TR_BASE_HREF);
624 $this->savant->assign('num_choices', $num_choices);
625 $this->savant->assign('answers', $answers);
626 $this->savant->assign('row', $row);
629 /*protected */function assignQTIVariables($row) {
630 $choices = $this->getChoices($row);
631 $num_choices = count($choices);
633 $this->savant->assign('num_choices', $num_choices);
634 $this->savant->assign('row', $row);
637 /*protected */function assignDisplayVariables($row, $response) {
638 // determine the number of choices this question has
639 // and save those choices to be re-assigned back to $row
640 // in the randomized order.
641 $choices = $this->getChoices($row);
642 $num_choices = count($choices);
644 // response from the test_answers table is in the correct order
645 // so, they have to be re-randomized in the same order as the
646 // choices are. this is only possible because of the seed() method.
647 $response = explode('|', $response);
648 $new_response = array();
650 // randomize the order of choices and re-assign to $row
651 $this->seed($row['question_id']);
652 $rand = array_rand($choices, $num_choices);
653 for ($i=0; $i < 10; $i++) {
654 $row['choice_'.$i] = $choices[$rand[$i]];
655 $new_response[$i] = $response[$rand[$i]];
658 $this->savant->assign('num_choices', $num_choices);
659 $this->savant->assign('row', $row);
661 $this->savant->assign('response', $new_response);
664 /*protected */function assignDisplayStatisticsVariables($row, $answers) {
666 foreach ($answers as $answer) {
667 $num_results += $answer['count'];
670 $choices = $this->getChoices($row);
671 $num_choices = count($choices);
673 $final_answers = array(); // assoc array of # of times that key was used correctly 0, 1, ... $num -1
674 foreach ($answers as $key => $value) {
675 $values = explode('|', $key);
676 // we assume $values is never empty and contains $num number of answers
677 for ($i=0; $i<=$num_choices; $i++) {
678 if ($values[$i] == $i) {
679 $final_answers[$i] += $answers[$key]['count'];
684 $this->savant->assign('num_results', $num_results);
685 $this->savant->assign('num_choices', $num_choices);
686 $this->savant->assign('answers', $final_answers);
687 $this->savant->assign('row', $row);
690 /*public */function mark($row) {
691 $this->seed($row['question_id']);
692 $num_choices = count($_POST['answers'][$row['question_id']]);
693 $answers = range(0, $num_choices-1);
694 $answers = array_rand($answers, $num_choices);
696 // Disturb the seed for ordering questions after mark to avoid the deterioration
697 // of the random distribution due to a repeated initialization of the same random seed
700 $num_answer_correct = 0;
702 $ordered_answers = array();
704 for ($i = 0; $i < $num_choices ; $i++) {
705 $_POST['answers'][$row['question_id']][$i] = intval($_POST['answers'][$row['question_id']][$i]);
707 if ($_POST['answers'][$row['question_id']][$i] == -1) {
708 // nothing to do. it was left blank
709 } else if ($_POST['answers'][$row['question_id']][$i] == $answers[$i]) {
710 $num_answer_correct++;
712 $ordered_answers[$answers[$i]] = $_POST['answers'][$row['question_id']][$i];
714 ksort($ordered_answers);
718 // to avoid roundoff errors:
719 if ($num_answer_correct == $num_choices) {
720 $score = $row['weight'];
721 } else if ($num_answer_correct > 0) {
722 $score = number_format($row['weight'] / $num_choices * $num_answer_correct, 2);
723 if ( (float) (int) $score == $score) {
724 $score = (int) $score; // a whole number with decimals, eg. "2.00"
726 $score = trim($score, '0'); // remove trailing zeros, if any, eg. "2.50"
730 $_POST['answers'][$row['question_id']] = implode('|', $ordered_answers);
735 //QTI Import Ordering Question
736 function importQTI($_POST){
739 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
740 require_once(TR_INCLUDE_PATH.'classes/Utility.class.php');
743 if ($_POST['question'] == ''){
744 $missing_fields[] = _AT('question');
747 if (trim($_POST['choice'][0]) == '') {
748 $missing_fields[] = _AT('item').' 1';
750 if (trim($_POST['choice'][1]) == '') {
751 $missing_fields[] = _AT('item').' 2';
754 if ($missing_fields) {
755 $missing_fields = implode(', ', $missing_fields);
756 $msg->addError(array('EMPTY_FIELDS', $missing_fields));
759 if (!$msg->containsErrors()) {
760 $choice_new = array(); // stores the non-blank choices
761 $answer_new = array(); // stores the non-blank answers
762 $order = 0; // order count
763 for ($i=0; $i<10; $i++) {
765 * Db defined it to be 255 length, chop strings off it it's less than that
768 $_POST['choice'][$i] = Utility::validateLength($_POST['choice'][$i], 255);
769 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
771 if ($_POST['choice'][$i] != '') {
772 /* filter out empty choices/ remove gaps */
773 $choice_new[] = $_POST['choice'][$i];
774 $answer_new[] = $order++;
778 $_POST['choice'] = array_pad($choice_new, 10, '');
779 $answer_new = array_pad($answer_new, 10, 0);
780 // $_POST['feedback'] = $addslashes($_POST['feedback']);
781 // $_POST['question'] = $addslashes($_POST['question']);
783 $sql_params = array( $_POST['category_id'],
808 $sql = vsprintf(TR_SQL_QUESTION_ORDERING, $sql_params);
810 // $result = mysql_query($sql, $db);
811 // if ($result==true){
813 if ($dao->execute($sql)) {
814 return mysql_insert_id();
824 class TruefalseQuestion extends AbstracttestQuestion {
825 /*protected */ var $sPrefix = 'truefalse';
826 /*protected */ var $sNameVar = 'test_tf';
828 /*protected */function assignQTIVariables($row) {
829 $this->savant->assign('row', $row);
832 /*protected */function assignDisplayResultVariables($row, $answer_row) {
834 $this->savant->assign('base_href', TR_BASE_HREF);
835 $this->savant->assign('answers', $answer_row['answer']);
836 $this->savant->assign('row', $row);
839 /*protected */function assignDisplayVariables($row, $response) {
840 $this->savant->assign('row', $row);
841 $this->savant->assign('response', $response);
844 /*protected */function assignDisplayStatisticsVariables($row, $answers) {
846 foreach ($answers as $answer) {
847 $num_results += $answer['count'];
850 $this->savant->assign('num_results', $num_results);
851 $this->savant->assign('num_blanks', (int) $answers['-1']['count']);
852 $this->savant->assign('num_true', (int) $answers['1']['count']);
853 $this->savant->assign('num_false', (int) $answers['2']['count']);
854 $this->savant->assign('row', $row);
857 /*public */function mark($row) {
858 $_POST['answers'][$row['question_id']] = intval($_POST['answers'][$row['question_id']]);
860 if ($row['answer_0'] == $_POST['answers'][$row['question_id']]) {
861 return (int) $row['weight'];
866 //QTI Import True/False Question
867 function importQTI($_POST){
868 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
869 global $msg, $db, $_course_id;
871 if ($_POST['question'] == ''){
872 $msg->addError(array('EMPTY_FIELDS', _AT('statement')));
875 //assign true answer to 1, false answer to 2, idk to 3, for ATutor
876 if ($_POST['answer'] == 'ChoiceT'){
877 $_POST['answer'] = 1;
879 $_POST['answer'] = 2;
882 if (!$msg->containsErrors()) {
883 // $_POST['feedback'] = $addslashes($_POST['feedback']);
884 // $_POST['question'] = $addslashes($_POST['question']);
887 $sql_params = array( $_POST['category_id'],
893 $sql = vsprintf(TR_SQL_QUESTION_TRUEFALSE, $sql_params);
894 // $result = mysql_query($sql, $db);
895 // if ($result==true){
897 if ($dao->execute($sql)) {
898 return mysql_insert_id();
908 class LikertQuestion extends AbstracttestQuestion {
909 /*protected */ var $sPrefix = 'likert';
910 /*protected */ var $sNameVar = 'test_lk';
912 /*protected */function assignQTIVariables($row) {
913 $choices = $this->getChoices($row);
914 $num_choices = count($choices);
916 $this->savant->assign('num_choices', $num_choices);
917 $this->savant->assign('row', $row);
920 /*protected */function assignDisplayResultVariables($row, $answer_row) {
921 $this->savant->assign('answer', $answer_row['answer']);
922 $this->savant->assign('row', $row);
925 /*protected */function assignDisplayVariables($row, $response) {
926 $choices = $this->getChoices($row);
927 $num_choices = count($choices);
929 $this->savant->assign('num_choices', $num_choices);
930 $this->savant->assign('row', $row);
932 if (empty($response)) {
935 $this->savant->assign('response', $response);
938 /*protected */function assignDisplayStatisticsVariables($row, $answers) {
940 foreach ($answers as $answer) {
941 $num_results += $answer['count'];
944 $choices = $this->getChoices($row);
945 $num_choices = count($choices);
948 for ($i=0; $i<$num_choices; $i++) {
949 $sum += ($i+1) * $answers[$i]['count'];
951 $average = round($sum/$num_results, 1);
953 $this->savant->assign('num_results', $num_results);
954 $this->savant->assign('average', $average);
955 $this->savant->assign('num_choices', $num_choices);
956 $this->savant->assign('num_blanks', (int) $answers['-1']['count']);
957 $this->savant->assign('answers', $answers);
958 $this->savant->assign('row', $row);
961 /*public */function mark($row) {
962 $_POST['answers'][$row['question_id']] = intval($_POST['answers'][$row['question_id']]);
966 //QTI Import Likert Question
967 function importQTI($_POST){
968 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
969 global $msg, $db, $_course_id;
970 // $_POST = $this->_POST;
972 $empty_fields = array();
973 if ($_POST['question'] == ''){
974 $empty_fields[] = _AT('question');
976 if ($_POST['choice'][0] == '') {
977 $empty_fields[] = _AT('choice').' 1';
980 if ($_POST['choice'][1] == '') {
981 $empty_fields[] = _AT('choice').' 2';
984 if (!empty($empty_fields)) {
985 // $msg->addError(array('EMPTY_FIELDS', implode(', ', $empty_fields)));
988 if (!$msg->containsErrors()) {
989 $_POST['feedback'] = '';
990 // $_POST['question'] = $addslashes($_POST['question']);
992 for ($i=0; $i<10; $i++) {
993 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
994 $_POST['answer'][$i] = intval($_POST['answer'][$i]);
996 if ($_POST['choice'][$i] == '') {
997 /* an empty option can't be correct */
998 $_POST['answer'][$i] = 0;
1002 $sql_params = array( $_POST['category_id'],
1006 $_POST['choice'][0],
1007 $_POST['choice'][1],
1008 $_POST['choice'][2],
1009 $_POST['choice'][3],
1010 $_POST['choice'][4],
1011 $_POST['choice'][5],
1012 $_POST['choice'][6],
1013 $_POST['choice'][7],
1014 $_POST['choice'][8],
1015 $_POST['choice'][9],
1016 $_POST['answer'][0],
1017 $_POST['answer'][1],
1018 $_POST['answer'][2],
1019 $_POST['answer'][3],
1020 $_POST['answer'][4],
1021 $_POST['answer'][5],
1022 $_POST['answer'][6],
1023 $_POST['answer'][7],
1024 $_POST['answer'][8],
1025 $_POST['answer'][9]);
1027 $sql = vsprintf(TR_SQL_QUESTION_LIKERT, $sql_params);
1028 // $result = mysql_query($sql, $db);
1029 // if ($result==true){
1031 if ($dao->execute($sql)) {
1032 return mysql_insert_id();
1042 class LongQuestion extends AbstracttestQuestion {
1043 /*protected */ var $sPrefix = 'long';
1044 /*protected */ var $sNameVar = 'test_open';
1046 /*protected */function assignQTIVariables($row) {
1047 $this->savant->assign('row', $row);
1050 /*protected */function assignDisplayResultVariables($row, $answer_row) {
1051 $this->savant->assign('answer', $answer_row['answer']);
1052 $this->savant->assign('row', $row);
1055 /*protected */function assignDisplayVariables($row, $response) {
1056 $this->savant->assign('row', $row);
1057 $this->savant->assign('response', $response);
1060 /*protected */function assignDisplayStatisticsVariables($row, $answers) {
1062 foreach ($answers as $answer) {
1063 $num_results += $answer['count'];
1066 $this->savant->assign('num_results', $num_results);
1067 $this->savant->assign('num_blanks', (int) $answers['']['count']);
1068 $this->savant->assign('answers', $answers);
1069 $this->savant->assign('row', $row);
1072 /*public */function mark($row) {
1074 $_POST['answers'][$row['question_id']] = $addslashes($_POST['answers'][$row['question_id']]);
1078 //QTI Import Open end/long Question
1079 function importQTI($_POST){
1080 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
1081 global $msg, $db, $_course_id;
1082 // $_POST = $this->_POST;
1084 if ($_POST['question'] == ''){
1085 // $msg->addError(array('EMPTY_FIELDS', _AT('question')));
1088 if (!$msg->containsErrors()) {
1089 // $_POST['feedback'] = $addslashes($_POST['feedback']);
1090 // $_POST['question'] = $addslashes($_POST['question']);
1092 if ($_POST['property']==''){
1093 $_POST['property'] = 4; //essay
1096 $sql_params = array( $_POST['category_id'],
1100 $_POST['property']);
1102 $sql = vsprintf(TR_SQL_QUESTION_LONG, $sql_params);
1104 // $result = mysql_query($sql, $db);
1105 // if ($result==true){
1107 if ($dao->execute($sql)) {
1108 return mysql_insert_id();
1118 class MatchingQuestion extends AbstracttestQuestion {
1119 /*protected */ var $sPrefix = 'matching';
1120 /*protected */ var $sNameVar = 'test_matching';
1122 /*protected */function assignQTIVariables($row) {
1123 $choices = $this->getChoices($row);
1124 $num_choices = count($choices);
1127 for ($i=0; $i < 10; $i++) {
1128 if ($row['option_'. $i] != '') {
1133 $this->savant->assign('num_choices', $num_choices);
1134 $this->savant->assign('num_options', $num_options);
1135 $this->savant->assign('row', $row);
1138 /*protected */function assignDisplayResultVariables($row, $answer_row) {
1140 for ($i=0; $i < 10; $i++) {
1141 if ($row['option_'. $i] != '') {
1146 $answer_row['answer'] = explode('|', $answer_row['answer']);
1150 $this->savant->assign('base_href', TR_BASE_HREF);
1151 $this->savant->assign('answers', $answer_row['answer']);
1152 $this->savant->assign('letters', $_letters);
1153 $this->savant->assign('num_options', $num_options);
1154 $this->savant->assign('row', $row);
1157 /*protected */function assignDisplayVariables($row, $response) {
1158 $choices = $this->getChoices($row);
1159 $num_choices = count($choices);
1161 if (empty($response)) {
1162 $response = array_fill(0, $num_choices, -1);
1164 $response = explode('|', $response);
1168 for ($i=0; $i < 10; $i++) {
1169 if ($row['option_'. $i] != '') {
1176 $this->savant->assign('num_choices', $num_choices);
1177 $this->savant->assign('base_href', TR_BASE_HREF);
1178 $this->savant->assign('letters', $_letters);
1179 $this->savant->assign('num_options', $num_options);
1180 $this->savant->assign('row', $row);
1182 $this->savant->assign('response', $response);
1185 /*protected */function assignDisplayStatisticsVariables($row, $answers) {
1186 $choices = $this->getChoices($row);
1187 $num_choices = count($choices);
1190 foreach ($answers as $answer) {
1191 $num_results += $answer['count'];
1194 foreach ($answers as $key => $value) {
1195 $values = explode('|', $key);
1196 if (count($values) > 1) {
1197 for ($i=0; $i<count($values); $i++) {
1198 $answers[$values[$i]]['count']++;
1203 $this->savant->assign('num_choices', $num_choices);
1204 $this->savant->assign('num_results', $num_results);
1205 $this->savant->assign('answers', $answers);
1206 $this->savant->assign('row', $row);
1209 /*public */function mark($row) {
1210 $num_choices = count($_POST['answers'][$row['question_id']]);
1211 $num_answer_correct = 0;
1212 foreach ($_POST['answers'][$row['question_id']] as $item_id => $response) {
1213 if ($row['answer_' . $item_id] == $response) {
1214 $num_answer_correct++;
1216 $_POST['answers'][$row['question_id']][$item_id] = intval($_POST['answers'][$row['question_id']][$item_id]);
1220 // to avoid roundoff errors:
1221 if ($num_answer_correct == $num_choices) {
1222 $score = $row['weight'];
1223 } else if ($num_answer_correct > 0) {
1224 $score = number_format($row['weight'] / $num_choices * $num_answer_correct, 2);
1225 if ( (float) (int) $score == $score) {
1226 $score = (int) $score; // a whole number with decimals, eg. "2.00"
1228 $score = trim($score, '0'); // remove trailing zeros, if any
1232 $_POST['answers'][$row['question_id']] = implode('|', $_POST['answers'][$row['question_id']]);
1237 //QTI Import Matching Question
1238 function importQTI($_POST){
1239 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
1240 global $msg, $db, $_course_id;
1241 // $_POST = $this->_POST;
1243 if (!is_array($_POST['answer'])){
1244 $temp = $_POST['answer'];
1245 $_POST['answer'] = array();
1246 $_POST['answer'][0] = $temp;
1248 ksort($_POST['answer']); //array_pad returns an array disregard of the array keys
1249 //default for matching is '-'
1250 $_POST['answer']= array_pad($_POST['answer'], 10, -1);
1252 for ($i = 0 ; $i < 10; $i++) {
1253 $_POST['groups'][$i] = trim($_POST['groups'][$i]);
1254 $_POST['answer'][$i] = (int) $_POST['answer'][$i];
1255 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
1258 if (!$_POST['groups'][0]
1259 || !$_POST['groups'][1]
1260 || !$_POST['choice'][0]
1261 || !$_POST['choice'][1]) {
1262 // $msg->addError('QUESTION_EMPTY');
1265 if (!$msg->containsErrors()) {
1266 // $_POST['feedback'] = $addslashes($_POST['feedback']);
1267 // $_POST['instructions'] = $addslashes($_POST['instructions']);
1269 $sql_params = array( $_POST['category_id'],
1273 $_POST['groups'][0],
1274 $_POST['groups'][1],
1275 $_POST['groups'][2],
1276 $_POST['groups'][3],
1277 $_POST['groups'][4],
1278 $_POST['groups'][5],
1279 $_POST['groups'][6],
1280 $_POST['groups'][7],
1281 $_POST['groups'][8],
1282 $_POST['groups'][9],
1283 $_POST['answer'][0],
1284 $_POST['answer'][1],
1285 $_POST['answer'][2],
1286 $_POST['answer'][3],
1287 $_POST['answer'][4],
1288 $_POST['answer'][5],
1289 $_POST['answer'][6],
1290 $_POST['answer'][7],
1291 $_POST['answer'][8],
1292 $_POST['answer'][9],
1293 $_POST['choice'][0],
1294 $_POST['choice'][1],
1295 $_POST['choice'][2],
1296 $_POST['choice'][3],
1297 $_POST['choice'][4],
1298 $_POST['choice'][5],
1299 $_POST['choice'][6],
1300 $_POST['choice'][7],
1301 $_POST['choice'][8],
1302 $_POST['choice'][9]);
1304 $sql = vsprintf(TR_SQL_QUESTION_MATCHINGDD, $sql_params);
1307 if ($dao->execute($sql)) {
1308 return mysql_insert_id();
1315 * matchingddQuestion
1318 class MatchingddQuestion extends MatchingQuestion {
1319 /*protected */ var $sPrefix = 'matchingdd';
1320 /*protected */ var $sNameVar = 'test_matchingdd';
1324 * multichoiceQuestion
1327 class MultichoiceQuestion extends AbstracttestQuestion {
1328 /*protected */ var $sPrefix = 'multichoice';
1329 /*protected */var $sNameVar = 'test_mc';
1331 /*protected */function assignQTIVariables($row) {
1332 $choices = $this->getChoices($row);
1333 $num_choices = count($choices);
1335 $this->savant->assign('num_choices', $num_choices);
1336 $this->savant->assign('row', $row);
1339 /*protected */function assignDisplayResultVariables($row, $answer_row) {
1340 if (strpos($answer_row['answer'], '|') !== false) {
1341 $answer_row['answer'] = explode('|', $answer_row['answer']);
1343 $answer_row['answer'] = array($answer_row['answer']);
1346 $this->savant->assign('base_href', TR_BASE_HREF);
1347 $this->savant->assign('answers', $answer_row['answer']);
1348 $this->savant->assign('row', $row);
1351 /*protected */function assignDisplayVariables($row, $response) {
1352 $choices = $this->getChoices($row);
1353 $num_choices = count($choices);
1355 if ($response == '') {
1358 $response = explode('|', $response);
1359 $this->savant->assign('response', $response);
1361 $this->savant->assign('num_choices', $num_choices);
1362 $this->savant->assign('row', $row);
1365 /*protected */function assignDisplayStatisticsVariables($row, $answers) {
1366 $choices = $this->getChoices($row);
1367 $num_choices = count($choices);
1370 foreach ($answers as $answer) {
1371 $num_results += $answer['count'];
1374 foreach ($answers as $key => $value) {
1375 $values = explode('|', $key);
1376 if (count($values) > 1) {
1377 for ($i=0; $i<count($values); $i++) {
1378 $answers[$values[$i]]['count']++;
1383 $this->savant->assign('num_choices', $num_choices);
1384 $this->savant->assign('num_results', $num_results);
1385 $this->savant->assign('num_blanks', (int) $answers['-1']['count']);
1386 $this->savant->assign('answers', $answers);
1387 $this->savant->assign('row', $row);
1390 /*public */function mark($row) {
1392 $_POST['answers'][$row['question_id']] = intval($_POST['answers'][$row['question_id']]);
1393 if ($row['answer_' . $_POST['answers'][$row['question_id']]]) {
1394 $score = $row['weight'];
1395 } else if ($_POST['answers'][$row['question_id']] == -1) {
1397 for($i=0; $i<10; $i++) {
1398 $has_answer += $row['answer_'.$i];
1400 if (!$has_answer && $row['weight']) {
1401 // If MC has no answer and user answered "leave blank"
1402 $score = $row['weight'];
1408 //QTI Import Multiple Choice Question
1409 function importQTI($_POST){
1410 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
1411 global $msg, $db, $_course_id;
1412 // $_POST = $this->_POST;
1413 if ($_POST['question'] == ''){
1414 $msg->addError(array('EMPTY_FIELDS', _AT('question')));
1417 if (!$msg->containsErrors()) {
1418 // $_POST['question'] = $addslashes($_POST['question']);
1420 for ($i=0; $i<10; $i++) {
1421 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
1424 $answers = array_fill(0, 10, 0);
1425 if (is_array($_POST['answer'])){
1426 $answers[0] = 1; //default the first to be the right answer. TODO, use summation of points.
1428 $answers[$_POST['answer']] = 1;
1431 $sql_params = array( $_POST['category_id'],
1435 $_POST['choice'][0],
1436 $_POST['choice'][1],
1437 $_POST['choice'][2],
1438 $_POST['choice'][3],
1439 $_POST['choice'][4],
1440 $_POST['choice'][5],
1441 $_POST['choice'][6],
1442 $_POST['choice'][7],
1443 $_POST['choice'][8],
1444 $_POST['choice'][9],
1456 $sql = vsprintf(TR_SQL_QUESTION_MULTI, $sql_params);
1457 // $result = mysql_query($sql, $db);
1458 // if ($result==true){
1460 if ($dao->execute($sql)) {
1461 return mysql_insert_id();
1468 * multianswerQuestion
1471 class MultianswerQuestion extends MultichoiceQuestion {
1472 /*protected */ var $sPrefix = 'multianswer';
1473 /*protected */ var $sNameVar = 'test_ma';
1475 /*public */function mark($row) {
1476 $num_correct = array_sum(array_slice($row, 3));
1478 if (is_array($_POST['answers'][$row['question_id']]) && count($_POST['answers'][$row['question_id']]) > 1) {
1479 if (($i = array_search('-1', $_POST['answers'][$row['question_id']])) !== FALSE) {
1480 unset($_POST['answers'][$row['question_id']][$i]);
1482 $num_answer_correct = 0;
1483 foreach ($_POST['answers'][$row['question_id']] as $item_id => $answer) {
1484 if ($row['answer_' . $answer]) {
1486 $num_answer_correct++;
1489 $num_answer_correct--;
1491 $_POST['answers'][$row['question_id']][$item_id] = intval($_POST['answers'][$row['question_id']][$item_id]);
1493 if ($num_answer_correct == $num_correct) {
1494 $score = $row['weight'];
1498 $_POST['answers'][$row['question_id']] = implode('|', $_POST['answers'][$row['question_id']]);
1501 $_POST['answers'][$row['question_id']] = '-1'; // left blank
1507 //QTI Import multianswer Question
1508 function importQTI($_POST){
1509 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
1510 require_once(TR_INCLUDE_PATH.'classes/Utility.class.php');
1511 global $msg, $db, $_course_id;
1512 // $_POST = $this->_POST;
1514 if ($_POST['question'] == ''){
1515 $msg->addError(array('EMPTY_FIELDS', _AT('question')));
1518 //Multiple answer can have 0+ answers, in the QTIImport.class, if size(answer) < 2, answer will be came a scalar.
1519 //The following code will change $_POST[answer] back to a vector.
1520 $_POST['answer'] = $_POST['answers'];
1522 if (!$msg->containsErrors()) {
1523 $choice_new = array(); // stores the non-blank choices
1524 $answer_new = array(); // stores the associated "answer" for the choices
1526 foreach ($_POST['choice'] as $choiceNum=>$choiceOpt) {
1527 $choiceOpt = Utility::validateLength($choiceOpt, 255);
1528 $choiceOpt = trim($choiceOpt);
1529 $_POST['answer'][$choiceNum] = intval($_POST['answer'][$choiceNum]);
1530 if ($choiceOpt == '') {
1531 /* an empty option can't be correct */
1532 $_POST['answer'][$choiceNum] = 0;
1534 /* filter out empty choices/ remove gaps */
1535 $choice_new[] = $choiceOpt;
1536 if (in_array($choiceNum, $_POST['answer'])){
1542 if ($_POST['answer'][$choiceNum] != 0)
1547 if ($has_answer != TRUE) {
1549 $hidden_vars['required'] = htmlspecialchars($_POST['required']);
1550 $hidden_vars['feedback'] = htmlspecialchars($_POST['feedback']);
1551 $hidden_vars['question'] = htmlspecialchars($_POST['question']);
1552 $hidden_vars['category_id'] = htmlspecialchars($_POST['category_id']);
1554 for ($i = 0; $i < count($choice_new); $i++) {
1555 $hidden_vars['answer['.$i.']'] = htmlspecialchars($answer_new[$i]);
1556 $hidden_vars['choice['.$i.']'] = htmlspecialchars($choice_new[$i]);
1559 $msg->addConfirm('NO_ANSWER', $hidden_vars);
1561 //add slahes throughout - does that fix it?
1562 $_POST['answer'] = $answer_new;
1563 $_POST['choice'] = $choice_new;
1564 $_POST['answer'] = array_pad($_POST['answer'], 10, 0);
1565 $_POST['choice'] = array_pad($_POST['choice'], 10, '');
1567 // $_POST['feedback'] = $addslashes($_POST['feedback']);
1568 // $_POST['question'] = $addslashes($_POST['question']);
1570 $sql_params = array( $_POST['category_id'],
1574 $_POST['choice'][0],
1575 $_POST['choice'][1],
1576 $_POST['choice'][2],
1577 $_POST['choice'][3],
1578 $_POST['choice'][4],
1579 $_POST['choice'][5],
1580 $_POST['choice'][6],
1581 $_POST['choice'][7],
1582 $_POST['choice'][8],
1583 $_POST['choice'][9],
1584 $_POST['answer'][0],
1585 $_POST['answer'][1],
1586 $_POST['answer'][2],
1587 $_POST['answer'][3],
1588 $_POST['answer'][4],
1589 $_POST['answer'][5],
1590 $_POST['answer'][6],
1591 $_POST['answer'][7],
1592 $_POST['answer'][8],
1593 $_POST['answer'][9]);
1595 $sql = vsprintf(TR_SQL_QUESTION_MULTIANSWER, $sql_params);
1597 // $result = mysql_query($sql, $db);
1598 // if ($result==true){
1600 if ($dao->execute($sql)) {
1601 return mysql_insert_id();