made a copy
[atutor.git] / include / classes / testQuestions.class.php
1 <?php
2 /****************************************************************/
3 /* ATutor                                                                                                               */
4 /****************************************************************/
5 /* Copyright (c) 2002-2008 by Greg Gay & Joel Kronenberg        */
6 /* Adaptive Technology Resource Centre / University of Toronto  */
7 /* http://atutor.ca                                                                                             */
8 /*                                                              */
9 /* This program is free software. You can redistribute it and/or*/
10 /* modify it under the terms of the GNU General Public License  */
11 /* as published by the Free Software Foundation.                                */
12 /****************************************************************/
13 // $Id$
14 require(AT_INCLUDE_PATH.'lib/test_question_queries.inc.php');
15
16 /*
17  * Steps to follow when adding a new question type:
18  *
19  * 1 - Create a class extending AbstractQuestion or extend an 
20  *     existing question class.
21  *     Define $sPrefix and $sNameVar appropriately.
22  *     Implement the following methods, which set template variables:
23  *
24  *        assignQTIVariables()
25  *        assignDisplayResultVariables()
26  *        assignDisplayVariables()
27  *        assignDisplayStatisticsVariables()
28  *                importQti()
29  *     
30  *     And implement mark() which is used for marking the result.
31  *
32  * 2 - Add the new class name to $question_classes in test_question_factory()
33  *
34  * 3 - Add $sNameVar to the language database.
35  *
36  * 4 - Create the following files for creating and editing the question,
37  *     where "{PREFIX}" is the value defined by $sPrefix:
38  *
39  *     /tools/tests/create_question_{PREFIX}.php
40  *     /tools/tests/edit_question_{PREFIX}.php
41  *
42  * 5 - Add those two newly created pages to 
43  *     /mods/_standard/tests/module.php
44  *
45  * 6 - Create the following template files:
46  *
47  *     /themes/default/test_questions/{PREFIX}.tmpl.php
48  *     /themes/default/test_questions/{PREFIX}_qti_2p1.tmpl.php
49  *     /themes/default/test_questions/{PREFIX}_result.tmpl.php
50  *     /themes/default/test_questions/{PREFIX}_stats.tmpl.php
51  *
52  * 7 - Add the new question type to qti import/export tools,
53  *     Implement the following methods, which set template variables:
54  *
55  *     include/classes/QTI/QTIParser.class.php
56  *         getQuestionType()
57  *
58  * 8 - Done!
59  **/
60 class TestQuestions {
61         // returns array of prefix => name, sorted!
62         /*static */function getQuestionPrefixNames() {
63                 $question_prefix_names = array(); // prefix => name
64                 $questions = TestQuestions::getQuestionClasses();
65                 foreach ($questions as $type => $question) {
66                         $o = TestQuestions::getQuestion($type);
67                         $question_prefix_names[$o->getPrefix()] = $o->getName();
68                 }
69                 asort($question_prefix_names);
70                 return $question_prefix_names;
71         }
72
73         /*static */function getQuestionClasses() {
74                 /** NOTE: The indices are CONSTANTS. Do NOT change!! **/
75                 $question_classes = array(); // type ID => class name
76                 $question_classes[1] = 'MultichoiceQuestion';
77                 $question_classes[2] = 'TruefalseQuestion';
78                 $question_classes[3] = 'LongQuestion';
79                 $question_classes[4] = 'LikertQuestion';
80                 $question_classes[5] = 'MatchingQuestion';
81                 $question_classes[6] = 'OrderingQuestion';
82                 $question_classes[7] = 'MultianswerQuestion';
83                 $question_classes[8] = 'MatchingddQuestion';
84
85                 return $question_classes;
86         }
87
88         /**
89          * Used to create question objects based on $question_type.
90          * A singleton that creates one obj per question since
91          * questions are all stateless.
92          * Returns a reference to the question object.
93          */
94         /*static */function & getQuestion($question_type) {
95                 static $objs, $question_classes;
96
97                 if (isset($objs[$question_type])) {
98                         return $objs[$question_type];
99                 }
100
101                 $question_classes = TestQuestions::getQuestionClasses();
102
103                 if (isset($question_classes[$question_type])) {
104                         global $savant;
105                         $objs[$question_type] = new $question_classes[$question_type]($savant);
106                 } else {
107                         return FALSE;
108                 }
109
110                 return $objs[$question_type];
111         }
112 }
113
114 /** 
115  * Export test questions
116  * @param       array   an array consist of all the ids of the questions in which we desired to export.
117  */
118 function test_question_qti_export_v2p1($question_ids) {
119         require(AT_INCLUDE_PATH.'classes/zipfile.class.php'); // for zipfile
120         require(AT_INCLUDE_PATH.'lib/html_resource_parser.inc.php'); // for get_html_resources()
121         require(AT_INCLUDE_PATH.'classes/XML/XML_HTMLSax/XML_HTMLSax.php');     // for XML_HTMLSax
122
123         global $savant, $db, $system_courses, $languageManager;
124
125         $course_language = $system_courses[$_SESSION['course_id']]['primary_language'];
126         $courseLanguage =& $languageManager->getLanguage($course_language);
127         $course_language_charset = $courseLanguage->getCharacterSet();
128
129         $zipfile = new zipfile();
130         $zipfile->create_dir('resources/'); // for all the dependency files
131         $resources    = array();
132         $dependencies = array();
133
134         asort($question_ids);
135
136         $question_ids_delim = implode(',',$question_ids);
137
138         $sql = "SELECT * FROM ".TABLE_PREFIX."tests_questions WHERE course_id=$_SESSION[course_id] AND question_id IN($question_ids_delim)";
139         $result = mysql_query($sql, $db);
140         while ($row = mysql_fetch_assoc($result)) {
141                 $obj = TestQuestions::getQuestion($row['type']);
142                 $xml = $obj->exportQTI($row, $course_language_charset, '2.1');
143                 $local_dependencies = array();
144
145                 $text_blob = implode(' ', $row);
146                 $local_dependencies = get_html_resources($text_blob);
147                 $dependencies = array_merge($dependencies, $local_dependencies);
148
149                 $resources[] = array('href'         => 'question_'.$row['question_id'].'.xml',
150                                                          'dependencies' => array_keys($local_dependencies));
151
152                 //TODO
153                 $savant->assign('xml_content', $xml);
154                 $savant->assign('title', $row['question']);
155                 $xml = $savant->fetch('test_questions/wrapper.tmpl.php');
156
157                 $zipfile->add_file($xml, 'question_'.$row['question_id'].'.xml');
158         }
159
160         // add any dependency files:
161         foreach ($dependencies as $resource => $resource_server_path) {
162                 $zipfile->add_file(@file_get_contents($resource_server_path), 'resources/' . $resource, filemtime($resource_server_path));
163         }
164
165         // construct the manifest xml
166         $savant->assign('resources', $resources);
167         $savant->assign('dependencies', array_keys($dependencies));
168         $savant->assign('encoding', $course_language_charset);
169         $manifest_xml = $savant->fetch('test_questions/manifest_qti_2p1.tmpl.php');
170
171         $zipfile->add_file($manifest_xml, 'imsmanifest.xml');
172
173         $zipfile->close();
174
175         $filename = str_replace(array(' ', ':'), '_', $_SESSION['course_title'].'-'._AT('question_database').'-'.date('Ymd'));
176         $zipfile->send_file($filename);
177         exit;
178 }
179
180
181 /** 
182  * Export test questions
183  * @param       array   an array consist of all the ids of the questions in which we desired to export.
184  */
185 function test_question_qti_export($question_ids) {
186         require(AT_INCLUDE_PATH.'classes/zipfile.class.php'); // for zipfile
187         require(AT_INCLUDE_PATH.'lib/html_resource_parser.inc.php'); // for get_html_resources()
188         require(AT_INCLUDE_PATH.'classes/XML/XML_HTMLSax/XML_HTMLSax.php');     // for XML_HTMLSax
189
190         global $savant, $db, $system_courses, $languageManager;
191
192         $course_language = $system_courses[$_SESSION['course_id']]['primary_language'];
193         $courseLanguage =& $languageManager->getLanguage($course_language);
194         $course_language_charset = $courseLanguage->getCharacterSet();
195
196         $zipfile = new zipfile();
197         $zipfile->create_dir('resources/'); // for all the dependency files
198         $resources    = array();
199         $dependencies = array();
200
201         asort($question_ids);
202
203         $question_ids_delim = implode(',',$question_ids);
204
205         $sql = "SELECT * FROM ".TABLE_PREFIX."tests_questions WHERE course_id=$_SESSION[course_id] AND question_id IN($question_ids_delim)";
206         $result = mysql_query($sql, $db);
207         while ($row = mysql_fetch_assoc($result)) {
208                 $obj = TestQuestions::getQuestion($row['type']);
209                 $local_xml = '';
210                 $local_xml = $obj->exportQTI($row, $course_language_charset, '1.2.1');
211                 $local_dependencies = array();
212
213                 $text_blob = implode(' ', $row);
214                 $local_dependencies = get_html_resources($text_blob);
215                 $dependencies = array_merge($dependencies, $local_dependencies);
216
217 //              $resources[] = array('href'         => 'question_'.$row['question_id'].'.xml',
218 //                                                       'dependencies' => array_keys($local_dependencies));
219
220                 $xml = $xml . "\n\n" . $local_xml;              
221         }
222         $xml = trim($xml);
223
224         //TODO
225         $savant->assign('xml_content', $xml);
226         $savant->assign('title', $row['question']);
227         $xml = $savant->fetch('test_questions/wrapper.tmpl.php');
228
229         $xml_filename = 'atutor_questions.xml';
230         $zipfile->add_file($xml, $xml_filename);
231
232         // add any dependency files:
233         foreach ($dependencies as $resource => $resource_server_path) {
234                 $zipfile->add_file(@file_get_contents($resource_server_path), 'resources/' . $resource, filemtime($resource_server_path));
235         }
236
237         // construct the manifest xml
238 //      $savant->assign('resources', $resources);
239         $savant->assign('dependencies', array_keys($dependencies));
240         $savant->assign('encoding', $course_language_charset);
241         $savant->assign('xml_filename', $xml_filename);
242 //      $manifest_xml = $savant->fetch('test_questions/manifest_qti_2p1.tmpl.php');
243         $manifest_xml = $savant->fetch('test_questions/manifest_qti_1p2.tmpl.php');
244
245         $zipfile->add_file($manifest_xml, 'imsmanifest.xml');
246
247         $zipfile->close();
248
249         $filename = str_replace(array(' ', ':'), '_', $_SESSION['course_title'].'-'._AT('question_database').'-'.date('Ymd'));
250         $zipfile->send_file($filename);
251         exit;
252 }
253
254
255 /** 
256  * Export test 
257  * @param       int             test id
258  * @param       string  the test title
259  * @param       ref             [OPTIONAL] zip object reference
260  * @param       array   [OPTIONAL] list of already added files.
261  */
262 function test_qti_export($tid, $test_title='', $zipfile = null){
263         require_once(AT_INCLUDE_PATH.'classes/zipfile.class.php'); // for zipfile
264         require_once(AT_INCLUDE_PATH.'classes/XML/XML_HTMLSax/XML_HTMLSax.php');        // for XML_HTMLSax
265         require_once(AT_INCLUDE_PATH.'lib/html_resource_parser.inc.php'); // for get_html_resources()
266         global $savant, $db, $system_courses, $languageManager, $test_zipped_files, $test_files, $use_cc;
267         global $course_id;
268
269         $course_language = $system_courses[$_SESSION['course_id']]['primary_language'];
270         $courseLanguage =& $languageManager->getLanguage($course_language);
271         $course_language_charset = $courseLanguage->getCharacterSet();
272         $imported_files;
273         $zipflag = false;
274
275         if ($zipfile==null){
276                 $zipflag = true;
277         }
278
279         if ($test_zipped_files == null){
280                 $test_zipped_files = array();
281         }
282
283         if ($zipflag){
284                 $zipfile = new zipfile();
285                 $zipfile->create_dir('resources/'); // for all the dependency files
286         }
287         $resources    = array();
288         $dependencies = array();
289
290 //      don't want to sort it, i want the same order out.
291 //      asort($question_ids);
292
293         //TODO: Merge the following 2 sqls together.
294         //Randomized or not, export all the questions that are associated with it.
295         $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";
296         $result = mysql_query($sql, $db);
297         $question_ids = array();
298
299         while (($question_row = mysql_fetch_assoc($result)) != false){
300                 $question_ids[] = $question_row['question_id'];
301         }
302
303         //No questions in the test
304         if (sizeof($question_ids)==0){
305                 return;
306         }
307
308         $question_ids_delim = implode(',',$question_ids);       
309
310         //$sql = "SELECT * FROM ".TABLE_PREFIX."tests_questions WHERE course_id=$_SESSION[course_id] AND question_id IN($question_ids_delim)";
311         $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";
312
313         $result = mysql_query($sql, $db);
314         while ($row = mysql_fetch_assoc($result)) {
315                 $obj = TestQuestions::getQuestion($row['type']);
316                 $local_xml = '';
317                 $local_xml = $obj->exportQTI($row, $course_language_charset, '1.2.1');
318                 $local_dependencies = array();
319
320                 $text_blob = implode(' ', $row);
321                 $local_dependencies = get_html_resources($text_blob);
322                 $dependencies = array_merge($dependencies, $local_dependencies);
323
324                 $xml = $xml . "\n\n" . $local_xml;
325         }
326
327         //files that are found inside the test; used by print_organization(), to add all test files into QTI/ folder.
328         $test_files = $dependencies;
329
330         $resources[] = array('href'         => 'tests_'.$tid.'.xml',
331                                                  'dependencies' => array_keys($dependencies));
332
333         $xml = trim($xml);
334
335         //get test title
336         $sql = "SELECT title FROM ".TABLE_PREFIX."tests WHERE test_id = $tid";
337         $result = mysql_query($sql, $db);
338         $row = mysql_fetch_array($result);
339
340         //TODO: wrap around xml now
341         $savant->assign('xml_content', $xml);
342         $savant->assign('title', htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8'));
343         $xml = $savant->fetch('test_questions/wrapper.tmpl.php');
344
345         $xml_filename = 'tests_'.$tid.'.xml';
346         if (!$use_cc){          
347                 $zipfile->add_file($xml, $xml_filename);
348         } else {
349                 $zipfile->add_file($xml, 'QTI/'.$xml_filename);
350         }
351
352         // add any dependency files:
353         if (!$use_cc){
354                 foreach ($dependencies as $resource => $resource_server_path) {
355                         //add this file in if it's not already in the zip package
356                         if (!in_array($resource_server_path, $test_zipped_files)){
357                                 $zipfile->add_file(@file_get_contents($resource_server_path), 'resources/'.$resource, filemtime($resource_server_path));
358                                 $test_zipped_files[] = $resource_server_path;
359                         }
360                 }
361         }
362
363         if ($zipflag){
364                 // construct the manifest xml
365                 $savant->assign('resources', $resources);
366                 $savant->assign('dependencies', array_keys($dependencies));
367                 $savant->assign('encoding', $course_language_charset);
368                 $savant->assign('title', $test_title);
369                 $savant->assign('xml_filename', $xml_filename);
370                 
371                 $manifest_xml = $savant->fetch('test_questions/manifest_qti_1p2.tmpl.php');
372                 $zipfile->add_file($manifest_xml, 'imsmanifest.xml');
373
374                 $zipfile->close();
375
376                 $filename = str_replace(array(' ', ':'), '_', $_SESSION['course_title'].'-'.$test_title.'-'.date('Ymd'));
377                 $zipfile->send_file($filename);
378                 exit;
379         }
380         
381         $return_array[$xml_filename] = array_keys($dependencies);
382         return $return_array;
383         //done
384 }
385
386
387 /* 
388  * Recursively create folders
389  * For the purpose of this webapp only.  All the paths are seperated by a /
390  * And thus this function will loop through each directory and create them on the way
391  * if it doesn't exist.
392  * @author harris
393  */
394 function recursive_mkdir($path, $mode = 0700) {
395     $dirs = explode(DIRECTORY_SEPARATOR , $path);
396     $count = count($dirs);
397     $path = '';
398     for ($i = 0; $i < $count; ++$i) {
399         $path .= $dirs[$i].DIRECTORY_SEPARATOR;
400                 //If the directory has not been created, create it and return error on failure
401         if (!is_dir($path) && !mkdir($path, $mode)) {
402             return false;
403         }
404     }
405     return true;
406 }
407
408
409 /**
410 * keeps count of the question number (when displaying the question)
411 * need this function because PHP 4 doesn't support static members
412 */
413 function TestQuestionCounter($increment = FALSE) {
414         static $count;
415
416         if (!isset($count)) { 
417                 $count = 0;
418         }
419         if ($increment) {
420                 $count++;
421         }
422
423         return $count;
424 }
425
426
427 /**
428  * testQuestion
429  *
430  * Note that all PHP 5 OO declarations and signatures are commented out to be
431  * backwards compatible with PHP 4.
432  *
433  */
434 /*abstract */ class AbstractTestQuestion  {
435         /**
436         * Savant2 $savant - refrence to the savant obj
437         */
438         /*protected */ var $savant;
439
440         /**
441         * Constructor method.  Initialises variables.
442         */
443         function AbstractTestQuestion(&$savant) { $this->savant =& $savant; }
444
445         /**
446         * Public
447         */
448         /*final public */function seed($salt) {
449                 /**
450                 * by controlling the seed before calling array_rand() we insure that
451                 * we can un-randomize the order for marking.
452                 * used with ordering type questions only.
453                 */
454                 srand($salt + $_SESSION['member_id']);
455         }
456
457         /**
458         * Public
459         */
460         /*final public */function unseed() {
461                 // To fix http://www.atutor.ca/atutor/mantis/view.php?id=3167
462                 // Disturb the seed for ordering questions after mark to avoid the deterioration  
463                 // of the random distribution due to a repeated initialization of the same random seed
464                 list($usec, $sec) = explode(" ", microtime());
465                 srand((int)($usec*10));
466         }
467
468         /**
469         * Public
470         * Prints the name of this question
471         */
472         /*final public */function printName() { echo $this->getName(); }
473
474         /**
475         * Public
476         * Prints the name of this question
477         */
478         /*final public */function getName() { return _AT($this->sNameVar); }
479
480         /**
481         * Public
482         * Returns the prefix string (used for file names)
483         */
484         /*final public */function getPrefix() { return $this->sPrefix; }
485
486         /**
487         * Display the current question (for taking or previewing a test/question)
488         */
489         /*final public */function display($row, $response = '') {
490                 // print the generic question header
491                 $this->displayHeader($row['weight']);
492
493                 // print the question specific template
494                 $row['question'] = format_content($row['question'], 1, '');
495                 $this->assignDisplayVariables($row, $response);
496                 $this->savant->display('test_questions/' . $this->sPrefix . '.tmpl.php');
497                 
498                 // print the generic question footer
499                 $this->displayFooter();
500         }
501
502         /**
503         * Display the result for the current question
504         */
505         /*final public */function displayResult($row, $answer_row, $editable = FALSE) {
506                 // print the generic question header
507                 $this->displayHeader($row['weight'], $answer_row['score'], $editable ? $row['question_id'] : FALSE);
508
509                 // print the question specific template
510                 $this->assignDisplayResultVariables($row, $answer_row);
511                 $this->savant->display('test_questions/' . $this->sPrefix . '_result.tmpl.php');
512                 
513                 // print the generic question footer
514                 $this->displayFooter();
515         }
516
517
518         /**
519         * print the question template header
520         */
521         /*final public */function displayResultStatistics($row, $answers) {
522                 TestQuestionCounter(TRUE);
523                 $this->assignDisplayStatisticsVariables($row, $answers);
524                 $this->savant->display('test_questions/' . $this->sPrefix . '_stats.tmpl.php');
525         }
526
527         /*final public */function exportQTI($row, $encoding, $version) {
528                 $this->savant->assign('encoding', $encoding);
529                 //Convert all row values to html entities
530                 foreach ($row as $k=>$v){
531                         $row[$k] = htmlspecialchars($v, ENT_QUOTES, 'UTF-8');   //not using htmlentities cause it changes some languages falsely.
532                 }
533                 $this->assignQTIVariables($row);
534                 if ($version=='2.1') {
535                         $xml = $this->savant->fetch('test_questions/'. $this->sPrefix . '_qti_2p1.tmpl.php');
536                 } else {        
537                         $xml = $this->savant->fetch('test_questions/'. $this->sPrefix . '_qti_1p2.tmpl.php');
538                 }
539                 return $xml;
540         }
541
542         /**
543         * print the question template header
544         */
545         /*final private */function displayHeader($weight, $score = FALSE, $question_id = FALSE) {
546                 TestQuestionCounter(TRUE);
547                 
548                 if ($score) $score = intval($score);
549                 $this->savant->assign('question_id', $question_id);
550                 $this->savant->assign('score', $score);
551                 $this->savant->assign('weight', $weight);
552                 $this->savant->assign('type',   _AT($this->sNameVar));
553                 $this->savant->assign('number', TestQuestionCounter());
554                 $this->savant->display('test_questions/header.tmpl.php');
555         }
556
557         /**
558         * print the question template footer
559         */
560         /*final private */function displayFooter() {
561                 $this->savant->display('test_questions/footer.tmpl.php');
562         }
563
564         /**
565         * return only the non-empty choices from $row.
566         * assumes choices are sequential.
567         */
568         /*protected */function getChoices($row) {
569                 $choices = array();
570                 for ($i=0; $i < 10; $i++) {
571                         if ($row['choice_'.$i] != '') {
572                                 $num_choices++;
573                                 $choices[] = $row['choice_'.$i];
574                         } else {
575                                 break;
576                         }
577                 }
578                 return $choices;
579         }
580 }
581
582 /**
583 * orderingQuestion
584 *
585 */
586 class OrderingQuestion extends AbstractTestQuestion {
587         /*protected */ var $sNameVar = 'test_ordering';
588         /*protected */ var $sPrefix = 'ordering';
589         
590         /*protected */function assignDisplayResultVariables($row, $answer_row) {
591                 $answers = explode('|', $answer_row['answer']);
592
593                 $num_choices = count($this->getChoices($row));
594
595                 $this->savant->assign('base_href', AT_BASE_HREF);
596                 $this->savant->assign('num_choices', $num_choices);
597                 $this->savant->assign('answers', $answers);
598                 $this->savant->assign('row', $row);
599         }
600
601         /*protected */function assignQTIVariables($row) {
602                 $choices = $this->getChoices($row);
603                 $num_choices = count($choices);
604
605                 $this->savant->assign('num_choices', $num_choices);
606                 $this->savant->assign('row', $row);
607         }
608
609         /*protected */function assignDisplayVariables($row, $response) {
610                 // determine the number of choices this question has
611                 // and save those choices to be re-assigned back to $row
612                 // in the randomized order.
613                 $choices = $this->getChoices($row);
614                 $num_choices = count($choices);
615
616                 // response from the test_answers table is in the correct order
617                 // so, they have to be re-randomized in the same order as the
618                 // choices are. this is only possible because of the seed() method.
619                 $response = explode('|', $response);
620                 $new_response = array();
621
622                 // randomize the order of choices and re-assign to $row
623                 $this->seed($row['question_id']);
624                 $rand = array_rand($choices, $num_choices);
625                 for ($i=0; $i < 10; $i++) {
626                         $row['choice_'.$i] = $choices[$rand[$i]];
627                         $new_response[$i]  = $response[$rand[$i]];
628                 }
629
630                 $this->savant->assign('num_choices', $num_choices);
631                 $this->savant->assign('row', $row);
632
633                 $this->savant->assign('response', $new_response);
634         }
635
636         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
637                 $num_results = 0;               
638                 foreach ($answers as $answer) {
639                         $num_results += $answer['count'];
640                 }
641
642                 $choices = $this->getChoices($row);
643                 $num_choices = count($choices);
644
645                 $final_answers = array(); // assoc array of # of times that key was used correctly 0, 1, ...  $num -1
646                 foreach ($answers as $key => $value) {
647                         $values = explode('|', $key);
648                         // we assume $values is never empty and contains $num number of answers
649                         for ($i=0; $i<=$num_choices; $i++) {
650                                 if ($values[$i] == $i) {
651                                         $final_answers[$i] += $answers[$key]['count'];
652                                 }
653                         }
654                 }
655
656                 $this->savant->assign('num_results', $num_results);
657                 $this->savant->assign('num_choices', $num_choices);
658                 $this->savant->assign('answers', $final_answers);
659                 $this->savant->assign('row', $row);
660         }
661
662         /*public */function mark($row) { 
663                 $this->seed($row['question_id']);
664                 $num_choices = count($_POST['answers'][$row['question_id']]);
665                 $answers = range(0, $num_choices-1);
666                 $answers = array_rand($answers, $num_choices);
667                 
668                 // Disturb the seed for ordering questions after mark to avoid the deterioration  
669                 // of the random distribution due to a repeated initialization of the same random seed
670                 $this->unseed();
671
672                 $num_answer_correct = 0;
673
674                 $ordered_answers = array();
675
676                 for ($i = 0; $i < $num_choices ; $i++) {
677                         $_POST['answers'][$row['question_id']][$i] = intval($_POST['answers'][$row['question_id']][$i]);
678
679                         if ($_POST['answers'][$row['question_id']][$i] == -1) {
680                                 // nothing to do. it was left blank
681                         } else if ($_POST['answers'][$row['question_id']][$i] == $answers[$i]) {
682                                 $num_answer_correct++;
683                         }
684                         $ordered_answers[$answers[$i]] = $_POST['answers'][$row['question_id']][$i];
685                 }
686                 ksort($ordered_answers);
687
688                 $score = 0;
689
690                 // to avoid roundoff errors:
691                 if ($num_answer_correct == $num_choices) {
692                         $score = $row['weight'];
693                 } else if ($num_answer_correct > 0) {
694                         $score = number_format($row['weight'] / $num_choices * $num_answer_correct, 2);
695                         if ( (float) (int) $score == $score) {
696                                 $score = (int) $score; // a whole number with decimals, eg. "2.00"
697                         } else {
698                                 $score = trim($score, '0'); // remove trailing zeros, if any, eg. "2.50"
699                         }
700                 }
701
702                 $_POST['answers'][$row['question_id']] = implode('|', $ordered_answers);
703
704                 return $score;
705         }
706
707         //QTI Import Ordering Question
708         function importQTI($_POST){
709                 global $msg, $db;
710
711                 if ($_POST['question'] == ''){
712                         $missing_fields[] = _AT('question');
713                 }
714
715                 if (trim($_POST['choice'][0]) == '') {
716                         $missing_fields[] = _AT('item').' 1';
717                 }
718                 if (trim($_POST['choice'][1]) == '') {
719                         $missing_fields[] = _AT('item').' 2';
720                 }
721
722                 if ($missing_fields) {
723                         $missing_fields = implode(', ', $missing_fields);
724                         $msg->addError(array('EMPTY_FIELDS', $missing_fields));
725                 }
726
727                 if (!$msg->containsErrors()) {
728                         $choice_new = array(); // stores the non-blank choices
729                         $answer_new = array(); // stores the non-blank answers
730                         $order = 0; // order count
731                         for ($i=0; $i<10; $i++) {
732                                 /**
733                                  * Db defined it to be 255 length, chop strings off it it's less than that
734                                  * @harris
735                                  */
736                                 $_POST['choice'][$i] = validate_length($_POST['choice'][$i], 255);
737                                 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
738
739                                 if ($_POST['choice'][$i] != '') {
740                                         /* filter out empty choices/ remove gaps */
741                                         $choice_new[] = $_POST['choice'][$i];
742                                         $answer_new[] = $order++;
743                                 }
744                         }
745
746                         $_POST['choice']   = array_pad($choice_new, 10, '');
747                         $answer_new        = array_pad($answer_new, 10, 0);
748 //                      $_POST['feedback'] = $addslashes($_POST['feedback']);
749 //                      $_POST['question'] = $addslashes($_POST['question']);
750                 
751                         $sql_params = array(    $_POST['category_id'], 
752                                                                         $_SESSION['course_id'],
753                                                                         $_POST['feedback'], 
754                                                                         $_POST['question'], 
755                                                                         $_POST['choice'][0], 
756                                                                         $_POST['choice'][1], 
757                                                                         $_POST['choice'][2], 
758                                                                         $_POST['choice'][3], 
759                                                                         $_POST['choice'][4], 
760                                                                         $_POST['choice'][5], 
761                                                                         $_POST['choice'][6], 
762                                                                         $_POST['choice'][7], 
763                                                                         $_POST['choice'][8], 
764                                                                         $_POST['choice'][9], 
765                                                                         $answer_new[0], 
766                                                                         $answer_new[1], 
767                                                                         $answer_new[2], 
768                                                                         $answer_new[3], 
769                                                                         $answer_new[4], 
770                                                                         $answer_new[5], 
771                                                                         $answer_new[6], 
772                                                                         $answer_new[7], 
773                                                                         $answer_new[8], 
774                                                                         $answer_new[9]);
775
776                         $sql = vsprintf(AT_SQL_QUESTION_ORDERING, $sql_params);
777
778                         $result = mysql_query($sql, $db);
779                         if ($result==true){
780                                 return mysql_insert_id();
781                         }                       
782                 }
783         }
784 }
785
786 /**
787 * truefalseQuestion
788 *
789 */
790 class TruefalseQuestion extends AbstracttestQuestion {
791         /*protected */ var $sPrefix = 'truefalse';
792         /*protected */ var $sNameVar   = 'test_tf';
793
794         /*protected */function assignQTIVariables($row) {
795                 $this->savant->assign('row', $row);
796         }
797
798         /*protected */function assignDisplayResultVariables($row, $answer_row) {
799
800                 $this->savant->assign('base_href', AT_BASE_HREF);
801                 $this->savant->assign('answers', $answer_row['answer']);
802                 $this->savant->assign('row', $row);
803         }
804
805         /*protected */function assignDisplayVariables($row, $response) {
806                 $this->savant->assign('row', $row);
807                 $this->savant->assign('response', $response);
808         }
809
810         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
811                 $num_results = 0;               
812                 foreach ($answers as $answer) {
813                         $num_results += $answer['count'];
814                 }
815
816                 $this->savant->assign('num_results', $num_results);
817                 $this->savant->assign('num_blanks', (int) $answers['-1']['count']);
818                 $this->savant->assign('num_true', (int) $answers['1']['count']);
819                 $this->savant->assign('num_false', (int) $answers['2']['count']);
820                 $this->savant->assign('row', $row);
821         }
822
823         /*public */function mark($row) { 
824                 $_POST['answers'][$row['question_id']] = intval($_POST['answers'][$row['question_id']]);
825
826                 if ($row['answer_0'] == $_POST['answers'][$row['question_id']]) {
827                         return (int) $row['weight'];
828                 } // else:
829                 return 0;
830         }
831
832         //QTI Import True/False Question
833         function importQTI($_POST){
834                 global $msg, $db;
835
836                 if ($_POST['question'] == ''){
837                         $msg->addError(array('EMPTY_FIELDS', _AT('statement')));
838                 }
839
840                 //assign true answer to 1, false answer to 2, idk to 3, for ATutor
841                 if  ($_POST['answer'] == 'ChoiceT'){
842                         $_POST['answer'] = 1;
843                 } else {
844                         $_POST['answer'] = 2;   
845                 }
846
847                 if (!$msg->containsErrors()) {
848 //                      $_POST['feedback'] = $addslashes($_POST['feedback']);
849 //                      $_POST['question'] = $addslashes($_POST['question']);
850
851
852                         $sql_params = array(    $_POST['category_id'], 
853                                                                         $_SESSION['course_id'],
854                                                                         $_POST['feedback'], 
855                                                                         $_POST['question'], 
856                                                                         $_POST['answer']);
857
858                         $sql = vsprintf(AT_SQL_QUESTION_TRUEFALSE, $sql_params);
859                         $result = mysql_query($sql, $db);
860                         if ($result==true){
861                                 return mysql_insert_id();
862                         }                       
863                 }
864         }
865 }
866
867 /**
868 * likertQuestion
869 *
870 */
871 class LikertQuestion extends AbstracttestQuestion {
872         /*protected */ var $sPrefix = 'likert';
873         /*protected */ var $sNameVar   = 'test_lk';
874
875         /*protected */function assignQTIVariables($row) {
876                 $choices = $this->getChoices($row);
877                 $num_choices = count($choices);
878
879                 $this->savant->assign('num_choices', $num_choices);
880                 $this->savant->assign('row', $row);
881         }
882
883         /*protected */function assignDisplayResultVariables($row, $answer_row) {
884                 $this->savant->assign('answer', $answer_row['answer']);
885                 $this->savant->assign('row', $row);
886         }
887
888         /*protected */function assignDisplayVariables($row, $response) {
889                 $choices = $this->getChoices($row);
890                 $num_choices = count($choices);
891
892                 $this->savant->assign('num_choices', $num_choices);
893                 $this->savant->assign('row', $row);
894
895                 if (empty($response)) {
896                         $response = -1;
897                 }
898                 $this->savant->assign('response', $response);
899         }
900
901         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
902                 $num_results = 0;               
903                 foreach ($answers as $answer) {
904                         $num_results += $answer['count'];
905                 }
906                 
907                 $choices = $this->getChoices($row);
908                 $num_choices = count($choices);
909
910                 $sum = 0;
911                 for ($i=0; $i<$num_choices; $i++) {
912                         $sum += ($i+1) * $answers[$i]['count'];
913                 }
914                 $average = round($sum/$num_results, 1);
915
916                 $this->savant->assign('num_results', $num_results);
917                 $this->savant->assign('average', $average);
918                 $this->savant->assign('num_choices', $num_choices);
919                 $this->savant->assign('num_blanks', (int) $answers['-1']['count']);
920                 $this->savant->assign('answers', $answers);
921                 $this->savant->assign('row', $row);
922         }
923
924         /*public */function mark($row) { 
925                 $_POST['answers'][$row['question_id']] = intval($_POST['answers'][$row['question_id']]);
926                 return 0;
927         }
928
929         //QTI Import Likert Question
930         function importQTI($_POST){
931                 global $msg, $db;
932 //              $_POST = $this->_POST; 
933
934                 $empty_fields = array();
935                 if ($_POST['question'] == ''){
936                         $empty_fields[] = _AT('question');
937                 }
938                 if ($_POST['choice'][0] == '') {
939                         $empty_fields[] = _AT('choice').' 1';
940                 }
941
942                 if ($_POST['choice'][1] == '') {
943                         $empty_fields[] = _AT('choice').' 2';
944                 }
945
946                 if (!empty($empty_fields)) {
947 //                      $msg->addError(array('EMPTY_FIELDS', implode(', ', $empty_fields)));
948                 }
949
950                 if (!$msg->containsErrors()) {
951                         $_POST['feedback']   = '';
952 //                      $_POST['question']   = $addslashes($_POST['question']);
953
954                         for ($i=0; $i<10; $i++) {
955                                 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
956                                 $_POST['answer'][$i] = intval($_POST['answer'][$i]);
957
958                                 if ($_POST['choice'][$i] == '') {
959                                         /* an empty option can't be correct */
960                                         $_POST['answer'][$i] = 0;
961                                 }
962                         }
963
964                         $sql_params = array(    $_POST['category_id'], 
965                                                                         $_SESSION['course_id'],
966                                                                         $_POST['feedback'], 
967                                                                         $_POST['question'], 
968                                                                         $_POST['choice'][0], 
969                                                                         $_POST['choice'][1], 
970                                                                         $_POST['choice'][2], 
971                                                                         $_POST['choice'][3], 
972                                                                         $_POST['choice'][4], 
973                                                                         $_POST['choice'][5], 
974                                                                         $_POST['choice'][6], 
975                                                                         $_POST['choice'][7], 
976                                                                         $_POST['choice'][8], 
977                                                                         $_POST['choice'][9], 
978                                                                         $_POST['answer'][0], 
979                                                                         $_POST['answer'][1], 
980                                                                         $_POST['answer'][2], 
981                                                                         $_POST['answer'][3], 
982                                                                         $_POST['answer'][4], 
983                                                                         $_POST['answer'][5], 
984                                                                         $_POST['answer'][6], 
985                                                                         $_POST['answer'][7], 
986                                                                         $_POST['answer'][8], 
987                                                                         $_POST['answer'][9]);
988
989                         $sql = vsprintf(AT_SQL_QUESTION_LIKERT, $sql_params);
990                         $result = mysql_query($sql, $db);
991                         if ($result==true){
992                                 return mysql_insert_id();
993                         }
994                 }
995         }
996 }
997
998 /**
999 * longQuestion
1000 *
1001 */
1002 class LongQuestion extends AbstracttestQuestion {
1003         /*protected */ var $sPrefix = 'long';
1004         /*protected */ var $sNameVar = 'test_open';
1005
1006         /*protected */function assignQTIVariables($row) {
1007                 $this->savant->assign('row', $row);
1008         }
1009
1010         /*protected */function assignDisplayResultVariables($row, $answer_row) {
1011                 $this->savant->assign('answer', $answer_row['answer']);
1012                 $this->savant->assign('row', $row);
1013         }
1014
1015         /*protected */function assignDisplayVariables($row, $response) {
1016                 $this->savant->assign('row', $row);
1017                 $this->savant->assign('response', $response);
1018         }
1019
1020         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
1021                 $num_results = 0;               
1022                 foreach ($answers as $answer) {
1023                         $num_results += $answer['count'];
1024                 }
1025                 
1026                 $this->savant->assign('num_results', $num_results);
1027                 $this->savant->assign('num_blanks', (int) $answers['']['count']);
1028                 $this->savant->assign('answers', $answers);
1029                 $this->savant->assign('row', $row);
1030         }
1031
1032         /*public */function mark($row) { 
1033                 global $addslashes;
1034                 $_POST['answers'][$row['question_id']] = $addslashes($_POST['answers'][$row['question_id']]);
1035                 return NULL;
1036         }
1037
1038         //QTI Import Open end/long Question
1039         function importQTI($_POST){
1040                 global $msg, $db;
1041 //              $_POST = $this->_POST; 
1042
1043                 if ($_POST['question'] == ''){
1044 //                      $msg->addError(array('EMPTY_FIELDS', _AT('question')));
1045                 }
1046
1047                 if (!$msg->containsErrors()) {
1048 //                      $_POST['feedback'] = $addslashes($_POST['feedback']);
1049 //                      $_POST['question'] = $addslashes($_POST['question']);
1050
1051                         if ($_POST['property']==''){
1052                                 $_POST['property'] = 4; //essay
1053                         }
1054
1055                         $sql_params = array(    $_POST['category_id'], 
1056                                                                         $_SESSION['course_id'],
1057                                                                         $_POST['feedback'], 
1058                                                                         $_POST['question'], 
1059                                                                         $_POST['property']);
1060
1061                         $sql = vsprintf(AT_SQL_QUESTION_LONG, $sql_params);
1062
1063                         $result = mysql_query($sql, $db);
1064                         if ($result==true){
1065                                 return mysql_insert_id();
1066                         }
1067                 }
1068         }
1069 }
1070
1071 /**
1072 * matchingQuestion
1073 *
1074 */
1075 class MatchingQuestion extends AbstracttestQuestion {
1076         /*protected */ var $sPrefix = 'matching';
1077         /*protected */ var $sNameVar   = 'test_matching';
1078
1079         /*protected */function assignQTIVariables($row) {
1080                 $choices = $this->getChoices($row);
1081                 $num_choices = count($choices);
1082
1083                 $num_options = 0;
1084                 for ($i=0; $i < 10; $i++) {
1085                         if ($row['option_'. $i] != '') {
1086                                 $num_options++;
1087                         }
1088                 }
1089
1090                 $this->savant->assign('num_choices', $num_choices);
1091                 $this->savant->assign('num_options', $num_options);
1092                 $this->savant->assign('row', $row);
1093         }
1094
1095         /*protected */function assignDisplayResultVariables($row, $answer_row) {
1096                 $num_options = 0;
1097                 for ($i=0; $i < 10; $i++) {
1098                         if ($row['option_'. $i] != '') {
1099                                 $num_options++;
1100                         }
1101                 }
1102
1103                 $answer_row['answer'] = explode('|', $answer_row['answer']);
1104
1105                 global $_letters;
1106
1107                 $this->savant->assign('base_href', AT_BASE_HREF);
1108                 $this->savant->assign('answers', $answer_row['answer']);
1109                 $this->savant->assign('letters', $_letters);
1110                 $this->savant->assign('num_options', $num_options);
1111                 $this->savant->assign('row', $row);
1112         }
1113
1114         /*protected */function assignDisplayVariables($row, $response) {
1115                 $choices = $this->getChoices($row);
1116                 $num_choices = count($choices);
1117
1118                 if (empty($response)) {
1119                         $response = array_fill(0, $num_choices, -1);
1120                 } else {
1121                         $response = explode('|', $response);
1122                 }
1123
1124                 $num_options = 0;
1125                 for ($i=0; $i < 10; $i++) {
1126                         if ($row['option_'. $i] != '') {
1127                                 $num_options++;
1128                         }
1129                 }
1130
1131                 global $_letters;
1132
1133                 $this->savant->assign('num_choices', $num_choices);
1134                 $this->savant->assign('base_href', AT_BASE_HREF);
1135                 $this->savant->assign('letters', $_letters);
1136                 $this->savant->assign('num_options', $num_options);
1137                 $this->savant->assign('row', $row);
1138
1139                 $this->savant->assign('response', $response);
1140         }
1141
1142         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
1143                 $choices = $this->getChoices($row);
1144                 $num_choices = count($choices);
1145
1146                 $num_results = 0;
1147                 foreach ($answers as $answer) {
1148                         $num_results += $answer['count'];
1149                 }
1150                                         
1151                 foreach ($answers as $key => $value) {
1152                         $values = explode('|', $key);
1153                         if (count($values) > 1) {
1154                                 for ($i=0; $i<count($values); $i++) {
1155                                         $answers[$values[$i]]['count']++;
1156                                 }
1157                         }
1158                 }
1159
1160                 $this->savant->assign('num_choices', $num_choices);
1161                 $this->savant->assign('num_results', $num_results);
1162                 $this->savant->assign('answers', $answers);
1163                 $this->savant->assign('row', $row);
1164         }
1165
1166         /*public */function mark($row) { 
1167                 $num_choices = count($_POST['answers'][$row['question_id']]);
1168                 $num_answer_correct = 0;
1169                 foreach ($_POST['answers'][$row['question_id']] as $item_id => $response) {
1170                         if ($row['answer_' . $item_id] == $response) {
1171                                 $num_answer_correct++;
1172                         }
1173                         $_POST['answers'][$row['question_id']][$item_id] = intval($_POST['answers'][$row['question_id']][$item_id]);
1174                 }
1175
1176                 $score = 0;
1177                 // to avoid roundoff errors:
1178                 if ($num_answer_correct == $num_choices) {
1179                         $score = $row['weight'];
1180                 } else if ($num_answer_correct > 0) {
1181                         $score = number_format($row['weight'] / $num_choices * $num_answer_correct, 2);
1182                         if ( (float) (int) $score == $score) {
1183                                 $score = (int) $score; // a whole number with decimals, eg. "2.00"
1184                         } else {
1185                                 $score = trim($score, '0'); // remove trailing zeros, if any
1186                         }
1187                 }
1188
1189                 $_POST['answers'][$row['question_id']] = implode('|', $_POST['answers'][$row['question_id']]);
1190
1191                 return $score;
1192         }
1193
1194         //QTI Import Matching Question
1195         function importQTI($_POST){
1196                 global $msg, $db;
1197 //              $_POST = $this->_POST; 
1198
1199                 if (!is_array($_POST['answer'])){
1200                         $temp = $_POST['answer'];
1201                         $_POST['answer'] = array();
1202                         $_POST['answer'][0] = $temp;
1203                 }
1204                 ksort($_POST['answer']);        //array_pad returns an array disregard of the array keys
1205                 //default for matching is '-'
1206                 $_POST['answer']= array_pad($_POST['answer'], 10, -1);
1207
1208                 for ($i = 0 ; $i < 10; $i++) {
1209                         $_POST['groups'][$i]        = trim($_POST['groups'][$i]);
1210                         $_POST['answer'][$i] = (int) $_POST['answer'][$i];
1211                         $_POST['choice'][$i]          = trim($_POST['choice'][$i]);
1212                 }
1213
1214                 if (!$_POST['groups'][0] 
1215                         || !$_POST['groups'][1] 
1216                         || !$_POST['choice'][0] 
1217                         || !$_POST['choice'][1]) {
1218 //                      $msg->addError('QUESTION_EMPTY');
1219                 }
1220
1221                 if (!$msg->containsErrors()) {
1222 //                      $_POST['feedback']     = $addslashes($_POST['feedback']);
1223 //                      $_POST['instructions'] = $addslashes($_POST['instructions']);
1224                 
1225                         $sql_params = array(    $_POST['category_id'], 
1226                                                                         $_SESSION['course_id'],
1227                                                                         $_POST['feedback'], 
1228                                                                         $_POST['question'], 
1229                                                                         $_POST['groups'][0], 
1230                                                                         $_POST['groups'][1], 
1231                                                                         $_POST['groups'][2], 
1232                                                                         $_POST['groups'][3], 
1233                                                                         $_POST['groups'][4], 
1234                                                                         $_POST['groups'][5], 
1235                                                                         $_POST['groups'][6], 
1236                                                                         $_POST['groups'][7], 
1237                                                                         $_POST['groups'][8], 
1238                                                                         $_POST['groups'][9], 
1239                                                                         $_POST['answer'][0], 
1240                                                                         $_POST['answer'][1], 
1241                                                                         $_POST['answer'][2], 
1242                                                                         $_POST['answer'][3], 
1243                                                                         $_POST['answer'][4], 
1244                                                                         $_POST['answer'][5], 
1245                                                                         $_POST['answer'][6], 
1246                                                                         $_POST['answer'][7], 
1247                                                                         $_POST['answer'][8], 
1248                                                                         $_POST['answer'][9],
1249                                                                         $_POST['choice'][0], 
1250                                                                         $_POST['choice'][1], 
1251                                                                         $_POST['choice'][2], 
1252                                                                         $_POST['choice'][3], 
1253                                                                         $_POST['choice'][4], 
1254                                                                         $_POST['choice'][5], 
1255                                                                         $_POST['choice'][6], 
1256                                                                         $_POST['choice'][7], 
1257                                                                         $_POST['choice'][8], 
1258                                                                         $_POST['choice'][9]);
1259
1260                         $sql = vsprintf(AT_SQL_QUESTION_MATCHINGDD, $sql_params);
1261
1262                         $result = mysql_query($sql, $db);
1263                         if ($result==true){
1264                                 return mysql_insert_id();
1265                         }
1266                 }
1267         }
1268 }
1269
1270 /**
1271 * matchingddQuestion
1272 *
1273 */
1274 class MatchingddQuestion extends MatchingQuestion {
1275         /*protected */ var $sPrefix = 'matchingdd';
1276         /*protected */ var $sNameVar   = 'test_matchingdd';
1277 }
1278
1279 /**
1280 * multichoiceQuestion
1281 *
1282 */
1283 class MultichoiceQuestion extends AbstracttestQuestion {
1284         /*protected */ var $sPrefix = 'multichoice';
1285         /*protected */var $sNameVar = 'test_mc';
1286
1287         /*protected */function assignQTIVariables($row) {
1288                 $choices = $this->getChoices($row);
1289                 $num_choices = count($choices);
1290
1291                 $this->savant->assign('num_choices', $num_choices);
1292                 $this->savant->assign('row', $row);
1293         }
1294
1295         /*protected */function assignDisplayResultVariables($row, $answer_row) {
1296                 if (strpos($answer_row['answer'], '|') !== false) {
1297                         $answer_row['answer'] = explode('|', $answer_row['answer']);
1298                 } else {
1299                         $answer_row['answer'] = array($answer_row['answer']);
1300                 }
1301
1302                 $this->savant->assign('base_href', AT_BASE_HREF);
1303                 $this->savant->assign('answers', $answer_row['answer']);
1304                 $this->savant->assign('row', $row);
1305         }
1306
1307         /*protected */function assignDisplayVariables($row, $response) {
1308                 $choices = $this->getChoices($row);
1309                 $num_choices = count($choices);
1310
1311                 if ($response == '') {
1312                         $response = -1;
1313                 }
1314                 $response = explode('|', $response);
1315                 $this->savant->assign('response', $response);
1316
1317                 $this->savant->assign('num_choices', $num_choices);
1318                 $this->savant->assign('row', $row);
1319         }
1320
1321         /*protected */function assignDisplayStatisticsVariables($row, $answers) {
1322                 $choices = $this->getChoices($row);
1323                 $num_choices = count($choices);
1324
1325                 $num_results = 0;
1326                 foreach ($answers as $answer) {
1327                         $num_results += $answer['count'];
1328                 }
1329                                         
1330                 foreach ($answers as $key => $value) {
1331                         $values = explode('|', $key);
1332                         if (count($values) > 1) {
1333                                 for ($i=0; $i<count($values); $i++) {
1334                                         $answers[$values[$i]]['count']++;
1335                                 }
1336                         }
1337                 }
1338
1339                 $this->savant->assign('num_choices', $num_choices);
1340                 $this->savant->assign('num_results', $num_results);
1341                 $this->savant->assign('num_blanks', (int) $answers['-1']['count']);
1342                 $this->savant->assign('answers', $answers);
1343                 $this->savant->assign('row', $row);
1344         }
1345
1346         /*public */function mark($row) { 
1347                 $score = 0;
1348                 $_POST['answers'][$row['question_id']] = intval($_POST['answers'][$row['question_id']]);
1349                 if ($row['answer_' . $_POST['answers'][$row['question_id']]]) {
1350                         $score = $row['weight'];
1351                 } else if ($_POST['answers'][$row['question_id']] == -1) {
1352                         $has_answer = 0;
1353                         for($i=0; $i<10; $i++) {
1354                                 $has_answer += $row['answer_'.$i];
1355                         }
1356                         if (!$has_answer && $row['weight']) {
1357                                 // If MC has no answer and user answered "leave blank"
1358                                 $score = $row['weight'];
1359                         }
1360                 }
1361                 return $score;
1362         }
1363
1364         //QTI Import Multiple Choice Question
1365         function importQTI($_POST){
1366                 global $msg, $db;
1367 //              $_POST = $this->_POST; 
1368                 if ($_POST['question'] == ''){
1369                         $msg->addError(array('EMPTY_FIELDS', _AT('question')));
1370                 }
1371                 
1372                 if (!$msg->containsErrors()) {
1373 //                      $_POST['question']   = $addslashes($_POST['question']);
1374
1375                         for ($i=0; $i<10; $i++) {
1376                                 $_POST['choice'][$i] = trim($_POST['choice'][$i]);
1377                         }
1378
1379                         $answers = array_fill(0, 10, 0);
1380                         if (is_array($_POST['answer'])){
1381                                 $answers[0] = 1;        //default the first to be the right answer. TODO, use summation of points.
1382                         } else {
1383                                 $answers[$_POST['answer']] = 1;
1384                         }
1385                 
1386                         $sql_params = array(    $_POST['category_id'], 
1387                                                                         $_SESSION['course_id'],
1388                                                                         $_POST['feedback'], 
1389                                                                         $_POST['question'], 
1390                                                                         $_POST['choice'][0], 
1391                                                                         $_POST['choice'][1], 
1392                                                                         $_POST['choice'][2], 
1393                                                                         $_POST['choice'][3], 
1394                                                                         $_POST['choice'][4], 
1395                                                                         $_POST['choice'][5], 
1396                                                                         $_POST['choice'][6], 
1397                                                                         $_POST['choice'][7], 
1398                                                                         $_POST['choice'][8], 
1399                                                                         $_POST['choice'][9], 
1400                                                                         $answers[0], 
1401                                                                         $answers[1], 
1402                                                                         $answers[2], 
1403                                                                         $answers[3], 
1404                                                                         $answers[4], 
1405                                                                         $answers[5], 
1406                                                                         $answers[6], 
1407                                                                         $answers[7], 
1408                                                                         $answers[8], 
1409                                                                         $answers[9]);
1410
1411                         $sql = vsprintf(AT_SQL_QUESTION_MULTI, $sql_params);
1412                         $result = mysql_query($sql, $db);
1413                         if ($result==true){
1414                                 return mysql_insert_id();
1415                         }
1416                 }
1417         }
1418 }
1419
1420 /**
1421 * multianswerQuestion
1422 *
1423 */
1424 class MultianswerQuestion extends MultichoiceQuestion {
1425         /*protected */ var $sPrefix  = 'multianswer';
1426         /*protected */ var $sNameVar = 'test_ma';
1427
1428         /*public */function mark($row) { 
1429                 $num_correct = array_sum(array_slice($row, 3));
1430
1431                 if (is_array($_POST['answers'][$row['question_id']]) && count($_POST['answers'][$row['question_id']]) > 1) {
1432                         if (($i = array_search('-1', $_POST['answers'][$row['question_id']])) !== FALSE) {
1433                                 unset($_POST['answers'][$row['question_id']][$i]);
1434                         }
1435                         $num_answer_correct = 0;
1436                         foreach ($_POST['answers'][$row['question_id']] as $item_id => $answer) {
1437                                 if ($row['answer_' . $answer]) {
1438                                         // correct answer
1439                                         $num_answer_correct++;
1440                                 } else {
1441                                         // wrong answer
1442                                         $num_answer_correct--;
1443                                 }
1444                                 $_POST['answers'][$row['question_id']][$item_id] = intval($_POST['answers'][$row['question_id']][$item_id]);
1445                         }
1446                         if ($num_answer_correct == $num_correct) {
1447                                 $score = $row['weight'];
1448                         } else {
1449                                 $score = 0;
1450                         }
1451                         $_POST['answers'][$row['question_id']] = implode('|', $_POST['answers'][$row['question_id']]);
1452                 } else {
1453                         // no answer given
1454                         $_POST['answers'][$row['question_id']] = '-1'; // left blank
1455                         $score = 0;
1456                 }
1457                 return $score;
1458         }
1459
1460         //QTI Import multianswer Question
1461         function importQTI($_POST){
1462                 global $msg, $db;
1463 //              $_POST = $this->_POST; 
1464
1465                 if ($_POST['question'] == ''){
1466                         $msg->addError(array('EMPTY_FIELDS', _AT('question')));
1467                 }
1468
1469                 //Multiple answer can have 0+ answers, in the QTIImport.class, if size(answer) < 2, answer will be came a scalar.
1470                 //The following code will change $_POST[answer] back to a vector.
1471                 $_POST['answer'] = $_POST['answers'];
1472
1473                 if (!$msg->containsErrors()) {
1474                         $choice_new = array(); // stores the non-blank choices
1475                         $answer_new = array(); // stores the associated "answer" for the choices
1476
1477                         foreach ($_POST['choice'] as $choiceNum=>$choiceOpt) {
1478                                 $choiceOpt = validate_length($choiceOpt, 255);
1479                                 $choiceOpt = trim($choiceOpt);
1480                                 $_POST['answer'][$choiceNum] = intval($_POST['answer'][$choiceNum]);
1481                                 if ($choiceOpt == '') {
1482                                         /* an empty option can't be correct */
1483                                         $_POST['answer'][$choiceNum] = 0;
1484                                 } else {
1485                                         /* filter out empty choices/ remove gaps */
1486                                         $choice_new[] = $choiceOpt;
1487                                         if (in_array($choiceNum, $_POST['answer'])){
1488                                                 $answer_new[] = 1;
1489                                         } else {
1490                                                 $answer_new[] = 0;
1491                                         }
1492
1493                                         if ($_POST['answer'][$choiceNum] != 0)
1494                                                 $has_answer = TRUE;
1495                                 }
1496                         }
1497
1498                         if ($has_answer != TRUE) {
1499                 
1500                                 $hidden_vars['required']    = htmlspecialchars($_POST['required']);
1501                                 $hidden_vars['feedback']    = htmlspecialchars($_POST['feedback']);
1502                                 $hidden_vars['question']    = htmlspecialchars($_POST['question']);
1503                                 $hidden_vars['category_id'] = htmlspecialchars($_POST['category_id']);
1504
1505                                 for ($i = 0; $i < count($choice_new); $i++) {
1506                                         $hidden_vars['answer['.$i.']'] = htmlspecialchars($answer_new[$i]);
1507                                         $hidden_vars['choice['.$i.']'] = htmlspecialchars($choice_new[$i]);
1508                                 }
1509
1510                                 $msg->addConfirm('NO_ANSWER', $hidden_vars);
1511                         } else {                        
1512                                 //add slahes throughout - does that fix it?
1513                                 $_POST['answer'] = $answer_new;
1514                                 $_POST['choice'] = $choice_new;
1515                                 $_POST['answer'] = array_pad($_POST['answer'], 10, 0);
1516                                 $_POST['choice'] = array_pad($_POST['choice'], 10, '');
1517                         
1518 //                              $_POST['feedback'] = $addslashes($_POST['feedback']);
1519 //                              $_POST['question'] = $addslashes($_POST['question']);
1520
1521                                 $sql_params = array(    $_POST['category_id'], 
1522                                                                                 $_SESSION['course_id'],
1523                                                                                 $_POST['feedback'], 
1524                                                                                 $_POST['question'], 
1525                                                                                 $_POST['choice'][0], 
1526                                                                                 $_POST['choice'][1], 
1527                                                                                 $_POST['choice'][2], 
1528                                                                                 $_POST['choice'][3], 
1529                                                                                 $_POST['choice'][4], 
1530                                                                                 $_POST['choice'][5], 
1531                                                                                 $_POST['choice'][6], 
1532                                                                                 $_POST['choice'][7], 
1533                                                                                 $_POST['choice'][8], 
1534                                                                                 $_POST['choice'][9], 
1535                                                                                 $_POST['answer'][0], 
1536                                                                                 $_POST['answer'][1], 
1537                                                                                 $_POST['answer'][2], 
1538                                                                                 $_POST['answer'][3], 
1539                                                                                 $_POST['answer'][4], 
1540                                                                                 $_POST['answer'][5], 
1541                                                                                 $_POST['answer'][6], 
1542                                                                                 $_POST['answer'][7], 
1543                                                                                 $_POST['answer'][8], 
1544                                                                                 $_POST['answer'][9]);
1545
1546                                 $sql = vsprintf(AT_SQL_QUESTION_MULTIANSWER, $sql_params);
1547
1548                                 $result = mysql_query($sql, $db);
1549                                 if ($result==true){
1550                                         return mysql_insert_id();
1551                                 }
1552                         }
1553                 }
1554         }
1555
1556 }
1557 ?>