2 /************************************************************************/
4 /************************************************************************/
5 /* Copyright (c) 2010 */
6 /* Inclusive Design Institute */
8 /* This program is free software. You can redistribute it and/or */
9 /* modify it under the terms of the GNU General Public License */
10 /* as published by the Free Software Foundation. */
11 /************************************************************************/
13 define('TR_INCLUDE_PATH', '../../');
14 require_once(TR_INCLUDE_PATH.'classes/testQuestions.class.php');
15 require_once(TR_INCLUDE_PATH.'classes/QTI/QTIParser.class.php');
16 require_once(TR_INCLUDE_PATH.'classes/DAO/TestsDAO.class.php');
20 * Class for prehandling the POST values before importing each QTI question into ATutor
21 * Some definitions for the QTI question type: ///
29 * 8 Graphical Matching
34 var $qti_params = array();
35 var $qid = array(); //store the question_id that is generated by this import
36 var $import_path = '';
38 var $weights = array();
41 function QTIImport($import_path){
42 $this->import_path = $import_path;
45 //Creates the parameters array for TestQuestion::importQTI
46 function constructParams($qti_params){
49 $qti_params['required'] = intval($qti_params['required']);
50 $qti_params['question'] = trim($qti_params['question']);
51 $qti_params['category_id'] = intval($qti_params['category_id']);
52 $qti_params['feedback'] = trim($qti_params['feedback']);
55 if (sizeof($qti_params['answers']) > 1){
56 $qti_params['answer'] = $qti_params['answers'];
57 } elseif (sizeof($qti_params['answers'])==1) {
58 $qti_params['answer'] = intval($qti_params['answers'][0]);
60 $this->qti_params = $qti_params;
63 //Decide which question type to import based in the integer
64 function getQuestionType($question_type){
65 $qti_obj = TestQuestions::getQuestion($question_type);
66 if ($qti_obj != null){
67 $qid = $qti_obj->importQTI($this->qti_params);
76 * This function will add the attributes that are extracted from the qti xml
79 * @param array attributes that are extracted from the QTI XML.
80 * @return int the question ids.
82 function importQuestions($attributes){
83 global $supported_media_type, $msg;
86 foreach($attributes as $resource=>$attrs){
87 if (preg_match('/imsqti\_(.*)/', $attrs['type'])){
88 //Instantiate class obj
89 $xml = new QTIParser($attrs['type']);
90 $xml_content = @file_get_contents($this->import_path . $attrs['href']);
91 $xml->setRelativePath($package_base_name);
93 if (!$xml->parse($xml_content)){
94 $msg->addError('QTI_WRONG_PACKAGE');
99 $this->title = $xml->title;
101 //if ($attrs[href] =='56B1BEDC-A820-7AA8-A21D-F32017189445/56B1BEDC-A820-7AA8-A21D-F32017189445.xml'){
102 // debug($xml, 'attributes');
104 //import file, should we use file href? or jsut this href?
105 //Aug 25, use both, so then it can check for respondus media as well.
106 foreach($attrs['file'] as $file_id => $file_name){
107 $file_pathinfo = pathinfo($file_name);
108 if ($file_pathinfo['basename'] == $attrs['href']){
109 //This file will be parsed later
113 if (in_array(strtolower($file_pathinfo['extension']), $supported_media_type)){
115 $this->copyMedia(array($file_name), $xml->items);
119 for ($loopcounter=0; $loopcounter<$xml->item_num; $loopcounter++){
120 //Create POST values.
121 unset($test_obj); //clear cache
122 $test_obj['required'] = 1;
123 $test_obj['preset_num'] = 0;
124 $test_obj['category_id'] = 0;
125 $test_obj['question'] = $xml->question[$loopcounter];
126 $test_obj['feedback'] = $xml->feedback[$loopcounter];
127 $test_obj['groups'] = $xml->groups[$loopcounter];
128 $test_obj['property'] = intval($xml->attributes[$loopcounter]['render_fib']['property']);
129 $test_obj['choice'] = array();
130 $test_obj['answers'] = array();
136 if (is_array($xml->answers[$loopcounter])){
137 array_walk($xml->answers[$loopcounter], 'trim_value');
139 //TODO: The groups is 1-0+ choices. So we should loop thru groups, not choices.
140 if (is_array($xml->choices[$loopcounter])){
141 foreach ($xml->choices[$loopcounter] as $choiceNum=>$choiceOpt){
142 if (sizeof($test_obj['groups'] )>0 && is_array($xml->answers[$loopcounter])) {
143 if (!empty($xml->answers[$loopcounter])){
144 foreach ($xml->answers[$loopcounter] as $ansNum=>$ansOpt){
145 if ($choiceNum == $ansOpt){
146 //Not exactly efficient, worst case N^2
147 $test_obj['answers'][$ansNum] = $i;
153 if (is_array($xml->answers[$loopcounter]) && in_array($choiceNum, $xml->answers[$loopcounter])){
154 $test_obj['answers'][] = $i;
157 $test_obj['choice'][] = $choiceOpt;
162 // unset($qti_import);
163 $this->constructParams($test_obj);
164 //debug($xml->getQuestionType($loopcounter), 'lp_'.$loopcounter);
166 $this->getQuestionType($xml->getQuestionType($loopcounter));
169 $qids[] = $this->qid;
171 //Dependency handling
172 if (!empty($attrs['dependency'])){
173 $xml_items = array_merge($xml_items, $xml->items);
178 if ($xml->title != ''){
179 $this->title = $xml->title;
182 //assign marks/weights
183 $this->weights = $xml->weights;
186 } elseif ($attrs['type'] == 'webcontent') {
187 //webcontent, copy it over.
188 $this->copyMedia($attrs['file'], $xml_items);
191 //debug($qids, 'qids');
196 * This function is to import a test and returns the test id.
197 * @param string custmom test title
199 * @return int test id
201 function importTest($title='') {
204 $title = ($title=='')?$this->title:$title;
206 $testsDAO = new TestsDAO();
207 $tid = $testsDAO->Create($_course_id,
209 $test_obj['description']);
211 // $sql_params = array ( $_SESSION['course_id'],
212 // $test_obj['title'],
213 // $test_obj['description'],
214 // $test_obj['format'],
217 // $test_obj['order'],
218 // $test_obj['num_questions'],
219 // $test_obj['instructions'],
220 // $test_obj['content_id'],
221 // $test_obj['passscore'],
222 // $test_obj['passpercent'],
223 // $test_obj['passfeedback'],
224 // $test_obj['failfeedback'],
225 // $test_obj['result_release'],
226 // $test_obj['random'],
227 // $test_obj['difficulty'],
228 // $test_obj['num_takes'],
229 // $test_obj['anonymous'],
231 // $test_obj['allow_guests'],
232 // $test_obj['display']);
234 // $sql = vsprintf(AT_SQL_TEST, $sql_params);
235 // $result = mysql_query($sql, $db);
236 // $tid = mysql_insert_id($db);
237 //debug($qti_import->weights, 'weights');
243 * Match the XML files to the actual files found in the content, then copy the media
244 * over to the content folder based on the actual links. *The XML file names might not be right.
245 * @param array The list of file names provided by the manifest's resources
246 * @param array The list of relative files that is used in the question contents. Default empty.
248 function copyMedia($files, $xml_items = array()){
250 foreach($files as $file_num => $file_loc){
251 //skip test xml files
252 if (preg_match('/tests\_[0-9]+\.xml/', $file_loc)){
259 Use the items list to handle and check which path it is from, so then it won't blindly truncate 'resource/' from the path
260 - For any x in xml_files, any y in new_file_loc, any k in the set of strings; such that k concat x = y, then use y, else use x
261 - BUG: Same filename fails. If resource/folder1/file1.jpg, and resource/file1.jpg, both will get replaced with file1.jpg
263 if(!empty($xml_items)){
264 foreach ($xml_items as $xk=>$xv){
265 if (($pos = strpos($file_loc, $xv))!==false){
266 //address the bug mentioned aboe.
267 //check if there is just one level of directory in this extra path.
268 //based on the assumption that most installation are just using 'resources/' or '{FOLDER_NAME}/'
269 $shortened = substr($file_loc, 0, $pos);
270 $num_of_occurrences = explode('/', $shortened);
271 if (sizeof($num_of_occurrences) == 2){
279 if ($new_file_loc==''){
280 $new_file_loc = $file_loc;
283 //Check if the file_loc has been changed, if not, don't move it, let ims class to handle it
284 //we only want to touch the files that the test/surveys use
285 if ($new_file_loc!=$file_loc){
286 //check if new folder is there, if not, create it.
287 createDir(TR_CONTENT_DIR .$_SESSION['course_id'].'/'.$new_file_loc );
290 if (file_exists(TR_CONTENT_DIR .$_SESSION['course_id'].'/'.$new_file_loc)){
291 unlink(TR_CONTENT_DIR .$_SESSION['course_id'].'/'.$new_file_loc);
293 if (file_exists($this->import_path.$file_loc)){
294 if (copy($this->import_path.$file_loc,
295 TR_CONTENT_DIR .$_SESSION['course_id'].'/'.$new_file_loc) === false) {
296 //TODO: Print out file already exist error.
297 if (!$msg->containsErrors()) {
298 // $msg->addError('FILE_EXISTED');