6d8a7ad79930dfbb6187bf78da98f377d0bf723b
[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     $savant->assign('num_takes', $row['num_takes']);
371     $savant->assign('use_cc', $use_cc);
372         $xml = $savant->fetch('tests/test_questions/wrapper.tmpl.php');
373
374         $xml_filename = 'tests_'.$tid.'.xml';
375         if (!$use_cc){          
376                 $zipfile->add_file($xml, $xml_filename);
377         } else {
378                 $zipfile->add_file($xml, 'QTI/'.$xml_filename);
379         }
380
381         // add any dependency files:
382         if (!$use_cc){
383                 foreach ($dependencies as $resource => $resource_server_path) {
384                         //add this file in if it's not already in the zip package
385                         if (!in_array($resource_server_path, $test_zipped_files)){
386                                 $zipfile->add_file(@file_get_contents($resource_server_path), 'resources/'.$resource, filemtime($resource_server_path));
387                                 $test_zipped_files[] = $resource_server_path;
388                         }
389                 }
390         }
391
392         if ($zipflag){
393                 // construct the manifest xml
394                 $savant->assign('resources', $resources);
395                 $savant->assign('dependencies', array_keys($dependencies));
396                 $savant->assign('encoding', $course_language_charset);
397                 $savant->assign('title', $test_title);
398                 $savant->assign('xml_filename', $xml_filename);
399                 
400                 $manifest_xml = $savant->fetch('tests/test_questions/manifest_qti_1p2.tmpl.php');
401                 $zipfile->add_file($manifest_xml, 'imsmanifest.xml');
402
403                 $zipfile->close();
404
405                 $filename = str_replace(array(' ', ':'), '_', $course_row['title'].'-'.$test_title.'-'.date('Ymd'));
406                 $zipfile->send_file($filename);
407                 exit;
408         }
409         
410         $return_array[$xml_filename] = array_keys($dependencies);
411         return $return_array;
412         //done
413 }
414
415
416 /* 
417  * Recursively create folders
418  * For the purpose of this webapp only.  All the paths are seperated by a /
419  * And thus this function will loop through each directory and create them on the way
420  * if it doesn't exist.
421  * @author harris
422  */
423 function recursive_mkdir($path, $mode = 0700) {
424     $dirs = explode(DIRECTORY_SEPARATOR , $path);
425     $count = count($dirs);
426     $path = '';
427     for ($i = 0; $i < $count; ++$i) {
428         $path .= $dirs[$i].DIRECTORY_SEPARATOR;
429                 //If the directory has not been created, create it and return error on failure
430         if (!is_dir($path) && !mkdir($path, $mode)) {
431             return false;
432         }
433     }
434     return true;
435 }
436
437
438 /**
439 * keeps count of the question number (when displaying the question)
440 * need this function because PHP 4 doesn't support static members
441 */
442 function TestQuestionCounter($increment = FALSE) {
443         static $count;
444
445         if (!isset($count)) { 
446                 $count = 0;
447         }
448         if ($increment) {
449                 $count++;
450         }
451
452         return $count;
453 }
454
455
456 /**
457  * testQuestion
458  *
459  * Note that all PHP 5 OO declarations and signatures are commented out to be
460  * backwards compatible with PHP 4.
461  *
462  */
463 /*abstract */ class AbstractTestQuestion  {
464         /**
465         * Savant2 $savant - refrence to the savant obj
466         */
467         /*protected */ var $savant;
468
469         /**
470         * Constructor method.  Initialises variables.
471         */
472         function AbstractTestQuestion(&$savant) { $this->savant =& $savant; }
473
474         /**
475         * Public
476         */
477         /*final public */function seed($salt) {
478                 /**
479                 * by controlling the seed before calling array_rand() we insure that
480                 * we can un-randomize the order for marking.
481                 * used with ordering type questions only.
482                 */
483                 srand($salt + $_SESSION['user_id']);
484         }
485
486         /**
487         * Public
488         */
489         /*final public */function unseed() {
490                 // To fix http://www.atutor.ca/atutor/mantis/view.php?id=3167
491                 // Disturb the seed for ordering questions after mark to avoid the deterioration  
492                 // of the random distribution due to a repeated initialization of the same random seed
493                 list($usec, $sec) = explode(" ", microtime());
494                 srand((int)($usec*10));
495         }
496
497         /**
498         * Public
499         * Prints the name of this question
500         */
501         /*final public */function printName() { echo $this->getName(); }
502
503         /**
504         * Public
505         * Prints the name of this question
506         */
507         /*final public */function getName() { return _AT($this->sNameVar); }
508
509         /**
510         * Public
511         * Returns the prefix string (used for file names)
512         */
513         /*final public */function getPrefix() { return $this->sPrefix; }
514
515         /**
516         * Display the current question (for taking or previewing a test/question)
517         */
518         /*final public */function display($row, $response = '') {
519                 // print the generic question header
520                 require_once(TR_INCLUDE_PATH.'../home/classes/ContentUtility.class.php');
521                 $this->displayHeader($row['weight']);
522
523                 // print the question specific template
524                 $row['question'] = ContentUtility::formatContent($row['question'], 1);
525                 $this->assignDisplayVariables($row, $response);
526                 $this->savant->display('tests/test_questions/' . $this->sPrefix . '.tmpl.php');
527                 
528                 // print the generic question footer
529                 $this->displayFooter();
530         }
531
532         /**
533         * Display the result for the current question
534         */
535         /*final public */function displayResult($row, $answer_row, $editable = FALSE) {
536                 // print the generic question header
537                 $this->displayHeader($row['weight'], $answer_row['score'], $editable ? $row['question_id'] : FALSE);
538
539                 // print the question specific template
540                 $this->assignDisplayResultVariables($row, $answer_row);
541                 $this->savant->display('tests/test_questions/' . $this->sPrefix . '_result.tmpl.php');
542                 
543                 // print the generic question footer
544                 $this->displayFooter();
545         }
546
547
548         /**
549         * print the question template header
550         */
551         /*final public */function displayResultStatistics($row, $answers) {
552                 TestQuestionCounter(TRUE);
553                 $this->assignDisplayStatisticsVariables($row, $answers);
554                 $this->savant->display('tests/test_questions/' . $this->sPrefix . '_stats.tmpl.php');
555         }
556
557         /*final public */function exportQTI($row, $encoding, $version) {
558                 $this->savant->assign('encoding', $encoding);
559         $this->savant->assign('weight', $row['weight']);
560                 //Convert all row values to html entities
561                 foreach ($row as $k=>$v){
562                         $row[$k] = htmlspecialchars($v, ENT_QUOTES, 'UTF-8');   //not using htmlentities cause it changes some languages falsely.
563                 }
564                 $this->assignQTIVariables($row);
565                 if ($version=='2.1') {
566                         $xml = $this->savant->fetch('tests/test_questions/'. $this->sPrefix . '_qti_2p1.tmpl.php');
567                 } else {        
568                         $xml = $this->savant->fetch('tests/test_questions/'. $this->sPrefix . '_qti_1p2.tmpl.php');
569                 }
570                 return $xml;
571         }
572
573         /**
574         * print the question template header
575         */
576         /*final private */function displayHeader($weight, $score = FALSE, $question_id = FALSE) {
577                 TestQuestionCounter(TRUE);
578                 
579                 if ($score) $score = intval($score);
580                 $this->savant->assign('question_id', $question_id);
581                 $this->savant->assign('score', $score);
582                 $this->savant->assign('weight', $weight);
583                 $this->savant->assign('type',   _AT($this->sNameVar));
584                 $this->savant->assign('number', TestQuestionCounter());
585                 $this->savant->display('tests/test_questions/header.tmpl.php');
586         }
587
588         /**
589         * print the question template footer
590         */
591         /*final private */function displayFooter() {
592                 $this->savant->display('tests/test_questions/footer.tmpl.php');
593         }
594
595         /**
596         * return only the non-empty choices from $row.
597         * assumes choices are sequential.
598         */
599         /*protected */function getChoices($row) {
600                 $choices = array();
601                 for ($i=0; $i < 10; $i++) {
602                         if ($row['choice_'.$i] != '') {
603                                 $num_choices++;
604                                 $choices[] = $row['choice_'.$i];
605                         } else {
606                                 break;
607                         }
608                 }
609                 return $choices;
610         }
611 }
612
613 /**
614 * orderingQuestion
615 *
616 */
617 class OrderingQuestion extends AbstractTestQuestion {
618         /*protected */ var $sNameVar = 'test_ordering';
619         /*protected */ var $sPrefix = 'ordering';
620         
621         /*protected */function assignDisplayResultVariables($row, $answer_row) {
622                 $answers = explode('|', $answer_row['answer']);
623
624                 $num_choices = count($this->getChoices($row));
625
626                 $this->savant->assign('base_href', TR_BASE_HREF);
627                 $this->savant->assign('num_choices', $num_choices);
628                 $this->savant->assign('answers', $answers);
629                 $this->savant->assign('row', $row);
630         }
631
632         /*protected */function assignQTIVariables($row) {
633                 $choices = $this->getChoices($row);
634                 $num_choices = count($choices);
635
636                 $this->savant->assign('num_choices', $num_choices);
637                 $this->savant->assign('row', $row);
638         }
639
640         /*protected */function assignDisplayVariables($row, $response) {
641                 // determine the number of choices this question has
642                 // and save those choices to be re-assigned back to $row
643                 // in the randomized order.
644                 $choices = $this->getChoices($row);
645                 $num_choices = count($choices);
646
647                 // response from the test_answers table is in the correct order
648                 // so, they have to be re-randomized in the same order as the
649                 // choices are. this is only possible because of the seed() method.
650                 $response = explode('|', $response);
651                 $new_response = array();
652
653                 // randomize the order of choices and re-assign to $row
654                 $this->seed($row['question_id']);
655                 $rand = array_rand($choices, $num_choices);
656                 for ($i=0; $i < 10; $i++) {
657                         $row['choice_'.$i] = $choices[$rand[$i]];
658                         $new_response[$i]  = $response[$rand[$i]];
659                 }
660
661                 $this->savant->assign('num_choices', $num_choices);
662                 $this->savant->assign('row', $row);
663
664                 $this->savant->assign('response', $new_response);
665         }
666
667         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
668                 $num_results = 0;               
669                 foreach ($answers as $answer) {
670                         $num_results += $answer['count'];
671                 }
672
673                 $choices = $this->getChoices($row);
674                 $num_choices = count($choices);
675
676                 $final_answers = array(); // assoc array of # of times that key was used correctly 0, 1, ...  $num -1
677                 foreach ($answers as $key => $value) {
678                         $values = explode('|', $key);
679                         // we assume $values is never empty and contains $num number of answers
680                         for ($i=0; $i<=$num_choices; $i++) {
681                                 if ($values[$i] == $i) {
682                                         $final_answers[$i] += $answers[$key]['count'];
683                                 }
684                         }
685                 }
686
687                 $this->savant->assign('num_results', $num_results);
688                 $this->savant->assign('num_choices', $num_choices);
689                 $this->savant->assign('answers', $final_answers);
690                 $this->savant->assign('row', $row);
691         }
692
693         /*public */function mark($row) { 
694                 $this->seed($row['question_id']);
695                 $num_choices = count($_POST['answers'][$row['question_id']]);
696                 $answers = range(0, $num_choices-1);
697                 $answers = array_rand($answers, $num_choices);
698                 
699                 // Disturb the seed for ordering questions after mark to avoid the deterioration  
700                 // of the random distribution due to a repeated initialization of the same random seed
701                 $this->unseed();
702
703                 $num_answer_correct = 0;
704
705                 $ordered_answers = array();
706
707                 for ($i = 0; $i < $num_choices ; $i++) {
708                         $_POST['answers'][$row['question_id']][$i] = intval($_POST['answers'][$row['question_id']][$i]);
709
710                         if ($_POST['answers'][$row['question_id']][$i] == -1) {
711                                 // nothing to do. it was left blank
712                         } else if ($_POST['answers'][$row['question_id']][$i] == $answers[$i]) {
713                                 $num_answer_correct++;
714                         }
715                         $ordered_answers[$answers[$i]] = $_POST['answers'][$row['question_id']][$i];
716                 }
717                 ksort($ordered_answers);
718
719                 $score = 0;
720
721                 // to avoid roundoff errors:
722                 if ($num_answer_correct == $num_choices) {
723                         $score = $row['weight'];
724                 } else if ($num_answer_correct > 0) {
725                         $score = number_format($row['weight'] / $num_choices * $num_answer_correct, 2);
726                         if ( (float) (int) $score == $score) {
727                                 $score = (int) $score; // a whole number with decimals, eg. "2.00"
728                         } else {
729                                 $score = trim($score, '0'); // remove trailing zeros, if any, eg. "2.50"
730                         }
731                 }
732
733                 $_POST['answers'][$row['question_id']] = implode('|', $ordered_answers);
734
735                 return $score;
736         }
737
738         //QTI Import Ordering Question
739         function importQTI($_POST){
740                 global $_course_id;
741                 
742                 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
743                 require_once(TR_INCLUDE_PATH.'classes/Utility.class.php');
744                 global $msg, $db;
745                 
746                 if ($_POST['question'] == ''){
747                         $missing_fields[] = _AT('question');
748                 }
749
750                 if (trim($_POST['choice'][0]) == '') {
751                         $missing_fields[] = _AT('item').' 1';
752                 }
753                 if (trim($_POST['choice'][1]) == '') {
754                         $missing_fields[] = _AT('item').' 2';
755                 }
756
757                 if ($missing_fields) {
758                         $missing_fields = implode(', ', $missing_fields);
759                         $msg->addError(array('EMPTY_FIELDS', $missing_fields));
760                 }
761
762                 if (!$msg->containsErrors()) {
763                         $choice_new = array(); // stores the non-blank choices
764                         $answer_new = array(); // stores the non-blank answers
765                         $order = 0; // order count
766                         for ($i=0; $i<10; $i++) {
767                                 /**
768                                  * Db defined it to be 255 length, chop strings off it it's less than that
769                                  * @harris
770                                  */
771                                 $_POST['choice'][$i] = Utility::validateLength($_POST['choice'][$i], 255);
772                                 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
773
774                                 if ($_POST['choice'][$i] != '') {
775                                         /* filter out empty choices/ remove gaps */
776                                         $choice_new[] = $_POST['choice'][$i];
777                                         $answer_new[] = $order++;
778                                 }
779                         }
780
781                         $_POST['choice']   = array_pad($choice_new, 10, '');
782                         $answer_new        = array_pad($answer_new, 10, 0);
783 //                      $_POST['feedback'] = $addslashes($_POST['feedback']);
784 //                      $_POST['question'] = $addslashes($_POST['question']);
785                 
786                         $sql_params = array(    $_POST['category_id'], 
787                                                                         $_course_id,
788                                                                         $_POST['feedback'], 
789                                                                         $_POST['question'], 
790                                                                         $_POST['choice'][0], 
791                                                                         $_POST['choice'][1], 
792                                                                         $_POST['choice'][2], 
793                                                                         $_POST['choice'][3], 
794                                                                         $_POST['choice'][4], 
795                                                                         $_POST['choice'][5], 
796                                                                         $_POST['choice'][6], 
797                                                                         $_POST['choice'][7], 
798                                                                         $_POST['choice'][8], 
799                                                                         $_POST['choice'][9], 
800                                                                         $answer_new[0], 
801                                                                         $answer_new[1], 
802                                                                         $answer_new[2], 
803                                                                         $answer_new[3], 
804                                                                         $answer_new[4], 
805                                                                         $answer_new[5], 
806                                                                         $answer_new[6], 
807                                                                         $answer_new[7], 
808                                                                         $answer_new[8], 
809                                                                         $answer_new[9]);
810
811                         $sql = vsprintf(TR_SQL_QUESTION_ORDERING, $sql_params);
812
813 //                      $result = mysql_query($sql, $db);
814 //                      if ($result==true){
815                         $dao = new DAO();
816                         if ($dao->execute($sql)) {
817                                 return mysql_insert_id();
818                         }                       
819                 }
820         }
821 }
822
823 /**
824 * truefalseQuestion
825 *
826 */
827 class TruefalseQuestion extends AbstracttestQuestion {
828         /*protected */ var $sPrefix = 'truefalse';
829         /*protected */ var $sNameVar   = 'test_tf';
830
831         /*protected */function assignQTIVariables($row) {
832                 $this->savant->assign('row', $row);
833         }
834
835         /*protected */function assignDisplayResultVariables($row, $answer_row) {
836
837                 $this->savant->assign('base_href', TR_BASE_HREF);
838                 $this->savant->assign('answers', $answer_row['answer']);
839                 $this->savant->assign('row', $row);
840         }
841
842         /*protected */function assignDisplayVariables($row, $response) {
843                 $this->savant->assign('row', $row);
844                 $this->savant->assign('response', $response);
845         }
846
847         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
848                 $num_results = 0;               
849                 foreach ($answers as $answer) {
850                         $num_results += $answer['count'];
851                 }
852
853                 $this->savant->assign('num_results', $num_results);
854                 $this->savant->assign('num_blanks', (int) $answers['-1']['count']);
855                 $this->savant->assign('num_true', (int) $answers['1']['count']);
856                 $this->savant->assign('num_false', (int) $answers['2']['count']);
857                 $this->savant->assign('row', $row);
858         }
859
860         /*public */function mark($row) { 
861                 $_POST['answers'][$row['question_id']] = intval($_POST['answers'][$row['question_id']]);
862
863                 if ($row['answer_0'] == $_POST['answers'][$row['question_id']]) {
864                         return (int) $row['weight'];
865                 } // else:
866                 return 0;
867         }
868
869         //QTI Import True/False Question
870         function importQTI($_POST){
871                 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
872                 global $msg, $db, $_course_id;
873
874                 if ($_POST['question'] == ''){
875                         $msg->addError(array('EMPTY_FIELDS', _AT('statement')));
876                 }
877
878                 //assign true answer to 1, false answer to 2, idk to 3, for ATutor
879                 if  ($_POST['answer'] == 'ChoiceT'){
880                         $_POST['answer'] = 1;
881                 } else {
882                         $_POST['answer'] = 2;   
883                 }
884
885                 if (!$msg->containsErrors()) {
886 //                      $_POST['feedback'] = $addslashes($_POST['feedback']);
887 //                      $_POST['question'] = $addslashes($_POST['question']);
888
889
890                         $sql_params = array(    $_POST['category_id'], 
891                                                                         $_course_id,
892                                                                         $_POST['feedback'], 
893                                                                         $_POST['question'], 
894                                                                         $_POST['answer']);
895
896                         $sql = vsprintf(TR_SQL_QUESTION_TRUEFALSE, $sql_params);
897 //                      $result = mysql_query($sql, $db);
898 //                      if ($result==true){
899                         $dao = new DAO();
900                         if ($dao->execute($sql)) {      
901                                 return mysql_insert_id();
902                         }
903                 }
904         }
905 }
906
907 /**
908 * likertQuestion
909 *
910 */
911 class LikertQuestion extends AbstracttestQuestion {
912         /*protected */ var $sPrefix = 'likert';
913         /*protected */ var $sNameVar   = 'test_lk';
914
915         /*protected */function assignQTIVariables($row) {
916                 $choices = $this->getChoices($row);
917                 $num_choices = count($choices);
918
919                 $this->savant->assign('num_choices', $num_choices);
920                 $this->savant->assign('row', $row);
921         }
922
923         /*protected */function assignDisplayResultVariables($row, $answer_row) {
924                 $this->savant->assign('answer', $answer_row['answer']);
925                 $this->savant->assign('row', $row);
926         }
927
928         /*protected */function assignDisplayVariables($row, $response) {
929                 $choices = $this->getChoices($row);
930                 $num_choices = count($choices);
931
932                 $this->savant->assign('num_choices', $num_choices);
933                 $this->savant->assign('row', $row);
934
935                 if (empty($response)) {
936                         $response = -1;
937                 }
938                 $this->savant->assign('response', $response);
939         }
940
941         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
942                 $num_results = 0;               
943                 foreach ($answers as $answer) {
944                         $num_results += $answer['count'];
945                 }
946                 
947                 $choices = $this->getChoices($row);
948                 $num_choices = count($choices);
949
950                 $sum = 0;
951                 for ($i=0; $i<$num_choices; $i++) {
952                         $sum += ($i+1) * $answers[$i]['count'];
953                 }
954                 $average = round($sum/$num_results, 1);
955
956                 $this->savant->assign('num_results', $num_results);
957                 $this->savant->assign('average', $average);
958                 $this->savant->assign('num_choices', $num_choices);
959                 $this->savant->assign('num_blanks', (int) $answers['-1']['count']);
960                 $this->savant->assign('answers', $answers);
961                 $this->savant->assign('row', $row);
962         }
963
964         /*public */function mark($row) { 
965                 $_POST['answers'][$row['question_id']] = intval($_POST['answers'][$row['question_id']]);
966                 return 0;
967         }
968
969         //QTI Import Likert Question
970         function importQTI($_POST){
971                 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
972                 global $msg, $db, $_course_id;
973 //              $_POST = $this->_POST; 
974
975                 $empty_fields = array();
976                 if ($_POST['question'] == ''){
977                         $empty_fields[] = _AT('question');
978                 }
979                 if ($_POST['choice'][0] == '') {
980                         $empty_fields[] = _AT('choice').' 1';
981                 }
982
983                 if ($_POST['choice'][1] == '') {
984                         $empty_fields[] = _AT('choice').' 2';
985                 }
986
987                 if (!empty($empty_fields)) {
988 //                      $msg->addError(array('EMPTY_FIELDS', implode(', ', $empty_fields)));
989                 }
990
991                 if (!$msg->containsErrors()) {
992                         $_POST['feedback']   = '';
993 //                      $_POST['question']   = $addslashes($_POST['question']);
994
995                         for ($i=0; $i<10; $i++) {
996                                 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
997                                 $_POST['answer'][$i] = intval($_POST['answer'][$i]);
998
999                                 if ($_POST['choice'][$i] == '') {
1000                                         /* an empty option can't be correct */
1001                                         $_POST['answer'][$i] = 0;
1002                                 }
1003                         }
1004
1005                         $sql_params = array(    $_POST['category_id'], 
1006                                                                         $_course_id,
1007                                                                         $_POST['feedback'], 
1008                                                                         $_POST['question'], 
1009                                                                         $_POST['choice'][0], 
1010                                                                         $_POST['choice'][1], 
1011                                                                         $_POST['choice'][2], 
1012                                                                         $_POST['choice'][3], 
1013                                                                         $_POST['choice'][4], 
1014                                                                         $_POST['choice'][5], 
1015                                                                         $_POST['choice'][6], 
1016                                                                         $_POST['choice'][7], 
1017                                                                         $_POST['choice'][8], 
1018                                                                         $_POST['choice'][9], 
1019                                                                         $_POST['answer'][0], 
1020                                                                         $_POST['answer'][1], 
1021                                                                         $_POST['answer'][2], 
1022                                                                         $_POST['answer'][3], 
1023                                                                         $_POST['answer'][4], 
1024                                                                         $_POST['answer'][5], 
1025                                                                         $_POST['answer'][6], 
1026                                                                         $_POST['answer'][7], 
1027                                                                         $_POST['answer'][8], 
1028                                                                         $_POST['answer'][9]);
1029
1030                         $sql = vsprintf(TR_SQL_QUESTION_LIKERT, $sql_params);
1031 //                      $result = mysql_query($sql, $db);
1032 //                      if ($result==true){
1033                         $dao = new DAO();
1034                         if ($dao->execute($sql)) {
1035                                 return mysql_insert_id();
1036                         }
1037                 }
1038         }
1039 }
1040
1041 /**
1042 * longQuestion
1043 *
1044 */
1045 class LongQuestion extends AbstracttestQuestion {
1046         /*protected */ var $sPrefix = 'long';
1047         /*protected */ var $sNameVar = 'test_open';
1048
1049         /*protected */function assignQTIVariables($row) {
1050                 $this->savant->assign('row', $row);
1051         }
1052
1053         /*protected */function assignDisplayResultVariables($row, $answer_row) {
1054                 $this->savant->assign('answer', $answer_row['answer']);
1055                 $this->savant->assign('row', $row);
1056         }
1057
1058         /*protected */function assignDisplayVariables($row, $response) {
1059                 $this->savant->assign('row', $row);
1060                 $this->savant->assign('response', $response);
1061         }
1062
1063         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
1064                 $num_results = 0;               
1065                 foreach ($answers as $answer) {
1066                         $num_results += $answer['count'];
1067                 }
1068                 
1069                 $this->savant->assign('num_results', $num_results);
1070                 $this->savant->assign('num_blanks', (int) $answers['']['count']);
1071                 $this->savant->assign('answers', $answers);
1072                 $this->savant->assign('row', $row);
1073         }
1074
1075         /*public */function mark($row) { 
1076                 global $addslashes;
1077                 $_POST['answers'][$row['question_id']] = $addslashes($_POST['answers'][$row['question_id']]);
1078                 return NULL;
1079         }
1080
1081         //QTI Import Open end/long Question
1082         function importQTI($_POST){
1083                 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
1084                 global $msg, $db, $_course_id;
1085 //              $_POST = $this->_POST; 
1086
1087                 if ($_POST['question'] == ''){
1088 //                      $msg->addError(array('EMPTY_FIELDS', _AT('question')));
1089                 }
1090
1091                 if (!$msg->containsErrors()) {
1092 //                      $_POST['feedback'] = $addslashes($_POST['feedback']);
1093 //                      $_POST['question'] = $addslashes($_POST['question']);
1094
1095                         if ($_POST['property']==''){
1096                                 $_POST['property'] = 4; //essay
1097                         }
1098
1099                         $sql_params = array(    $_POST['category_id'], 
1100                                                                         $_course_id,
1101                                                                         $_POST['feedback'], 
1102                                                                         $_POST['question'], 
1103                                                                         $_POST['property']);
1104
1105                         $sql = vsprintf(TR_SQL_QUESTION_LONG, $sql_params);
1106
1107 //                      $result = mysql_query($sql, $db);
1108 //                      if ($result==true){
1109                         $dao = new DAO();
1110                         if ($dao->execute($sql)) {
1111                                 return mysql_insert_id();
1112                         }
1113                 }
1114         }
1115 }
1116
1117 /**
1118 * matchingQuestion
1119 *
1120 */
1121 class MatchingQuestion extends AbstracttestQuestion {
1122         /*protected */ var $sPrefix = 'matching';
1123         /*protected */ var $sNameVar   = 'test_matching';
1124
1125         /*protected */function assignQTIVariables($row) {
1126                 $choices = $this->getChoices($row);
1127                 $num_choices = count($choices);
1128
1129                 $num_options = 0;
1130                 for ($i=0; $i < 10; $i++) {
1131                         if ($row['option_'. $i] != '') {
1132                                 $num_options++;
1133                         }
1134                 }
1135
1136                 $this->savant->assign('num_choices', $num_choices);
1137                 $this->savant->assign('num_options', $num_options);
1138                 $this->savant->assign('row', $row);
1139         }
1140
1141         /*protected */function assignDisplayResultVariables($row, $answer_row) {
1142                 $num_options = 0;
1143                 for ($i=0; $i < 10; $i++) {
1144                         if ($row['option_'. $i] != '') {
1145                                 $num_options++;
1146                         }
1147                 }
1148
1149                 $answer_row['answer'] = explode('|', $answer_row['answer']);
1150
1151                 global $_letters;
1152
1153                 $this->savant->assign('base_href', TR_BASE_HREF);
1154                 $this->savant->assign('answers', $answer_row['answer']);
1155                 $this->savant->assign('letters', $_letters);
1156                 $this->savant->assign('num_options', $num_options);
1157                 $this->savant->assign('row', $row);
1158         }
1159
1160         /*protected */function assignDisplayVariables($row, $response) {
1161                 $choices = $this->getChoices($row);
1162                 $num_choices = count($choices);
1163
1164                 if (empty($response)) {
1165                         $response = array_fill(0, $num_choices, -1);
1166                 } else {
1167                         $response = explode('|', $response);
1168                 }
1169
1170                 $num_options = 0;
1171                 for ($i=0; $i < 10; $i++) {
1172                         if ($row['option_'. $i] != '') {
1173                                 $num_options++;
1174                         }
1175                 }
1176
1177                 global $_letters;
1178
1179                 $this->savant->assign('num_choices', $num_choices);
1180                 $this->savant->assign('base_href', TR_BASE_HREF);
1181                 $this->savant->assign('letters', $_letters);
1182                 $this->savant->assign('num_options', $num_options);
1183                 $this->savant->assign('row', $row);
1184
1185                 $this->savant->assign('response', $response);
1186         }
1187
1188         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
1189                 $choices = $this->getChoices($row);
1190                 $num_choices = count($choices);
1191
1192                 $num_results = 0;
1193                 foreach ($answers as $answer) {
1194                         $num_results += $answer['count'];
1195                 }
1196                                         
1197                 foreach ($answers as $key => $value) {
1198                         $values = explode('|', $key);
1199                         if (count($values) > 1) {
1200                                 for ($i=0; $i<count($values); $i++) {
1201                                         $answers[$values[$i]]['count']++;
1202                                 }
1203                         }
1204                 }
1205
1206                 $this->savant->assign('num_choices', $num_choices);
1207                 $this->savant->assign('num_results', $num_results);
1208                 $this->savant->assign('answers', $answers);
1209                 $this->savant->assign('row', $row);
1210         }
1211
1212         /*public */function mark($row) { 
1213                 $num_choices = count($_POST['answers'][$row['question_id']]);
1214                 $num_answer_correct = 0;
1215                 foreach ($_POST['answers'][$row['question_id']] as $item_id => $response) {
1216                         if ($row['answer_' . $item_id] == $response) {
1217                                 $num_answer_correct++;
1218                         }
1219                         $_POST['answers'][$row['question_id']][$item_id] = intval($_POST['answers'][$row['question_id']][$item_id]);
1220                 }
1221
1222                 $score = 0;
1223                 // to avoid roundoff errors:
1224                 if ($num_answer_correct == $num_choices) {
1225                         $score = $row['weight'];
1226                 } else if ($num_answer_correct > 0) {
1227                         $score = number_format($row['weight'] / $num_choices * $num_answer_correct, 2);
1228                         if ( (float) (int) $score == $score) {
1229                                 $score = (int) $score; // a whole number with decimals, eg. "2.00"
1230                         } else {
1231                                 $score = trim($score, '0'); // remove trailing zeros, if any
1232                         }
1233                 }
1234
1235                 $_POST['answers'][$row['question_id']] = implode('|', $_POST['answers'][$row['question_id']]);
1236
1237                 return $score;
1238         }
1239
1240         //QTI Import Matching Question
1241         function importQTI($_POST){
1242                 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
1243                 global $msg, $db, $_course_id;
1244 //              $_POST = $this->_POST; 
1245
1246                 if (!is_array($_POST['answer'])){
1247                         $temp = $_POST['answer'];
1248                         $_POST['answer'] = array();
1249                         $_POST['answer'][0] = $temp;
1250                 }
1251                 ksort($_POST['answer']);        //array_pad returns an array disregard of the array keys
1252                 //default for matching is '-'
1253                 $_POST['answer']= array_pad($_POST['answer'], 10, -1);
1254
1255                 for ($i = 0 ; $i < 10; $i++) {
1256                         $_POST['groups'][$i]        = trim($_POST['groups'][$i]);
1257                         $_POST['answer'][$i] = (int) $_POST['answer'][$i];
1258                         $_POST['choice'][$i]          = trim($_POST['choice'][$i]);
1259                 }
1260
1261                 if (!$_POST['groups'][0] 
1262                         || !$_POST['groups'][1] 
1263                         || !$_POST['choice'][0] 
1264                         || !$_POST['choice'][1]) {
1265 //                      $msg->addError('QUESTION_EMPTY');
1266                 }
1267
1268                 if (!$msg->containsErrors()) {
1269 //                      $_POST['feedback']     = $addslashes($_POST['feedback']);
1270 //                      $_POST['instructions'] = $addslashes($_POST['instructions']);
1271                 
1272                         $sql_params = array(    $_POST['category_id'], 
1273                                                                         $_course_id,
1274                                                                         $_POST['feedback'], 
1275                                                                         $_POST['question'], 
1276                                                                         $_POST['groups'][0], 
1277                                                                         $_POST['groups'][1], 
1278                                                                         $_POST['groups'][2], 
1279                                                                         $_POST['groups'][3], 
1280                                                                         $_POST['groups'][4], 
1281                                                                         $_POST['groups'][5], 
1282                                                                         $_POST['groups'][6], 
1283                                                                         $_POST['groups'][7], 
1284                                                                         $_POST['groups'][8], 
1285                                                                         $_POST['groups'][9], 
1286                                                                         $_POST['answer'][0], 
1287                                                                         $_POST['answer'][1], 
1288                                                                         $_POST['answer'][2], 
1289                                                                         $_POST['answer'][3], 
1290                                                                         $_POST['answer'][4], 
1291                                                                         $_POST['answer'][5], 
1292                                                                         $_POST['answer'][6], 
1293                                                                         $_POST['answer'][7], 
1294                                                                         $_POST['answer'][8], 
1295                                                                         $_POST['answer'][9],
1296                                                                         $_POST['choice'][0], 
1297                                                                         $_POST['choice'][1], 
1298                                                                         $_POST['choice'][2], 
1299                                                                         $_POST['choice'][3], 
1300                                                                         $_POST['choice'][4], 
1301                                                                         $_POST['choice'][5], 
1302                                                                         $_POST['choice'][6], 
1303                                                                         $_POST['choice'][7], 
1304                                                                         $_POST['choice'][8], 
1305                                                                         $_POST['choice'][9]);
1306
1307                         $sql = vsprintf(TR_SQL_QUESTION_MATCHINGDD, $sql_params);
1308
1309                         $dao = new DAO();
1310                         if ($dao->execute($sql)) {
1311                                 return mysql_insert_id();
1312                         }
1313                 }
1314         }
1315 }
1316
1317 /**
1318 * matchingddQuestion
1319 *
1320 */
1321 class MatchingddQuestion extends MatchingQuestion {
1322         /*protected */ var $sPrefix = 'matchingdd';
1323         /*protected */ var $sNameVar   = 'test_matchingdd';
1324 }
1325
1326 /**
1327 * multichoiceQuestion
1328 *
1329 */
1330 class MultichoiceQuestion extends AbstracttestQuestion {
1331         /*protected */ var $sPrefix = 'multichoice';
1332         /*protected */var $sNameVar = 'test_mc';
1333
1334         /*protected */function assignQTIVariables($row) {
1335                 $choices = $this->getChoices($row);
1336                 $num_choices = count($choices);
1337
1338                 $this->savant->assign('num_choices', $num_choices);
1339                 $this->savant->assign('row', $row);
1340         }
1341
1342         /*protected */function assignDisplayResultVariables($row, $answer_row) {
1343                 if (strpos($answer_row['answer'], '|') !== false) {
1344                         $answer_row['answer'] = explode('|', $answer_row['answer']);
1345                 } else {
1346                         $answer_row['answer'] = array($answer_row['answer']);
1347                 }
1348
1349                 $this->savant->assign('base_href', TR_BASE_HREF);
1350                 $this->savant->assign('answers', $answer_row['answer']);
1351                 $this->savant->assign('row', $row);
1352         }
1353
1354         /*protected */function assignDisplayVariables($row, $response) {
1355                 $choices = $this->getChoices($row);
1356                 $num_choices = count($choices);
1357
1358                 if ($response == '') {
1359                         $response = -1;
1360                 }
1361                 $response = explode('|', $response);
1362                 $this->savant->assign('response', $response);
1363
1364                 $this->savant->assign('num_choices', $num_choices);
1365                 $this->savant->assign('row', $row);
1366         }
1367
1368         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
1369                 $choices = $this->getChoices($row);
1370                 $num_choices = count($choices);
1371
1372                 $num_results = 0;
1373                 foreach ($answers as $answer) {
1374                         $num_results += $answer['count'];
1375                 }
1376                                         
1377                 foreach ($answers as $key => $value) {
1378                         $values = explode('|', $key);
1379                         if (count($values) > 1) {
1380                                 for ($i=0; $i<count($values); $i++) {
1381                                         $answers[$values[$i]]['count']++;
1382                                 }
1383                         }
1384                 }
1385
1386                 $this->savant->assign('num_choices', $num_choices);
1387                 $this->savant->assign('num_results', $num_results);
1388                 $this->savant->assign('num_blanks', (int) $answers['-1']['count']);
1389                 $this->savant->assign('answers', $answers);
1390                 $this->savant->assign('row', $row);
1391         }
1392
1393         /*public */function mark($row) { 
1394                 $score = 0;
1395                 $_POST['answers'][$row['question_id']] = intval($_POST['answers'][$row['question_id']]);
1396                 if ($row['answer_' . $_POST['answers'][$row['question_id']]]) {
1397                         $score = $row['weight'];
1398                 } else if ($_POST['answers'][$row['question_id']] == -1) {
1399                         $has_answer = 0;
1400                         for($i=0; $i<10; $i++) {
1401                                 $has_answer += $row['answer_'.$i];
1402                         }
1403                         if (!$has_answer && $row['weight']) {
1404                                 // If MC has no answer and user answered "leave blank"
1405                                 $score = $row['weight'];
1406                         }
1407                 }
1408                 return $score;
1409         }
1410
1411         //QTI Import Multiple Choice Question
1412         function importQTI($_POST){
1413                 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
1414                 global $msg, $db, $_course_id;
1415 //              $_POST = $this->_POST; 
1416                 if ($_POST['question'] == ''){
1417                         $msg->addError(array('EMPTY_FIELDS', _AT('question')));
1418                 }
1419                 
1420                 if (!$msg->containsErrors()) {
1421 //                      $_POST['question']   = $addslashes($_POST['question']);
1422
1423                         for ($i=0; $i<10; $i++) {
1424                                 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
1425                         }
1426
1427                         $answers = array_fill(0, 10, 0);
1428                         if (is_array($_POST['answer'])){
1429                                 $answers[0] = 1;        //default the first to be the right answer. TODO, use summation of points.
1430                         } else {
1431                                 $answers[$_POST['answer']] = 1;
1432                         }
1433                 
1434                         $sql_params = array(    $_POST['category_id'], 
1435                                                                         $_course_id,
1436                                                                         $_POST['feedback'], 
1437                                                                         $_POST['question'], 
1438                                                                         $_POST['choice'][0], 
1439                                                                         $_POST['choice'][1], 
1440                                                                         $_POST['choice'][2], 
1441                                                                         $_POST['choice'][3], 
1442                                                                         $_POST['choice'][4], 
1443                                                                         $_POST['choice'][5], 
1444                                                                         $_POST['choice'][6], 
1445                                                                         $_POST['choice'][7], 
1446                                                                         $_POST['choice'][8], 
1447                                                                         $_POST['choice'][9], 
1448                                                                         $answers[0], 
1449                                                                         $answers[1], 
1450                                                                         $answers[2], 
1451                                                                         $answers[3], 
1452                                                                         $answers[4], 
1453                                                                         $answers[5], 
1454                                                                         $answers[6], 
1455                                                                         $answers[7], 
1456                                                                         $answers[8], 
1457                                                                         $answers[9]);
1458
1459                         $sql = vsprintf(TR_SQL_QUESTION_MULTI, $sql_params);
1460 //                      $result = mysql_query($sql, $db);
1461 //                      if ($result==true){
1462                         $dao = new DAO();
1463                         if ($dao->execute($sql)) {
1464                                 return mysql_insert_id();
1465                         }
1466                 }
1467         }
1468 }
1469
1470 /**
1471 * multianswerQuestion
1472 *
1473 */
1474 class MultianswerQuestion extends MultichoiceQuestion {
1475         /*protected */ var $sPrefix  = 'multianswer';
1476         /*protected */ var $sNameVar = 'test_ma';
1477
1478         /*public */function mark($row) { 
1479                 $num_correct = array_sum(array_slice($row, 3));
1480
1481                 if (is_array($_POST['answers'][$row['question_id']]) && count($_POST['answers'][$row['question_id']]) > 1) {
1482                         if (($i = array_search('-1', $_POST['answers'][$row['question_id']])) !== FALSE) {
1483                                 unset($_POST['answers'][$row['question_id']][$i]);
1484                         }
1485                         $num_answer_correct = 0;
1486                         foreach ($_POST['answers'][$row['question_id']] as $item_id => $answer) {
1487                                 if ($row['answer_' . $answer]) {
1488                                         // correct answer
1489                                         $num_answer_correct++;
1490                                 } else {
1491                                         // wrong answer
1492                                         $num_answer_correct--;
1493                                 }
1494                                 $_POST['answers'][$row['question_id']][$item_id] = intval($_POST['answers'][$row['question_id']][$item_id]);
1495                         }
1496                         if ($num_answer_correct == $num_correct) {
1497                                 $score = $row['weight'];
1498                         } else {
1499                                 $score = 0;
1500                         }
1501                         $_POST['answers'][$row['question_id']] = implode('|', $_POST['answers'][$row['question_id']]);
1502                 } else {
1503                         // no answer given
1504                         $_POST['answers'][$row['question_id']] = '-1'; // left blank
1505                         $score = 0;
1506                 }
1507                 return $score;
1508         }
1509
1510         //QTI Import multianswer Question
1511         function importQTI($_POST){
1512                 require_once(TR_INCLUDE_PATH.'classes/DAO/DAO.class.php');
1513                 require_once(TR_INCLUDE_PATH.'classes/Utility.class.php');
1514                 global $msg, $db, $_course_id;
1515 //              $_POST = $this->_POST; 
1516
1517                 if ($_POST['question'] == ''){
1518                         $msg->addError(array('EMPTY_FIELDS', _AT('question')));
1519                 }
1520
1521                 //Multiple answer can have 0+ answers, in the QTIImport.class, if size(answer) < 2, answer will be came a scalar.
1522                 //The following code will change $_POST[answer] back to a vector.
1523                 $_POST['answer'] = $_POST['answers'];
1524
1525                 if (!$msg->containsErrors()) {
1526                         $choice_new = array(); // stores the non-blank choices
1527                         $answer_new = array(); // stores the associated "answer" for the choices
1528
1529                         foreach ($_POST['choice'] as $choiceNum=>$choiceOpt) {
1530                                 $choiceOpt = Utility::validateLength($choiceOpt, 255);
1531                                 $choiceOpt = trim($choiceOpt);
1532                                 $_POST['answer'][$choiceNum] = intval($_POST['answer'][$choiceNum]);
1533                                 if ($choiceOpt == '') {
1534                                         /* an empty option can't be correct */
1535                                         $_POST['answer'][$choiceNum] = 0;
1536                                 } else {
1537                                         /* filter out empty choices/ remove gaps */
1538                                         $choice_new[] = $choiceOpt;
1539                                         if (in_array($choiceNum, $_POST['answer'])){
1540                                                 $answer_new[] = 1;
1541                                         } else {
1542                                                 $answer_new[] = 0;
1543                                         }
1544
1545                                         if ($_POST['answer'][$choiceNum] != 0)
1546                                                 $has_answer = TRUE;
1547                                 }
1548                         }
1549
1550                         if ($has_answer != TRUE) {
1551                 
1552                                 $hidden_vars['required']    = htmlspecialchars($_POST['required']);
1553                                 $hidden_vars['feedback']    = htmlspecialchars($_POST['feedback']);
1554                                 $hidden_vars['question']    = htmlspecialchars($_POST['question']);
1555                                 $hidden_vars['category_id'] = htmlspecialchars($_POST['category_id']);
1556
1557                                 for ($i = 0; $i < count($choice_new); $i++) {
1558                                         $hidden_vars['answer['.$i.']'] = htmlspecialchars($answer_new[$i]);
1559                                         $hidden_vars['choice['.$i.']'] = htmlspecialchars($choice_new[$i]);
1560                                 }
1561
1562                                 $msg->addConfirm('NO_ANSWER', $hidden_vars);
1563                         } else {                        
1564                                 //add slahes throughout - does that fix it?
1565                                 $_POST['answer'] = $answer_new;
1566                                 $_POST['choice'] = $choice_new;
1567                                 $_POST['answer'] = array_pad($_POST['answer'], 10, 0);
1568                                 $_POST['choice'] = array_pad($_POST['choice'], 10, '');
1569                         
1570 //                              $_POST['feedback'] = $addslashes($_POST['feedback']);
1571 //                              $_POST['question'] = $addslashes($_POST['question']);
1572
1573                                 $sql_params = array(    $_POST['category_id'], 
1574                                                                                 $_course_id,
1575                                                                                 $_POST['feedback'], 
1576                                                                                 $_POST['question'], 
1577                                                                                 $_POST['choice'][0], 
1578                                                                                 $_POST['choice'][1], 
1579                                                                                 $_POST['choice'][2], 
1580                                                                                 $_POST['choice'][3], 
1581                                                                                 $_POST['choice'][4], 
1582                                                                                 $_POST['choice'][5], 
1583                                                                                 $_POST['choice'][6], 
1584                                                                                 $_POST['choice'][7], 
1585                                                                                 $_POST['choice'][8], 
1586                                                                                 $_POST['choice'][9], 
1587                                                                                 $_POST['answer'][0], 
1588                                                                                 $_POST['answer'][1], 
1589                                                                                 $_POST['answer'][2], 
1590                                                                                 $_POST['answer'][3], 
1591                                                                                 $_POST['answer'][4], 
1592                                                                                 $_POST['answer'][5], 
1593                                                                                 $_POST['answer'][6], 
1594                                                                                 $_POST['answer'][7], 
1595                                                                                 $_POST['answer'][8], 
1596                                                                                 $_POST['answer'][9]);
1597
1598                                 $sql = vsprintf(TR_SQL_QUESTION_MULTIANSWER, $sql_params);
1599
1600 //                              $result = mysql_query($sql, $db);
1601 //                              if ($result==true){
1602                                 $dao = new DAO();
1603                                 if ($dao->execute($sql)) {
1604                                         return mysql_insert_id();
1605                                 }
1606                         }
1607                 }
1608         }
1609
1610 }
1611 ?>