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