1c370b127aace6591d24e365c4f804f1d1f4e996
[acontent.git] / docs / include / classes / testQuestions.class.php
1 <?php
2 /************************************************************************/
3 /* AContent                                                             */
4 /************************************************************************/
5 /* Copyright (c) 2010                                                   */
6 /* Inclusive Design Institute                                           */
7 /*                                                                      */
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 /************************************************************************/
12
13 require_once(TR_INCLUDE_PATH.'lib/test_question_queries.inc.php');
14
15 /*
16  * Steps to follow when adding a new question type:
17  *
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:
22  *
23  *        assignQTIVariables()
24  *        assignDisplayResultVariables()
25  *        assignDisplayVariables()
26  *        assignDisplayStatisticsVariables()
27  *                importQti()
28  *     
29  *     And implement mark() which is used for marking the result.
30  *
31  * 2 - Add the new class name to $question_classes in test_question_factory()
32  *
33  * 3 - Add $sNameVar to the language database.
34  *
35  * 4 - Create the following files for creating and editing the question,
36  *     where "{PREFIX}" is the value defined by $sPrefix:
37  *
38  *     /tools/tests/create_question_{PREFIX}.php
39  *     /tools/tests/edit_question_{PREFIX}.php
40  *
41  * 5 - Add those two newly created pages to 
42  *     /tests/module.php
43  *
44  * 6 - Create the following template files:
45  *
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
50  *
51  * 7 - Add the new question type to qti import/export tools,
52  *     Implement the following methods, which set template variables:
53  *
54  *     include/classes/QTI/QTIParser.class.php
55  *         getQuestionType()
56  *
57  * 8 - Done!
58  **/
59 class TestQuestions {
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();
67                 }
68                 asort($question_prefix_names);
69                 return $question_prefix_names;
70         }
71
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';
83
84                 return $question_classes;
85         }
86
87         /**
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.
92          */
93         /*static */function & getQuestion($question_type) {
94                 static $objs, $question_classes;
95
96                 if (isset($objs[$question_type])) {
97                         return $objs[$question_type];
98                 }
99
100                 $question_classes = TestQuestions::getQuestionClasses();
101
102                 if (isset($question_classes[$question_type])) {
103                         global $savant;
104                         $objs[$question_type] = new $question_classes[$question_type]($savant);
105                 } else {
106                         return FALSE;
107                 }
108
109                 return $objs[$question_type];
110         }
111 }
112
113 /** 
114  * Export test questions
115  * @param       array   an array consist of all the ids of the questions in which we desired to export.
116  */
117 function test_question_qti_export_v2p1($question_ids) {
118         global $_course_id;
119         
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
125
126         global $savant, $db, $languageManager;
127
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();
133
134         $zipfile = new zipfile();
135         $zipfile->create_dir('resources/'); // for all the dependency files
136         $resources    = array();
137         $dependencies = array();
138
139         asort($question_ids);
140
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();
148         
149                         $text_blob = implode(' ', $row);
150                         $local_dependencies = get_html_resources($text_blob);
151                         $dependencies = array_merge($dependencies, $local_dependencies);
152         
153                         $resources[] = array('href'         => 'question_'.$row['question_id'].'.xml',
154                                                                  'dependencies' => array_keys($local_dependencies));
155         
156                         //TODO
157                         $savant->assign('xml_content', $xml);
158                         $savant->assign('title', $row['question']);
159                         $xml = $savant->fetch('tests/test_questions/wrapper.tmpl.php');
160         
161                         $zipfile->add_file($xml, 'question_'.$row['question_id'].'.xml');
162                 }
163         }
164
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));
168         }
169
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');
175
176         $zipfile->add_file($manifest_xml, 'imsmanifest.xml');
177
178         $zipfile->close();
179
180         $filename = str_replace(array(' ', ':'), '_', $course_row['title'].'-'._AT('question_database').'-'.date('Ymd'));
181         $zipfile->send_file($filename);
182         exit;
183 }
184
185
186 /** 
187  * Export test questions
188  * @param       array   an array consist of all the ids of the questions in which we desired to export.
189  */
190 function test_question_qti_export($question_ids) {
191         global $_course_id;
192         
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
198
199         global $savant, $db, $languageManager;
200
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();
206
207         $zipfile = new zipfile();
208         $zipfile->create_dir('resources/'); // for all the dependency files
209         $resources    = array();
210         $dependencies = array();
211
212         asort($question_ids);
213
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']);
219                         $local_xml = '';
220                         $local_xml = $obj->exportQTI($row, $course_language_charset, '1.2.1');
221                         $local_dependencies = array();
222         
223                         $text_blob = implode(' ', $row);
224                         $local_dependencies = get_html_resources($text_blob);
225                         $dependencies = array_merge($dependencies, $local_dependencies);
226         
227         //              $resources[] = array('href'         => 'question_'.$row['question_id'].'.xml',
228         //                                                       'dependencies' => array_keys($local_dependencies));
229         
230                         $xml = $xml . "\n\n" . $local_xml;
231                 }               
232         }
233         $xml = trim($xml);
234
235         //TODO
236         $savant->assign('xml_content', $xml);
237         $savant->assign('title', $row['question']);
238         $xml = $savant->fetch('tests/test_questions/wrapper.tmpl.php');
239
240         $xml_filename = 'atutor_questions.xml';
241         $zipfile->add_file($xml, $xml_filename);
242
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));
246         }
247
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');
255
256         $zipfile->add_file($manifest_xml, 'imsmanifest.xml');
257
258         $zipfile->close();
259
260         $filename = str_replace(array(' ', ':'), '_', $course_row['title'].'-'._AT('question_database').'-'.date('Ymd'));
261         $zipfile->send_file($filename);
262         exit;
263 }
264
265
266 /** 
267  * Export test 
268  * @param       int             test id
269  * @param       string  the test title
270  * @param       ref             [OPTIONAL] zip object reference
271  * @param       array   [OPTIONAL] list of already added files.
272  */
273 function test_qti_export($tid, $test_title='', $zipfile = null){
274         global $_course_id;
275         
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;
283         global $course_id;
284
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();
290         $imported_files;
291         $zipflag = false;
292
293         if ($zipfile==null){
294                 $zipflag = true;
295         }
296
297         if ($test_zipped_files == null){
298                 $test_zipped_files = array();
299         }
300
301         if ($zipflag){
302                 $zipfile = new zipfile();
303                 $zipfile->create_dir('resources/'); // for all the dependency files
304         }
305         $resources    = array();
306         $dependencies = array();
307
308 //      don't want to sort it, i want the same order out.
309 //      asort($question_ids);
310
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();
316 //
317 //      if (is_array($question_rows)){
318 //              foreach ($question_rows as $question_row) $question_ids[] = $question_row['question_id'];
319 //      }
320 //
321 //      //No questions in the test
322 //      if (sizeof($question_ids)==0){
323 //              return;
324 //      }
325 //
326 //      $question_ids_delim = implode(',',$question_ids);       
327 //
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";
330 //
331 //      $result = mysql_query($sql, $db);
332 //      while ($row = mysql_fetch_assoc($result)) {
333         $testsQuestionsAssocDAO = new TestsQuestionsAssocDAO();
334         $question_rows = $testsQuestionsAssocDAO->getByTestID($tid);
335
336         if (!is_array($question_rows)) return false;
337         else {
338                 foreach ($question_rows as $row) {
339                         $obj = TestQuestions::getQuestion($row['type']);
340                         $local_xml = '';
341                         $local_xml = $obj->exportQTI($row, $course_language_charset, '1.2.1');
342                         $local_dependencies = array();
343         
344                         $text_blob = implode(' ', $row);
345                         $local_dependencies = get_html_resources($text_blob);
346                         $dependencies = array_merge($dependencies, $local_dependencies);
347         
348                         $xml = $xml . "\n\n" . $local_xml;
349                 }
350         }
351
352         //files that are found inside the test; used by print_organization(), to add all test files into QTI/ folder.
353         $test_files = $dependencies;
354
355         $resources[] = array('href'         => 'tests_'.$tid.'.xml',
356                                                  'dependencies' => array_keys($dependencies));
357
358         $xml = trim($xml);
359
360         //get test title
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);
366         
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');
371
372         $xml_filename = 'tests_'.$tid.'.xml';
373         if (!$use_cc){          
374                 $zipfile->add_file($xml, $xml_filename);
375         } else {
376                 $zipfile->add_file($xml, 'QTI/'.$xml_filename);
377         }
378
379         // add any dependency files:
380         if (!$use_cc){
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;
386                         }
387                 }
388         }
389
390         if ($zipflag){
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);
397                 
398                 $manifest_xml = $savant->fetch('tests/test_questions/manifest_qti_1p2.tmpl.php');
399                 $zipfile->add_file($manifest_xml, 'imsmanifest.xml');
400
401                 $zipfile->close();
402
403                 $filename = str_replace(array(' ', ':'), '_', $course_row['title'].'-'.$test_title.'-'.date('Ymd'));
404                 $zipfile->send_file($filename);
405                 exit;
406         }
407         
408         $return_array[$xml_filename] = array_keys($dependencies);
409         return $return_array;
410         //done
411 }
412
413
414 /* 
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.
419  * @author harris
420  */
421 function recursive_mkdir($path, $mode = 0700) {
422     $dirs = explode(DIRECTORY_SEPARATOR , $path);
423     $count = count($dirs);
424     $path = '';
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)) {
429             return false;
430         }
431     }
432     return true;
433 }
434
435
436 /**
437 * keeps count of the question number (when displaying the question)
438 * need this function because PHP 4 doesn't support static members
439 */
440 function TestQuestionCounter($increment = FALSE) {
441         static $count;
442
443         if (!isset($count)) { 
444                 $count = 0;
445         }
446         if ($increment) {
447                 $count++;
448         }
449
450         return $count;
451 }
452
453
454 /**
455  * testQuestion
456  *
457  * Note that all PHP 5 OO declarations and signatures are commented out to be
458  * backwards compatible with PHP 4.
459  *
460  */
461 /*abstract */ class AbstractTestQuestion  {
462         /**
463         * Savant2 $savant - refrence to the savant obj
464         */
465         /*protected */ var $savant;
466
467         /**
468         * Constructor method.  Initialises variables.
469         */
470         function AbstractTestQuestion(&$savant) { $this->savant =& $savant; }
471
472         /**
473         * Public
474         */
475         /*final public */function seed($salt) {
476                 /**
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.
480                 */
481                 srand($salt + $_SESSION['user_id']);
482         }
483
484         /**
485         * Public
486         */
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));
493         }
494
495         /**
496         * Public
497         * Prints the name of this question
498         */
499         /*final public */function printName() { echo $this->getName(); }
500
501         /**
502         * Public
503         * Prints the name of this question
504         */
505         /*final public */function getName() { return _AT($this->sNameVar); }
506
507         /**
508         * Public
509         * Returns the prefix string (used for file names)
510         */
511         /*final public */function getPrefix() { return $this->sPrefix; }
512
513         /**
514         * Display the current question (for taking or previewing a test/question)
515         */
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']);
520
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');
525                 
526                 // print the generic question footer
527                 $this->displayFooter();
528         }
529
530         /**
531         * Display the result for the current question
532         */
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);
536
537                 // print the question specific template
538                 $this->assignDisplayResultVariables($row, $answer_row);
539                 $this->savant->display('tests/test_questions/' . $this->sPrefix . '_result.tmpl.php');
540                 
541                 // print the generic question footer
542                 $this->displayFooter();
543         }
544
545
546         /**
547         * print the question template header
548         */
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');
553         }
554
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.
560                 }
561                 $this->assignQTIVariables($row);
562                 if ($version=='2.1') {
563                         $xml = $this->savant->fetch('tests/test_questions/'. $this->sPrefix . '_qti_2p1.tmpl.php');
564                 } else {        
565                         $xml = $this->savant->fetch('tests/test_questions/'. $this->sPrefix . '_qti_1p2.tmpl.php');
566                 }
567                 return $xml;
568         }
569
570         /**
571         * print the question template header
572         */
573         /*final private */function displayHeader($weight, $score = FALSE, $question_id = FALSE) {
574                 TestQuestionCounter(TRUE);
575                 
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');
583         }
584
585         /**
586         * print the question template footer
587         */
588         /*final private */function displayFooter() {
589                 $this->savant->display('tests/test_questions/footer.tmpl.php');
590         }
591
592         /**
593         * return only the non-empty choices from $row.
594         * assumes choices are sequential.
595         */
596         /*protected */function getChoices($row) {
597                 $choices = array();
598                 for ($i=0; $i < 10; $i++) {
599                         if ($row['choice_'.$i] != '') {
600                                 $num_choices++;
601                                 $choices[] = $row['choice_'.$i];
602                         } else {
603                                 break;
604                         }
605                 }
606                 return $choices;
607         }
608 }
609
610 /**
611 * orderingQuestion
612 *
613 */
614 class OrderingQuestion extends AbstractTestQuestion {
615         /*protected */ var $sNameVar = 'test_ordering';
616         /*protected */ var $sPrefix = 'ordering';
617         
618         /*protected */function assignDisplayResultVariables($row, $answer_row) {
619                 $answers = explode('|', $answer_row['answer']);
620
621                 $num_choices = count($this->getChoices($row));
622
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);
627         }
628
629         /*protected */function assignQTIVariables($row) {
630                 $choices = $this->getChoices($row);
631                 $num_choices = count($choices);
632
633                 $this->savant->assign('num_choices', $num_choices);
634                 $this->savant->assign('row', $row);
635         }
636
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);
643
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();
649
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]];
656                 }
657
658                 $this->savant->assign('num_choices', $num_choices);
659                 $this->savant->assign('row', $row);
660
661                 $this->savant->assign('response', $new_response);
662         }
663
664         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
665                 $num_results = 0;               
666                 foreach ($answers as $answer) {
667                         $num_results += $answer['count'];
668                 }
669
670                 $choices = $this->getChoices($row);
671                 $num_choices = count($choices);
672
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'];
680                                 }
681                         }
682                 }
683
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);
688         }
689
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);
695                 
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
698                 $this->unseed();
699
700                 $num_answer_correct = 0;
701
702                 $ordered_answers = array();
703
704                 for ($i = 0; $i < $num_choices ; $i++) {
705                         $_POST['answers'][$row['question_id']][$i] = intval($_POST['answers'][$row['question_id']][$i]);
706
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++;
711                         }
712                         $ordered_answers[$answers[$i]] = $_POST['answers'][$row['question_id']][$i];
713                 }
714                 ksort($ordered_answers);
715
716                 $score = 0;
717
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"
725                         } else {
726                                 $score = trim($score, '0'); // remove trailing zeros, if any, eg. "2.50"
727                         }
728                 }
729
730                 $_POST['answers'][$row['question_id']] = implode('|', $ordered_answers);
731
732                 return $score;
733         }
734
735         //QTI Import Ordering Question
736         function importQTI($_POST){
737                 global $_course_id;
738                 
739                 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
740                 require_once(TR_INCLUDE_PATH.'classes/Utility.class.php');
741                 global $msg, $db;
742                 
743                 if ($_POST['question'] == ''){
744                         $missing_fields[] = _AT('question');
745                 }
746
747                 if (trim($_POST['choice'][0]) == '') {
748                         $missing_fields[] = _AT('item').' 1';
749                 }
750                 if (trim($_POST['choice'][1]) == '') {
751                         $missing_fields[] = _AT('item').' 2';
752                 }
753
754                 if ($missing_fields) {
755                         $missing_fields = implode(', ', $missing_fields);
756                         $msg->addError(array('EMPTY_FIELDS', $missing_fields));
757                 }
758
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++) {
764                                 /**
765                                  * Db defined it to be 255 length, chop strings off it it's less than that
766                                  * @harris
767                                  */
768                                 $_POST['choice'][$i] = Utility::validateLength($_POST['choice'][$i], 255);
769                                 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
770
771                                 if ($_POST['choice'][$i] != '') {
772                                         /* filter out empty choices/ remove gaps */
773                                         $choice_new[] = $_POST['choice'][$i];
774                                         $answer_new[] = $order++;
775                                 }
776                         }
777
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']);
782                 
783                         $sql_params = array(    $_POST['category_id'], 
784                                                                         $_course_id,
785                                                                         $_POST['feedback'], 
786                                                                         $_POST['question'], 
787                                                                         $_POST['choice'][0], 
788                                                                         $_POST['choice'][1], 
789                                                                         $_POST['choice'][2], 
790                                                                         $_POST['choice'][3], 
791                                                                         $_POST['choice'][4], 
792                                                                         $_POST['choice'][5], 
793                                                                         $_POST['choice'][6], 
794                                                                         $_POST['choice'][7], 
795                                                                         $_POST['choice'][8], 
796                                                                         $_POST['choice'][9], 
797                                                                         $answer_new[0], 
798                                                                         $answer_new[1], 
799                                                                         $answer_new[2], 
800                                                                         $answer_new[3], 
801                                                                         $answer_new[4], 
802                                                                         $answer_new[5], 
803                                                                         $answer_new[6], 
804                                                                         $answer_new[7], 
805                                                                         $answer_new[8], 
806                                                                         $answer_new[9]);
807
808                         $sql = vsprintf(TR_SQL_QUESTION_ORDERING, $sql_params);
809
810 //                      $result = mysql_query($sql, $db);
811 //                      if ($result==true){
812                         $dao = new DAO();
813                         if ($dao->execute($sql)) {
814                                 return mysql_insert_id();
815                         }                       
816                 }
817         }
818 }
819
820 /**
821 * truefalseQuestion
822 *
823 */
824 class TruefalseQuestion extends AbstracttestQuestion {
825         /*protected */ var $sPrefix = 'truefalse';
826         /*protected */ var $sNameVar   = 'test_tf';
827
828         /*protected */function assignQTIVariables($row) {
829                 $this->savant->assign('row', $row);
830         }
831
832         /*protected */function assignDisplayResultVariables($row, $answer_row) {
833
834                 $this->savant->assign('base_href', TR_BASE_HREF);
835                 $this->savant->assign('answers', $answer_row['answer']);
836                 $this->savant->assign('row', $row);
837         }
838
839         /*protected */function assignDisplayVariables($row, $response) {
840                 $this->savant->assign('row', $row);
841                 $this->savant->assign('response', $response);
842         }
843
844         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
845                 $num_results = 0;               
846                 foreach ($answers as $answer) {
847                         $num_results += $answer['count'];
848                 }
849
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);
855         }
856
857         /*public */function mark($row) { 
858                 $_POST['answers'][$row['question_id']] = intval($_POST['answers'][$row['question_id']]);
859
860                 if ($row['answer_0'] == $_POST['answers'][$row['question_id']]) {
861                         return (int) $row['weight'];
862                 } // else:
863                 return 0;
864         }
865
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;
870
871                 if ($_POST['question'] == ''){
872                         $msg->addError(array('EMPTY_FIELDS', _AT('statement')));
873                 }
874
875                 //assign true answer to 1, false answer to 2, idk to 3, for ATutor
876                 if  ($_POST['answer'] == 'ChoiceT'){
877                         $_POST['answer'] = 1;
878                 } else {
879                         $_POST['answer'] = 2;   
880                 }
881
882                 if (!$msg->containsErrors()) {
883 //                      $_POST['feedback'] = $addslashes($_POST['feedback']);
884 //                      $_POST['question'] = $addslashes($_POST['question']);
885
886
887                         $sql_params = array(    $_POST['category_id'], 
888                                                                         $_course_id,
889                                                                         $_POST['feedback'], 
890                                                                         $_POST['question'], 
891                                                                         $_POST['answer']);
892
893                         $sql = vsprintf(TR_SQL_QUESTION_TRUEFALSE, $sql_params);
894 //                      $result = mysql_query($sql, $db);
895 //                      if ($result==true){
896                         $dao = new DAO();
897                         if ($dao->execute($sql)) {      
898                                 return mysql_insert_id();
899                         }
900                 }
901         }
902 }
903
904 /**
905 * likertQuestion
906 *
907 */
908 class LikertQuestion extends AbstracttestQuestion {
909         /*protected */ var $sPrefix = 'likert';
910         /*protected */ var $sNameVar   = 'test_lk';
911
912         /*protected */function assignQTIVariables($row) {
913                 $choices = $this->getChoices($row);
914                 $num_choices = count($choices);
915
916                 $this->savant->assign('num_choices', $num_choices);
917                 $this->savant->assign('row', $row);
918         }
919
920         /*protected */function assignDisplayResultVariables($row, $answer_row) {
921                 $this->savant->assign('answer', $answer_row['answer']);
922                 $this->savant->assign('row', $row);
923         }
924
925         /*protected */function assignDisplayVariables($row, $response) {
926                 $choices = $this->getChoices($row);
927                 $num_choices = count($choices);
928
929                 $this->savant->assign('num_choices', $num_choices);
930                 $this->savant->assign('row', $row);
931
932                 if (empty($response)) {
933                         $response = -1;
934                 }
935                 $this->savant->assign('response', $response);
936         }
937
938         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
939                 $num_results = 0;               
940                 foreach ($answers as $answer) {
941                         $num_results += $answer['count'];
942                 }
943                 
944                 $choices = $this->getChoices($row);
945                 $num_choices = count($choices);
946
947                 $sum = 0;
948                 for ($i=0; $i<$num_choices; $i++) {
949                         $sum += ($i+1) * $answers[$i]['count'];
950                 }
951                 $average = round($sum/$num_results, 1);
952
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);
959         }
960
961         /*public */function mark($row) { 
962                 $_POST['answers'][$row['question_id']] = intval($_POST['answers'][$row['question_id']]);
963                 return 0;
964         }
965
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; 
971
972                 $empty_fields = array();
973                 if ($_POST['question'] == ''){
974                         $empty_fields[] = _AT('question');
975                 }
976                 if ($_POST['choice'][0] == '') {
977                         $empty_fields[] = _AT('choice').' 1';
978                 }
979
980                 if ($_POST['choice'][1] == '') {
981                         $empty_fields[] = _AT('choice').' 2';
982                 }
983
984                 if (!empty($empty_fields)) {
985 //                      $msg->addError(array('EMPTY_FIELDS', implode(', ', $empty_fields)));
986                 }
987
988                 if (!$msg->containsErrors()) {
989                         $_POST['feedback']   = '';
990 //                      $_POST['question']   = $addslashes($_POST['question']);
991
992                         for ($i=0; $i<10; $i++) {
993                                 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
994                                 $_POST['answer'][$i] = intval($_POST['answer'][$i]);
995
996                                 if ($_POST['choice'][$i] == '') {
997                                         /* an empty option can't be correct */
998                                         $_POST['answer'][$i] = 0;
999                                 }
1000                         }
1001
1002                         $sql_params = array(    $_POST['category_id'], 
1003                                                                         $_course_id,
1004                                                                         $_POST['feedback'], 
1005                                                                         $_POST['question'], 
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]);
1026
1027                         $sql = vsprintf(TR_SQL_QUESTION_LIKERT, $sql_params);
1028 //                      $result = mysql_query($sql, $db);
1029 //                      if ($result==true){
1030                         $dao = new DAO();
1031                         if ($dao->execute($sql)) {
1032                                 return mysql_insert_id();
1033                         }
1034                 }
1035         }
1036 }
1037
1038 /**
1039 * longQuestion
1040 *
1041 */
1042 class LongQuestion extends AbstracttestQuestion {
1043         /*protected */ var $sPrefix = 'long';
1044         /*protected */ var $sNameVar = 'test_open';
1045
1046         /*protected */function assignQTIVariables($row) {
1047                 $this->savant->assign('row', $row);
1048         }
1049
1050         /*protected */function assignDisplayResultVariables($row, $answer_row) {
1051                 $this->savant->assign('answer', $answer_row['answer']);
1052                 $this->savant->assign('row', $row);
1053         }
1054
1055         /*protected */function assignDisplayVariables($row, $response) {
1056                 $this->savant->assign('row', $row);
1057                 $this->savant->assign('response', $response);
1058         }
1059
1060         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
1061                 $num_results = 0;               
1062                 foreach ($answers as $answer) {
1063                         $num_results += $answer['count'];
1064                 }
1065                 
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);
1070         }
1071
1072         /*public */function mark($row) { 
1073                 global $addslashes;
1074                 $_POST['answers'][$row['question_id']] = $addslashes($_POST['answers'][$row['question_id']]);
1075                 return NULL;
1076         }
1077
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; 
1083
1084                 if ($_POST['question'] == ''){
1085 //                      $msg->addError(array('EMPTY_FIELDS', _AT('question')));
1086                 }
1087
1088                 if (!$msg->containsErrors()) {
1089 //                      $_POST['feedback'] = $addslashes($_POST['feedback']);
1090 //                      $_POST['question'] = $addslashes($_POST['question']);
1091
1092                         if ($_POST['property']==''){
1093                                 $_POST['property'] = 4; //essay
1094                         }
1095
1096                         $sql_params = array(    $_POST['category_id'], 
1097                                                                         $_course_id,
1098                                                                         $_POST['feedback'], 
1099                                                                         $_POST['question'], 
1100                                                                         $_POST['property']);
1101
1102                         $sql = vsprintf(TR_SQL_QUESTION_LONG, $sql_params);
1103
1104 //                      $result = mysql_query($sql, $db);
1105 //                      if ($result==true){
1106                         $dao = new DAO();
1107                         if ($dao->execute($sql)) {
1108                                 return mysql_insert_id();
1109                         }
1110                 }
1111         }
1112 }
1113
1114 /**
1115 * matchingQuestion
1116 *
1117 */
1118 class MatchingQuestion extends AbstracttestQuestion {
1119         /*protected */ var $sPrefix = 'matching';
1120         /*protected */ var $sNameVar   = 'test_matching';
1121
1122         /*protected */function assignQTIVariables($row) {
1123                 $choices = $this->getChoices($row);
1124                 $num_choices = count($choices);
1125
1126                 $num_options = 0;
1127                 for ($i=0; $i < 10; $i++) {
1128                         if ($row['option_'. $i] != '') {
1129                                 $num_options++;
1130                         }
1131                 }
1132
1133                 $this->savant->assign('num_choices', $num_choices);
1134                 $this->savant->assign('num_options', $num_options);
1135                 $this->savant->assign('row', $row);
1136         }
1137
1138         /*protected */function assignDisplayResultVariables($row, $answer_row) {
1139                 $num_options = 0;
1140                 for ($i=0; $i < 10; $i++) {
1141                         if ($row['option_'. $i] != '') {
1142                                 $num_options++;
1143                         }
1144                 }
1145
1146                 $answer_row['answer'] = explode('|', $answer_row['answer']);
1147
1148                 global $_letters;
1149
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);
1155         }
1156
1157         /*protected */function assignDisplayVariables($row, $response) {
1158                 $choices = $this->getChoices($row);
1159                 $num_choices = count($choices);
1160
1161                 if (empty($response)) {
1162                         $response = array_fill(0, $num_choices, -1);
1163                 } else {
1164                         $response = explode('|', $response);
1165                 }
1166
1167                 $num_options = 0;
1168                 for ($i=0; $i < 10; $i++) {
1169                         if ($row['option_'. $i] != '') {
1170                                 $num_options++;
1171                         }
1172                 }
1173
1174                 global $_letters;
1175
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);
1181
1182                 $this->savant->assign('response', $response);
1183         }
1184
1185         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
1186                 $choices = $this->getChoices($row);
1187                 $num_choices = count($choices);
1188
1189                 $num_results = 0;
1190                 foreach ($answers as $answer) {
1191                         $num_results += $answer['count'];
1192                 }
1193                                         
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']++;
1199                                 }
1200                         }
1201                 }
1202
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);
1207         }
1208
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++;
1215                         }
1216                         $_POST['answers'][$row['question_id']][$item_id] = intval($_POST['answers'][$row['question_id']][$item_id]);
1217                 }
1218
1219                 $score = 0;
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"
1227                         } else {
1228                                 $score = trim($score, '0'); // remove trailing zeros, if any
1229                         }
1230                 }
1231
1232                 $_POST['answers'][$row['question_id']] = implode('|', $_POST['answers'][$row['question_id']]);
1233
1234                 return $score;
1235         }
1236
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; 
1242
1243                 if (!is_array($_POST['answer'])){
1244                         $temp = $_POST['answer'];
1245                         $_POST['answer'] = array();
1246                         $_POST['answer'][0] = $temp;
1247                 }
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);
1251
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]);
1256                 }
1257
1258                 if (!$_POST['groups'][0] 
1259                         || !$_POST['groups'][1] 
1260                         || !$_POST['choice'][0] 
1261                         || !$_POST['choice'][1]) {
1262 //                      $msg->addError('QUESTION_EMPTY');
1263                 }
1264
1265                 if (!$msg->containsErrors()) {
1266 //                      $_POST['feedback']     = $addslashes($_POST['feedback']);
1267 //                      $_POST['instructions'] = $addslashes($_POST['instructions']);
1268                 
1269                         $sql_params = array(    $_POST['category_id'], 
1270                                                                         $_course_id,
1271                                                                         $_POST['feedback'], 
1272                                                                         $_POST['question'], 
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]);
1303
1304                         $sql = vsprintf(TR_SQL_QUESTION_MATCHINGDD, $sql_params);
1305
1306                         $dao = new DAO();
1307                         if ($dao->execute($sql)) {
1308                                 return mysql_insert_id();
1309                         }
1310                 }
1311         }
1312 }
1313
1314 /**
1315 * matchingddQuestion
1316 *
1317 */
1318 class MatchingddQuestion extends MatchingQuestion {
1319         /*protected */ var $sPrefix = 'matchingdd';
1320         /*protected */ var $sNameVar   = 'test_matchingdd';
1321 }
1322
1323 /**
1324 * multichoiceQuestion
1325 *
1326 */
1327 class MultichoiceQuestion extends AbstracttestQuestion {
1328         /*protected */ var $sPrefix = 'multichoice';
1329         /*protected */var $sNameVar = 'test_mc';
1330
1331         /*protected */function assignQTIVariables($row) {
1332                 $choices = $this->getChoices($row);
1333                 $num_choices = count($choices);
1334
1335                 $this->savant->assign('num_choices', $num_choices);
1336                 $this->savant->assign('row', $row);
1337         }
1338
1339         /*protected */function assignDisplayResultVariables($row, $answer_row) {
1340                 if (strpos($answer_row['answer'], '|') !== false) {
1341                         $answer_row['answer'] = explode('|', $answer_row['answer']);
1342                 } else {
1343                         $answer_row['answer'] = array($answer_row['answer']);
1344                 }
1345
1346                 $this->savant->assign('base_href', TR_BASE_HREF);
1347                 $this->savant->assign('answers', $answer_row['answer']);
1348                 $this->savant->assign('row', $row);
1349         }
1350
1351         /*protected */function assignDisplayVariables($row, $response) {
1352                 $choices = $this->getChoices($row);
1353                 $num_choices = count($choices);
1354
1355                 if ($response == '') {
1356                         $response = -1;
1357                 }
1358                 $response = explode('|', $response);
1359                 $this->savant->assign('response', $response);
1360
1361                 $this->savant->assign('num_choices', $num_choices);
1362                 $this->savant->assign('row', $row);
1363         }
1364
1365         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
1366                 $choices = $this->getChoices($row);
1367                 $num_choices = count($choices);
1368
1369                 $num_results = 0;
1370                 foreach ($answers as $answer) {
1371                         $num_results += $answer['count'];
1372                 }
1373                                         
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']++;
1379                                 }
1380                         }
1381                 }
1382
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);
1388         }
1389
1390         /*public */function mark($row) { 
1391                 $score = 0;
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) {
1396                         $has_answer = 0;
1397                         for($i=0; $i<10; $i++) {
1398                                 $has_answer += $row['answer_'.$i];
1399                         }
1400                         if (!$has_answer && $row['weight']) {
1401                                 // If MC has no answer and user answered "leave blank"
1402                                 $score = $row['weight'];
1403                         }
1404                 }
1405                 return $score;
1406         }
1407
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')));
1415                 }
1416                 
1417                 if (!$msg->containsErrors()) {
1418 //                      $_POST['question']   = $addslashes($_POST['question']);
1419
1420                         for ($i=0; $i<10; $i++) {
1421                                 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
1422                         }
1423
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.
1427                         } else {
1428                                 $answers[$_POST['answer']] = 1;
1429                         }
1430                 
1431                         $sql_params = array(    $_POST['category_id'], 
1432                                                                         $_course_id,
1433                                                                         $_POST['feedback'], 
1434                                                                         $_POST['question'], 
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], 
1445                                                                         $answers[0], 
1446                                                                         $answers[1], 
1447                                                                         $answers[2], 
1448                                                                         $answers[3], 
1449                                                                         $answers[4], 
1450                                                                         $answers[5], 
1451                                                                         $answers[6], 
1452                                                                         $answers[7], 
1453                                                                         $answers[8], 
1454                                                                         $answers[9]);
1455
1456                         $sql = vsprintf(TR_SQL_QUESTION_MULTI, $sql_params);
1457 //                      $result = mysql_query($sql, $db);
1458 //                      if ($result==true){
1459                         $dao = new DAO();
1460                         if ($dao->execute($sql)) {
1461                                 return mysql_insert_id();
1462                         }
1463                 }
1464         }
1465 }
1466
1467 /**
1468 * multianswerQuestion
1469 *
1470 */
1471 class MultianswerQuestion extends MultichoiceQuestion {
1472         /*protected */ var $sPrefix  = 'multianswer';
1473         /*protected */ var $sNameVar = 'test_ma';
1474
1475         /*public */function mark($row) { 
1476                 $num_correct = array_sum(array_slice($row, 3));
1477
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]);
1481                         }
1482                         $num_answer_correct = 0;
1483                         foreach ($_POST['answers'][$row['question_id']] as $item_id => $answer) {
1484                                 if ($row['answer_' . $answer]) {
1485                                         // correct answer
1486                                         $num_answer_correct++;
1487                                 } else {
1488                                         // wrong answer
1489                                         $num_answer_correct--;
1490                                 }
1491                                 $_POST['answers'][$row['question_id']][$item_id] = intval($_POST['answers'][$row['question_id']][$item_id]);
1492                         }
1493                         if ($num_answer_correct == $num_correct) {
1494                                 $score = $row['weight'];
1495                         } else {
1496                                 $score = 0;
1497                         }
1498                         $_POST['answers'][$row['question_id']] = implode('|', $_POST['answers'][$row['question_id']]);
1499                 } else {
1500                         // no answer given
1501                         $_POST['answers'][$row['question_id']] = '-1'; // left blank
1502                         $score = 0;
1503                 }
1504                 return $score;
1505         }
1506
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; 
1513
1514                 if ($_POST['question'] == ''){
1515                         $msg->addError(array('EMPTY_FIELDS', _AT('question')));
1516                 }
1517
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'];
1521
1522                 if (!$msg->containsErrors()) {
1523                         $choice_new = array(); // stores the non-blank choices
1524                         $answer_new = array(); // stores the associated "answer" for the choices
1525
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;
1533                                 } else {
1534                                         /* filter out empty choices/ remove gaps */
1535                                         $choice_new[] = $choiceOpt;
1536                                         if (in_array($choiceNum, $_POST['answer'])){
1537                                                 $answer_new[] = 1;
1538                                         } else {
1539                                                 $answer_new[] = 0;
1540                                         }
1541
1542                                         if ($_POST['answer'][$choiceNum] != 0)
1543                                                 $has_answer = TRUE;
1544                                 }
1545                         }
1546
1547                         if ($has_answer != TRUE) {
1548                 
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']);
1553
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]);
1557                                 }
1558
1559                                 $msg->addConfirm('NO_ANSWER', $hidden_vars);
1560                         } else {                        
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, '');
1566                         
1567 //                              $_POST['feedback'] = $addslashes($_POST['feedback']);
1568 //                              $_POST['question'] = $addslashes($_POST['question']);
1569
1570                                 $sql_params = array(    $_POST['category_id'], 
1571                                                                                 $_course_id,
1572                                                                                 $_POST['feedback'], 
1573                                                                                 $_POST['question'], 
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]);
1594
1595                                 $sql = vsprintf(TR_SQL_QUESTION_MULTIANSWER, $sql_params);
1596
1597 //                              $result = mysql_query($sql, $db);
1598 //                              if ($result==true){
1599                                 $dao = new DAO();
1600                                 if ($dao->execute($sql)) {
1601                                         return mysql_insert_id();
1602                                 }
1603                         }
1604                 }
1605         }
1606
1607 }
1608 ?>