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