http://atutor.ca/atutor/mantis/view.php?id=4506
[acontent.git] / docs / home / ims / ims_import.php
1 <?php
2 /************************************************************************/
3 /* AContent                                                             */
4 /************************************************************************/
5 /* Copyright (c) 2010                                                   */
6 /* Inclusive Design Institute                                           */
7 /*                                                                      */
8 /* This program is free software. You can redistribute it and/or        */
9 /* modify it under the terms of the GNU General Public License          */
10 /* as published by the Free Software Foundation.                        */
11 /************************************************************************/
12
13 /** Commented by Cindy Li on Feb 2, 2010
14  * Modified from ATutor mods/_core/imscp/ims_import.php, SVN revision 9126
15  */
16
17 define('TR_INCLUDE_PATH', '../../include/');
18
19 // Validate OAuth token and set $SESSION['user_id']
20 // Must come before require(vitals.inc.php) because vitals redirects to index page 
21 // when $SESSION['user_id'] is not set
22 $oauth_import = false;  // whether the import request is from oauth web service
23
24 // By default, enable the import of associated tests and a4a objects
25
26 if (!isset($_POST['allow_test_import'])) $_POST['allow_test_import'] = 1;
27 if (!isset($_POST['allow_a4a_import'])) $_POST['allow_a4a_import'] = 1;
28
29 // the import request is from oauth web service, find the user id from the given token
30 if (isset($_GET['oauth_token']))
31 {
32         require_once(TR_INCLUDE_PATH.'config.inc.php');
33         require_once(TR_INCLUDE_PATH.'constants.inc.php');
34         
35         if ($_GET['oauth_token'] == '')
36         {
37                 echo "error=".urlencode('Empty OAuth token');
38                 exit;
39         }
40         else
41         {
42                 $oauth_import = true;
43                 require_once(TR_INCLUDE_PATH.'classes/DAO/OAuthServerTokensDAO.class.php');
44                 $oAuthServerTokensDAO = new OAuthServerTokensDAO();
45                 $token_row = $oAuthServerTokensDAO->getByTokenAndType($_GET['oauth_token'], 'access');
46
47                 if (!is_array($token_row))
48                 {
49                         echo "error=".urlencode('Invalid OAuth token');
50                         exit;
51                 }
52                 else if ($oAuthServerTokensDAO->isTokenExpired($_GET['oauth_token']))
53                 {
54                         echo "error=".urlencode('OAuth token expired');
55                         exit;
56                 }
57                 
58                 $_user_id = $token_row[0]['user_id'];
59         }
60 }
61
62 require(TR_INCLUDE_PATH.'vitals.inc.php');
63
64 require_once(TR_INCLUDE_PATH.'classes/Utility.class.php');
65 require_once(TR_INCLUDE_PATH.'../home/classes/ContentUtility.class.php');
66 require_once(TR_INCLUDE_PATH.'classes/DAO/UsersDAO.class.php');
67 require_once(TR_INCLUDE_PATH.'classes/DAO/CoursesDAO.class.php');
68 require_once(TR_INCLUDE_PATH.'classes/DAO/UserCoursesDAO.class.php');
69 require_once(TR_INCLUDE_PATH.'classes/DAO/ContentDAO.class.php');
70 require_once(TR_INCLUDE_PATH.'classes/DAO/TestsQuestionsAssocDAO.class.php');
71 require_once(TR_INCLUDE_PATH.'classes/DAO/ContentTestsAssocDAO.class.php');
72 require_once(TR_INCLUDE_PATH.'classes/FileUtility.class.php'); /* for clr_dir() and preImportCallBack and dirsize() */
73
74 require_once(TR_INCLUDE_PATH.'lib/pclzip.lib.php');
75 require_once(TR_INCLUDE_PATH.'lib/pclzip_callback.lib.php');
76 require_once(TR_INCLUDE_PATH.'lib/qti.inc.php'); 
77 //require(TR_INCLUDE_PATH.'classes/QTI/QTIParser.class.php');   
78 require_once(TR_INCLUDE_PATH.'classes/QTI/QTIImport.class.php');
79 require_once(TR_INCLUDE_PATH.'classes/A4a/A4aImport.class.php');
80 require(TR_INCLUDE_PATH.'../home/ims/ns.inc.php');      //namespace, no longer needs, delete it after it's stable.
81 require_once(TR_INCLUDE_PATH.'classes/Weblinks/WeblinksParser.class.php');
82 require(TR_INCLUDE_PATH.'classes/DiscussionTools/DiscussionToolsParser.class.php');
83 require(TR_INCLUDE_PATH.'classes/DiscussionTools/DiscussionToolsImport.class.php');
84
85 // make sure the user has author privilege
86 Utility::authenticate(TR_PRIV_ISAUTHOR);
87
88 /* to avoid timing out on large files */
89 @set_time_limit(0);
90 $_SESSION['done'] = 1;
91
92 $html_head_tags = array("style", "script", "link");
93
94 $package_base_path = '';
95 $package_real_base_path = '';   //the path to save the contents
96 $all_package_base_path = array();
97 $xml_base_path = '';
98 $element_path = array();
99 $imported_glossary = array();
100 $character_data = '';
101 $test_message = '';
102 $test_title = '';
103 $content_type = '';
104 $skip_ims_validation = false;
105 $added_dt = array();    //the mapping of discussion tools that are added
106 $avail_dt = array();    //list of discussion tools that have not been handled
107
108 function check_available_size($course_id)
109 {
110         global $coursesDAO, $MaxCourseSize, $import_path, $msg, $oauth_import;
111
112         $q_row = $coursesDAO->get($course_id);
113         
114         //$sql  = "SELECT max_quota FROM ".TABLE_PREFIX."courses WHERE course_id=$_SESSION[course_id]";
115         //$result = mysql_query($sql, $db);
116         //$q_row        = mysql_fetch_assoc($result);
117         
118         if ($q_row['max_quota'] == TR_COURSESIZE_UNLIMITED) return;
119         else $zip_size_limit = $MaxCourseSize;
120
121         $totalBytes   = FileUtility::dirsize($import_path);
122         
123         $total_after  = $zip_size_limit - $totalBytes;
124         
125         if (is_dir(TR_CONTENT_DIR . $course_id.'/')) 
126         {
127                 $course_total = FileUtility::dirsize(TR_CONTENT_DIR . $course_id.'/');
128                 $total_after  -= $course_total;
129         }
130         
131         if ($total_after < 0) {
132                 /* remove the content dir, since there's no space for it */
133                 $errors = array('NO_CONTENT_SPACE', number_format(-1*($total_after/TR_KBYTE_SIZE), 2 ) );
134                 $msg->addError($errors);
135                 
136                 // Clean up import path and inserted course row
137                 FileUtility::clr_dir($import_path);
138                 $coursesDAO->Delete($course_id);
139
140                 if (isset($_GET['tile'])) {
141                         header('Location: '.$_base_path.'tools/tile/index.php');
142                 } 
143                 else if ($oauth_import) {
144                         echo "error=".urlencode('No space for the content.');
145                 }
146                 else {
147                         header('Location: '.$_SERVER['HTTP_REFERER']);
148                 }
149                 exit;
150         }
151 }
152
153 /*
154  * return the error messages represented by the given array 
155  * @author      Mike A.
156  * @ref         http://ca3.php.net/manual/en/domdocument.schemavalidate.php
157  */
158 function libxml_display_error($error)
159 {
160     $return = "<br/>\n";
161     switch ($error->level) {
162         case LIBXML_ERR_WARNING:
163             $return .= "<b>Warning $error->code</b>: ";
164             break;
165         case LIBXML_ERR_ERROR:
166             $return .= "<b>Error $error->code</b>: ";
167             break;
168         case LIBXML_ERR_FATAL:
169             $return .= "<b>Fatal Error $error->code</b>: ";
170             break;
171     }
172     $return .= trim($error->message);
173     if ($error->file) {
174         $return .=    " in <b>$error->file</b>";
175     }
176     $return .= " on line <b>$error->line</b>\n";
177
178     return $return;
179 }
180
181 /**
182  * Validate all the XML in the package, including checking XSDs, missing data.
183  * @param       string          the path of the directory that contains all the package files
184  * @return      boolean         true if every file exists in the manifest, false if any is missing.
185  */
186 function checkResources($import_path){
187         global $items, $msg, $skip_ims_validation, $avail_dt;
188
189         if (!is_dir($import_path)){
190                 return;
191         }
192
193         //if the package has access for all content, skip validation for now. 
194         //todo: import the XSD into our validator
195         if ($skip_ims_validation){
196                 return true;
197         }
198
199         //generate a file tree
200         $data = rscandir($import_path);
201
202         //check if every file is presented in the manifest
203         foreach($data as $filepath){
204                 $filepath = substr($filepath, strlen($import_path));
205
206                 //validate xml via its xsd/dtds
207                 if (preg_match('/(.*)\.xml/', $filepath)){
208                         libxml_use_internal_errors(true);
209                         $dom = new DOMDocument();
210                         $dom->load(realpath($import_path.$filepath));
211                         if (!@$dom->schemaValidate('main.xsd')){
212                                 $errors = libxml_get_errors();
213                                 foreach ($errors as $error) {
214                                         //suppress warnings
215                                         if ($error->level==LIBXML_ERR_WARNING){
216                                                 continue;
217                                         }
218                                         $msg->addError(array('IMPORT_CARTRIDGE_FAILED', libxml_display_error($error)));
219                                 }
220                                 libxml_clear_errors();
221                         }
222                         //if this is the manifest file, we do not have to check for its existance.
223 //                      if (preg_match('/(.*)imsmanifest\.xml/', $filepath)){
224 //                              continue;
225 //                      }
226                 }
227         }
228
229         //Create an array that mimics the structure of the data array, based on the xml items
230         $filearray = array();
231         foreach($items as $name=>$fileinfo){
232                 if(isset($fileinfo['file']) && is_array($fileinfo['file']) && !empty($fileinfo['file'])){
233                         foreach($fileinfo['file'] as $fn){
234                                 if (!in_array(realpath($import_path.$fn), $filearray)){
235                                         //if url, skip
236                                         if (preg_match('/^http[s]?\:/', $fn) == 0){
237                                                 $filearray[] = realpath($import_path. $fn);
238                                         }                                       
239                                 }
240                         }
241                 }
242
243                 //validate the xml by its schema
244                 if (preg_match('/imsqti\_(.*)/', $fileinfo['type'])){
245                         $qti = new QTIParser($fileinfo['type']);
246                         $xml_content = @file_get_contents($import_path . $fileinfo['href']);
247                         $qti->parse($xml_content); //will add error to $msg if failed                   
248                 } 
249
250                 //add all dependent discussion tools to a list
251                 if(isset($fileinfo['dependency']) && !empty($fileinfo['dependency'])){
252                         $avail_dt = array_merge($avail_dt, $fileinfo['dependency']);
253                 }
254         }
255
256         //check if all files in the xml is presented in the archieve
257         $result = array_diff($filearray, $data);
258         //using sizeof because array_diff only 
259         //returns an array containing all the entries from array1  that are not present in any of the 
260         //other arrays. 
261         //Using sizeof make sure it's not a subset of array2.
262         //-1 on data because it always contain the imsmanifest.xml file
263         if (!$skip_ims_validation){
264                 if (!empty($result) || sizeof($data)-1>sizeof($filearray)){
265                         $msg->addError(array('IMPORT_CARTRIDGE_FAILED', _AT('ims_missing_references')));
266                 }
267         }
268         return true;
269 }
270
271 /*
272  * @example rscandir(dirname(__FILE__).'/'));
273  * @param string $base
274  * @param array $omit
275  * @param array $data
276  * @return array
277  */
278 function rscandir($base='', &$data=array()) {
279   $array = array_diff(scandir($base), array('.', '..')); # remove ' and .. from the array */
280   foreach($array as $value) : /* loop through the array at the level of the supplied $base */
281  
282     if (is_dir($base.$value)) : /* if this is a directory */
283 //        don't save the directory name
284 //        $data[] = $base.$value.'/'; /* add it to the $data array */
285       $data = rscandir($base.$value.'/', $data); /* then make a recursive call with the
286       current $value as the $base supplying the $data array to carry into the recursion */
287      
288     elseif (is_file($base.$value)) : /* else if the current $value is a file */
289       $data[] = realpath($base.$value); /* just add the current $value to the $data array */
290      
291     endif;
292    
293   endforeach;
294   return $data; // return the $data array
295  
296 }
297
298 /**
299  * Function to restructure the $items.  So that old import will merge the top page into its children, and
300  * create a new folder on top of it
301  */
302 function rehash($items){
303         global $order;
304         $parent_page_maps = array();    //old=>new
305         $temp_popped_items = array();
306         $rehashed_items = array();      //the reconstructed array
307         foreach($items as $id => $content){
308                 $parent_obj = $items[$content['parent_content_id']];
309                 $rehashed_items[$id] = $content;        //copy
310         //first check if this is the top folder of the archieve, we don't want the top folder, remove it.
311 /*        if (isset($content['parent_content_id']) && !isset($parent_obj) && !isset($content['type'])){
312             //if we can get into here, it means the parent_content_id of this is empty
313             //implying this is the first folder.
314             //note: it checks content[type] cause it could be a webcontent. In that case, 
315             //      we do want to keep it.  
316                         debug($content, 'hit');
317             unset($rehashed_items[$id]);
318             continue;
319         }               
320                 //then check if there exists a mapping for this item, if so, simply replace is and next.
321                 else
322 */              if (isset($parent_page_maps[$content['parent_content_id']])){
323                         $rehashed_items [$id]['parent_content_id'] = $parent_page_maps[$content['parent_content_id']];
324                         $rehashed_items [$id]['ordering']++;
325                 } 
326                 //If its parent page is a top page and have an identiferref
327                 elseif (isset($parent_obj) && isset($parent_obj['href'])){                      
328                         if (!isset($parent_obj['href'])){
329                                 //check if this top page is already a folder, if so, next.
330                                 continue;
331                         }
332                         //else, make its parent page to a folder
333                         $new_item['title'] = $parent_obj['title'];
334                         //check if this parent has been modified, if so, chnage it
335                         if (isset($parent_page_maps[$parent_obj['parent_content_id']])){
336                             $new_item['parent_content_id'] = $parent_page_maps[$parent_obj['parent_content_id']];
337                         } else {
338                         $new_item['parent_content_id'] = $parent_obj['parent_content_id'];
339             }
340                         //all ordering needs to be +1 because we are creating a new folder on top of
341                         //everything, except the first page.
342                         $new_item['ordering'] = $parent_obj['ordering'];
343                         if ($new_item['parent_content_id']!='0'){
344                                 $new_item['ordering']++;
345                         } 
346
347                 //assign this new parent folder to the pending items array
348                         $new_item_name = $content['parent_content_id'].'_FOLDER';
349                         //a not so brilliant way to append the folder in its appropriate position
350                         $reordered_hashed_items = array();  //use to store the new rehashed item with the correct item order
351                         foreach($rehashed_items as $rh_id=>$rh_content){
352                             if ($rh_id == $content['parent_content_id']){
353                                 //add the folder in before the parent subpage.
354                                 $reordered_hashed_items[$new_item_name] = $new_item;
355                             }
356                             $reordered_hashed_items[$rh_id] = $rh_content;  //clone
357                         }
358                         $rehashed_items = $reordered_hashed_items;  //replace it back
359                         unset($reordered_hashed_items);
360                         $parent_page_maps[$content['parent_content_id']] = $new_item_name;  //save this page on the hash map
361
362                         //reconstruct the parent
363                         $rehashed_items[$content['parent_content_id']]['parent_content_id'] = $parent_page_maps[$content['parent_content_id']];
364                         $rehashed_items[$content['parent_content_id']]['ordering'] = 0; //always the first one.
365
366                         //reconstruct itself
367                         $rehashed_items[$id]['parent_content_id'] = $parent_page_maps[$content['parent_content_id']];
368                         $rehashed_items[$id]['ordering']++;
369
370                 }
371         }
372         return $rehashed_items;
373 }
374
375 /**
376  * Take out the common path within all $items['new_path'].
377  * This allows import/export repeatedly without duplicating its path
378  * @param   array   contains the breakdown of all resources in the XML
379  */
380 function removeCommonPath($items){
381     $common_path; 
382     $quit = false;  //a flag that is set if it's not the first time being run.
383
384     $filearray = array();
385     //get all files listed in the manifest
386     foreach($items as $name=>$fileinfo){
387                 if(isset($fileinfo['file']) && is_array($fileinfo['file']) && !empty($fileinfo['file'])){
388                         foreach($fileinfo['file'] as $fn){
389                                 if (!in_array($fn, $filearray)){
390                                         if (preg_match('/^http[s]?\:/', $fn) == 0){
391                                                 $filearray[] = $fn;
392                                         }                                       
393                                 }
394                         }
395                 }
396         }
397
398     foreach($filearray as $index=>$path){
399         //hack
400         //check if this is a XML file; if so, skip through, 
401         //cause XML most likely isn't a content resource.
402         $ext = substr($path, (strrpos($path, '.')+1));
403         if($ext=='xml'){
404             continue;
405         }
406         
407         //if common path is empty, assign the first path to it.
408         if ($common_path=='' && $quit==false){
409             $common_path = $path;
410             $quit = true;   //the next time common_path is empty, quit;
411             continue;
412         }
413         //we use '/' here instead of DIRECTORY_SEPARATOR because php would
414         //actually use '\' and return the whole string. 
415         $common_array = explode('/', $common_path);
416         $path_array = explode('/', $path);
417         $intersect_array = array_intersect($common_array, $path_array);
418         $common_path = implode('/', $intersect_array);       
419     }
420     return $common_path;
421 }
422
423
424 /** 
425  * This function will take the test accessment XML and add these to the database.
426  * @param       string  The path of the XML, without the import_path.
427  * @param       mixed   An item singleton.  Contains the info of this item, namely, the accessment details.
428  *                                      The item must be an object created by the ims class.
429  * @param       string  the import path
430  * @return      mixed   An Array that contains all the question IDs that have been imported.
431  */
432  function addQuestions($xml, $item, $import_path){
433         global $test_title;
434         $qti_import = new QTIImport($import_path);
435         $tests_xml = $import_path.$xml;
436         
437         //Mimic the array for now.
438         $test_attributes['resource']['href'] = $item['href'];
439         $test_attributes['resource']['type'] = preg_match('/imsqti_xmlv1p2/', $item['type'])==1?'imsqti_xmlv1p2':'imsqti_xmlv1p1';
440         $test_attributes['resource']['file'] = $item['file'];
441
442         //Get the XML file out and start importing them into our database.
443         //TODO: See question_import.php 287-289.
444         $qids = $qti_import->importQuestions($test_attributes);
445         $test_title = $qti_import->title;
446
447         return $qids;
448  }
449
450
451         /* called at the start of en element */
452         /* builds the $path array which is the path from the root to the current element */
453         function startElement($parser, $name, $attrs) {
454                 global $items, $path, $package_base_path, $all_package_base_path, $package_real_base_path;
455                 global $element_path, $import_path, $skip_ims_validation;
456                 global $xml_base_path, $test_message, $content_type;
457                 global $current_identifier, $msg, $ns, $ns_cp;
458                 global $course_primary_lang;
459                 
460                 //check if the xml is valid
461 /*
462                 if(isset($attrs['xsi:schemaLocation']) && $name == 'manifest'){
463                         //run the loop and check it thru the ns.inc.php
464                 } elseif ($name == 'manifest' && !isset($attrs['xsi:schemaLocation'])) {
465                         //$msg->addError('MANIFEST_NOT_WELLFORM: NO NAMESPACE');
466                         $msg->addError('IMPORT_CARTRIDGE_FAILED');
467                 } else {
468                         //error
469                 }
470                 //error if the tag names are wrong
471                 if (preg_match('/^xsi\:/', $name) >= 1){
472                         //$msg->addError('MANIFEST_NOT_WELLFORM');
473                         $msg->addError('IMPORT_CARTRIDGE_FAILED');
474                 }
475 */
476
477                 // get language from CONTENT PACKAGE
478                 if (substr($element_path[count($element_path)-1], -6) == ':title' && substr($name, -11) == ':langstring') {
479                         $course_primary_lang = trim($attrs['xml:lang']);
480                 }
481                 
482                 //validate namespaces
483                 if(!$skip_ims_validation && isset($attrs['xsi:schemaLocation']) && $name=='manifest'){
484                         $schema_location = array();
485                         $split_location = preg_split('/[\r\n\s]+/', trim($attrs['xsi:schemaLocation']));
486
487                         //check if the namespace is actually right, have an array or some sort in IMS class
488                         if(sizeof($split_location)%2==1){
489                                 //schema is not in the form of "The first URI reference in each pair is a namespace name,
490                                 //and the second is the location of a schema that describes that namespace."
491                                 //$msg->addError('MANIFEST_NOT_WELLFORM');
492                                 $msg->addError(array('IMPORT_CARTRIDGE_FAILED', _AT('schema_error')));
493                         }
494
495                         //turn the xsi:schemaLocation URI into a schema that describe namespace.
496                         //name = url
497                         //http://msdn.microsoft.com/en-us/library/ms256100(VS.85).aspx
498                         //http://www.w3.org/TR/xmlschema-1/
499                         for($i=0; $i < sizeof($split_location);$i=$i+2){
500                                 /*
501                                 if (isset($ns[$split_location[$i]]) && $ns[$split_location[$i]] != $split_location[$i+1]){
502                                         //$msg->addError('MANIFEST_NOT_WELLFORM: SCHEMA');
503                                         $msg->addError('IMPORT_CARTRIDGE_FAILED');
504                                 }
505                                 */
506                                 //if the key of the namespace is not defined. Throw error.
507                                 if(!isset($ns[$split_location[$i]]) && !isset($ns_cp[$split_location[$i]])){
508                                         $msg->addError(array('IMPORT_CARTRIDGE_FAILED', _AT('schema_error')));
509                                 }
510                         }
511                 } else {
512                         //throw error           
513                 }
514
515                 if ($name == 'manifest' && isset($attrs['xml:base']) && $attrs['xml:base']) {
516                         $xml_base_path = $attrs['xml:base'];
517                 } else if ($name == 'file') {
518                         // check if it misses file references
519                         if(!$skip_ims_validation && (!isset($attrs['href']) || $attrs['href']=='')){
520                                 //$msg->addError('MANIFEST_NOT_WELLFORM');
521                                 $msg->addError(array('IMPORT_CARTRIDGE_FAILED', _AT('ims_missing_references')));
522                         }
523
524                         // special case for webCT content packages that don't specify the `href` attribute 
525                         // with the `<resource>` element.
526                         // we take the `href` from the first `<file>` element.
527                         if (isset($items[$current_identifier]) && ($items[$current_identifier]['href'] == '')) {
528                                 $attrs['href'] = urldecode($attrs['href']);
529                                 $items[$current_identifier]['href'] = $attrs['href'];
530                         }
531
532                         $temp_path = pathinfo($attrs['href']);
533                         $temp_path = explode('/', $temp_path['dirname']);
534                         if (empty($package_base_path)){
535                             $package_base_path = $temp_path;
536             }
537                         if ($all_package_base_path!='' && empty($all_package_base_path)){
538                                 $all_package_base_path = $temp_path;
539                         }
540                         $package_base_path = array_intersect_assoc($package_base_path, $temp_path);
541                         
542                         //calculate the depths of relative paths
543                         if ($all_package_base_path!=''){
544                                 $no_relative_temp_path = $temp_path;
545                                 foreach($no_relative_temp_path as $path_node){
546                                         if ($path_node=='..'){
547                                                 array_pop($no_relative_temp_path);
548                                                 array_pop($no_relative_temp_path); //not a typo, have to pop twice, both itself('..'), and the one before.
549                                         }
550                                 }
551                                 $all_package_base_path = array_intersect_assoc($all_package_base_path, $no_relative_temp_path);
552                                 if (empty($all_package_base_path)){
553                                         $all_package_base_path = '';    //unset it, there is no intersection.
554                                 }
555                         }
556
557                         //save the actual content base path
558                         if (in_array('..', $temp_path)){
559                                 $sizeofrp = array_count_values($temp_path);
560                         }
561
562                         //for IMSCC, assume that all resources lies in the same folder, except styles.css
563                         if ($items[$current_identifier]['type']=='webcontent' || $items[$current_identifier]['type']=='imsdt_xmlv1p0'){
564                                 //find the intersection of each item's related files, then that intersection is the content_path
565                                 if (isset($items[$current_identifier]['file'])){
566                                         foreach ($items[$current_identifier]['file'] as $resource_path){
567                                                 $temp_path = pathinfo($resource_path);
568                                                 $temp_path = explode('/', $temp_path['dirname']);
569                                                 $package_base_path = array_intersect_assoc($package_base_path, $temp_path);                                             
570                                         }
571                                 }
572                         }
573
574                         //real content path
575                         if($sizeofrp['..'] > 0 && !empty($all_package_base_path)){
576                                 for ($i=0; $i<$sizeofrp['..']; $i++){
577                                         array_pop($all_package_base_path);
578                                 }
579                         }
580                         if (count($package_base_path) > 0) {
581                                 $items[$current_identifier]['new_path'] = implode('/', $package_base_path);
582                         }       
583 /* 
584  * @harris, reworked the package_base_path 
585                                 if ($package_base_path=="") {
586                                         $package_base_path = $temp_path;
587                                 } 
588                                 elseif (is_array($package_base_path) && $content_type != 'IMS Common Cartridge') {
589                                         //if this is a content package, we want only intersection
590                                         $package_base_path = array_intersect($package_base_path, $temp_path);
591                                         $temp_path = $package_base_path;
592                                 }
593                                 //added these 2 lines in so that pictures would load.  making the elseif above redundant.
594                                 //if there is a bug for pictures not load, then it's the next 2 lines.
595                                 $package_base_path = array_intersect($package_base_path, $temp_path);
596                                 $temp_path = $package_base_path;
597                         }
598                         $items[$current_identifier]['new_path'] = implode('/', $temp_path);     
599 */
600                         if (isset($_POST['allow_test_import']) && isset($items[$current_identifier]) 
601                                                 && preg_match('/((.*)\/)*tests\_[0-9]+\.xml$/', $attrs['href'])) {
602                                 $items[$current_identifier]['tests'][] = $attrs['href'];
603                         } 
604                         if (isset($_POST['allow_a4a_import']) && isset($items[$current_identifier])) {
605                                 $items[$current_identifier]['a4a_import_enabled'] = true;
606                         }
607                 } else if (($name == 'item') && ($attrs['identifierref'] != '')) {
608                         $path[] = $attrs['identifierref'];
609                 } else if (($name == 'item') && ($attrs['identifier'])) {
610                         $path[] = $attrs['identifier'];
611 //              } else if (($name == 'resource') && is_array($items[$attrs['identifier']]))  {
612                 } else if (($name == 'resource')) {
613                         $current_identifier = $attrs['identifier'];
614                         $items[$current_identifier]['type'] = $attrs['type'];
615                         if ($attrs['href']) {
616                                 $attrs['href'] = urldecode($attrs['href']);
617
618                                 $items[$attrs['identifier']]['href'] = $attrs['href'];
619
620                                 // href points to a remote url
621                                 if (preg_match('/^http.*:\/\//', trim($attrs['href'])))
622                                         $items[$attrs['identifier']]['new_path'] = '';
623                                 else // href points to local file
624                                 {
625                                         $temp_path = pathinfo($attrs['href']);
626                                         $temp_path = explode('/', $temp_path['dirname']);
627 //                                      if (empty($package_base_path)) {
628                                                 $package_base_path = $temp_path;
629 //                                      } 
630 //                                      else {
631 //                                              $package_base_path = array_intersect($package_base_path, $temp_path);
632 //                                      }
633                                         $items[$attrs['identifier']]['new_path'] = implode('/', $temp_path);
634                                 }
635                         }
636
637                         //if test custom message has not been saved
638 //                      if (!isset($items[$current_identifier]['test_message'])){
639 //                              $items[$current_identifier]['test_message'] = $test_message;
640 //                      }
641                 } else if ($name=='dependency' && $attrs['identifierref']!='') {
642                         //if there is a dependency, attach it to the item array['file']
643                         $items[$current_identifier]['dependency'][] = $attrs['identifierref'];
644                 }
645                 if (($name == 'item') && ($attrs['parameters'] != '')) {
646                         $items[$attrs['identifierref']]['test_message'] = $attrs['parameters'];
647                 }
648                 if ($name=='file'){
649                         if(!isset($items[$current_identifier]) && $attrs['href']!=''){
650                                 $items[$current_identifier]['href']      = $attrs['href'];
651                         }
652                         if (substr($attrs['href'], 0, 7) == 'http://' || substr($attrs['href'], 0, 8) == 'https://' || file_exists($import_path.$attrs['href']) || $skip_ims_validation){
653                                 $items[$current_identifier]['file'][] = $attrs['href'];
654                         } else {
655                                 //$msg->addError('');
656                                 $msg->addError(array('IMPORT_CARTRIDGE_FAILED', _AT(array('ims_files_missing', $attrs['href']))));
657                         }
658                 }               
659                 if ($name=='cc:authorizations'){
660                         //don't have authorization setup.
661                         //$msg->addError('');
662                         $msg->addError('IMS_AUTHORIZATION_NOT_SUPPORT');
663                 }
664                 array_push($element_path, $name);
665         }
666
667         /* called when an element ends */
668         /* removed the current element from the $path */
669         function endElement($parser, $name) {
670                 global $path, $element_path, $my_data, $items, $oauth_import;
671                 global $current_identifier, $skip_ims_validation;
672                 global $msg, $content_type;
673                 global $course_title, $course_description, $course_primary_lang;  // added by Cindy Li
674                 static $resource_num = 0;
675                 
676                 if ($name == 'item') {
677                         array_pop($path);
678                 } 
679
680                 // added by Cindy Li on Jan 10, 2010
681                 // Extract course title, description and primary language for a newly-created course
682                 if (substr($element_path[count($element_path)-2], -6) == ':title') {
683                         if (substr($element_path[count($element_path)-1], -7) == ':string' ||
684                             substr($element_path[count($element_path)-1], -11) == ':langstring') {
685                                 $course_title = trim($my_data);
686                         }
687                 }
688                 
689                 if (substr($element_path[count($element_path)-2], -12) == ':description') {
690                         if (substr($element_path[count($element_path)-1], -7) == ':string' ||
691                             substr($element_path[count($element_path)-1], -11) == ':langstring') {
692                                 $course_description = trim($my_data);
693                         }
694                 }
695                 
696                 // get language from COMMON CARTRIDGE
697                 if (substr($element_path[count($element_path)-1], -9) == ':language') {
698                         $course_primary_lang = trim($my_data);
699                 }
700                 // end of added by Cindy Li on Jan 10, 2010
701                 
702                 //check if this is a test import
703                 if ($name == 'schema'){
704                         if (trim($my_data)=='IMS Question and Test Interoperability'){
705                                 if ($oauth_import) {
706                                         echo "error=".urlencode('A test import');
707                                 } else {
708                                         $msg->addError('IMPORT_FAILED');
709                                 }
710                         } 
711                         $content_type = trim($my_data);
712                 }
713
714                 //Handles A4a
715                 if ($current_identifier != ''){
716                         $my_data = trim($my_data);
717                         $last_file_name = $items[$current_identifier]['file'][(sizeof($items[$current_identifier]['file']))-1];
718
719                         if ($name=='originalAccessMode'){                               
720                                 if (in_array('accessModeStatement', $element_path)){
721                                         $items[$current_identifier]['a4a'][$last_file_name][$resource_num]['access_stmt_originalAccessMode'][] = $my_data;
722                                 } elseif (in_array('adaptationStatement', $element_path)){
723                                         $items[$current_identifier]['a4a'][$last_file_name][$resource_num]['adapt_stmt_originalAccessMode'][] = $my_data;
724                                 }                       
725                         } elseif (($name=='language') && in_array('accessModeStatement', $element_path)){
726                                 $items[$current_identifier]['a4a'][$last_file_name][$resource_num]['language'][] = $my_data;
727                         } elseif ($name=='hasAdaptation') {
728                                 $items[$current_identifier]['a4a'][$last_file_name][$resource_num]['hasAdaptation'][] = $my_data;
729                         } elseif ($name=='isAdaptationOf'){
730                                 $items[$current_identifier]['a4a'][$last_file_name][$resource_num]['isAdaptationOf'][] = $my_data;
731                         } elseif ($name=='accessForAllResource'){
732                                 /* the head node of accessForAll Metadata, if this exists in the manifest. Skip XSD validation,
733                                  * because A4a doesn't have a xsd yet.  Our access for all is based on ISO which will not pass 
734                                  * the current IMS validation.  
735                                  * Also, since ATutor is the only one (as of Oct 21, 2009) that exports IMS with access for all
736                                  * content, we can almost assume that any ims access for all content is by us, and is valid. 
737                                  */
738                                 $skip_ims_validation = true;
739                                 $resource_num++;
740                         } elseif($name=='file'){
741                                 $resource_num = 0;      //reset resournce number to 0 when the file tags ends
742                         }
743                 }
744
745                 if ($element_path === array('manifest', 'metadata', 'imsmd:lom', 'imsmd:general', 'imsmd:title', 'imsmd:langstring')) {
746                         global $package_base_name;
747                         $package_base_name = trim($my_data);
748                 }
749
750                 array_pop($element_path);
751                 $my_data = '';
752         }
753
754         /* called when there is character data within elements */
755         /* constructs the $items array using the last entry in $path as the parent element */
756         function characterData($parser, $data){
757                 global $path, $items, $order, $my_data, $element_path;
758                 global $current_identifier;
759
760                 $str_trimmed_data = trim($data);
761                 
762                 if (!empty($str_trimmed_data)) {
763                         $size = count($path);
764                         if ($size > 0) {
765                                 $current_item_id = $path[$size-1];
766                                 if ($size > 1) {
767                                         $parent_item_id = $path[$size-2];
768                                 } else {
769                                         $parent_item_id = 0;
770                                 }
771
772                                 if (isset($items[$current_item_id]['parent_content_id']) && is_array($items[$current_item_id])) {
773
774                                         /* this item already exists, append the title           */
775                                         /* this fixes {\n, \t, `, &} characters in elements */
776
777                                         /* horible kludge to fix the <ns2:objectiveDesc xmlns:ns2="http://www.utoronto.ca/atrc/tile/xsd/tile_objective"> */
778                                         /* from TILE */
779                                         if (in_array('accessForAllResource', $element_path)){
780                                                 //skip this tag
781                                         } elseif ($element_path[count($element_path)-1] != 'ns1:objectiveDesc') {
782                                                 $items[$current_item_id]['title'] .= $data;
783                                         }
784         
785                                 } else {
786                                         $order[$parent_item_id] ++;
787                                         $item_tmpl = array(     'title'                         => $data,
788                                                                                 'parent_content_id' => $parent_item_id,
789                                                                                 'ordering'                      => $order[$parent_item_id]-1);
790                                         //append other array values if it exists
791                                         if (is_array($items[$current_item_id])){
792                                                 $items[$current_item_id] = array_merge($items[$current_item_id], $item_tmpl);
793                                         } else {
794                                                 $items[$current_item_id] = $item_tmpl;
795                                         }
796                                 }
797                         }
798                 }
799
800                 $my_data .= $data;
801         }
802
803         /* glossary parser: */
804         function glossaryStartElement($parser, $name, $attrs) {
805                 global $element_path;
806
807                 array_push($element_path, $name);
808         }
809
810         /* called when an element ends */
811         /* removed the current element from the $path */
812         function glossaryEndElement($parser, $name) {
813                 global $element_path, $my_data, $imported_glossary;
814                 static $current_term;
815
816                 if ($element_path === array('glossary', 'item', 'term') || 
817                         $element_path === array('glossary:glossary', 'item', 'term')) {
818                         $current_term = $my_data;
819
820                 } else if ($element_path === array('glossary', 'item', 'definition') || 
821                                    $element_path === array('glossary:glossary', 'item', 'definition')) {
822                         $imported_glossary[trim($current_term)] = trim($my_data);
823                 }
824
825                 array_pop($element_path);
826                 $my_data = '';
827         }
828
829         function glossaryCharacterData($parser, $data){
830                 global $my_data;
831
832                 $my_data .= $data;
833         }
834
835 if (!isset($_POST['submit']) && !isset($_POST['cancel']) && !isset($_GET['oauth_token'])) {
836         /* just a catch all */
837         $msg->addError('NO_PRIV');
838         header('Location: '.$_SERVER['HTTP_REFERER']);
839         exit;
840 } else if (isset($_POST['cancel'])) {
841         $msg->addFeedback('IMPORT_CANCELLED');
842
843         header('Location: '.$_SERVER['HTTP_REFERER']);
844         exit;
845 }
846
847 $cid = intval($_POST['cid']);
848
849 //If user chooses to ignore validation.
850 if(isset($_POST['ignore_validation']) && $_POST['ignore_validation']==1) {
851         $skip_ims_validation = true;
852 }
853
854 if (isset($_REQUEST['url']) && ($_REQUEST['url'] != 'http://') ) {
855         if ($content = @file_get_contents($_REQUEST['url'])) {
856                 $filename = substr(time(), -6). '.zip';
857                 $full_filename = TR_CONTENT_DIR . $filename;
858
859                 if (!$fp = fopen($full_filename, 'w+b')) {
860                         echo "Cannot open file ($filename)";
861                         exit;
862                 }
863
864                 if (fwrite($fp, $content, strlen($content) ) === FALSE) {
865                         echo "Cannot write to file ($filename)";
866                         exit;
867                 }
868                 fclose($fp);
869         }       
870         $_FILES['file']['name']     = $filename;
871         $_FILES['file']['tmp_name'] = $full_filename;
872         $_FILES['file']['size']     = strlen($content);
873         unset($content);
874         $url_parts = pathinfo($_REQUEST['url']);
875         $package_base_name_url = $url_parts['basename'];
876 }
877 $ext = pathinfo($_FILES['file']['name']);
878 $ext = $ext['extension'];
879
880 if ($ext != 'zip') {
881 //      debug($ext);debug('not zip');exit;
882         $msg->addError('IMPORTDIR_IMS_NOTVALID');
883 } else if ($_FILES['file']['error'] == 1) {
884 //      debug('file error is 1');exit;
885         $errors = array('FILE_MAX_SIZE', ini_get('upload_max_filesize'));
886         $msg->addError($errors);
887 } else if ( !$_FILES['file']['name'] || (!is_uploaded_file($_FILES['file']['tmp_name']) && !$_REQUEST['url'])) {
888 //      debug('file not selected');exit;
889         $msg->addError('FILE_NOT_SELECTED');
890 } else if ($_FILES['file']['size'] == 0) {
891 //      debug('file size 0');exit;
892         $msg->addError('IMPORTFILE_EMPTY');
893
894 $msg->printAll();
895 if ($msg->containsErrors()) {
896         if (isset($_GET['tile'])) {
897                 header('Location: '.$_base_path.'tools/tile/index.php');
898         } else if ($oauth_import) {
899                 echo "error=".urlencode('Invalid imported file');
900         } else {
901                 header('Location: '.$_SERVER['HTTP_REFERER']);
902         }
903         if (file_exists($full_filename)) @unlink($full_filename);
904         exit;
905 }
906
907 /* check if ../content/import/ exists */
908 $import_path = TR_CONTENT_DIR . 'import/';
909 $content_path = TR_CONTENT_DIR;
910
911 if (!is_dir($import_path)) {
912         if (!@mkdir($import_path, 0700)) {
913                 $msg->addError('IMPORTDIR_FAILED');
914         }
915 }
916
917 if (isset($_POST['_course_id'])) $import_path .= $_POST['_course_id'].'/';
918 else $import_path .= Utility::getRandomStr(16).'/';
919
920 if (is_dir($import_path)) {
921         FileUtility::clr_dir($import_path);
922 }
923
924 if (!@mkdir($import_path, 0700)) {
925         $msg->addError('IMPORTDIR_FAILED');
926 }
927
928 if ($msg->containsErrors()) {
929         if (isset($_GET['tile'])) {
930                 header('Location: '.$_base_path.'tools/tile/index.php');
931         } else if ($oauth_import) {
932                 echo "error=".urlencode('Cannot create import directory');
933         } else {
934                 header('Location: '.$_SERVER['HTTP_REFERER']);
935         }
936         if (file_exists($full_filename)) @unlink($full_filename);
937         exit;
938 }
939
940 /* extract the entire archive into TR_COURSE_CONTENT . import/$course using the call back function to filter out php files */
941 error_reporting(0);
942 $archive = new PclZip($_FILES['file']['tmp_name']);
943
944 if ($archive->extract(  PCLZIP_OPT_PATH,        $import_path,
945                                                 PCLZIP_CB_PRE_EXTRACT,  'preImportCallBack') == 0) {
946         if ($oauth_import) {
947                 echo "error=".urlencode('Cannot unzip the package');
948         } else {
949                 $msg->addError('IMPORT_FAILED');
950                 echo 'Error : '.$archive->errorInfo(true);
951         }
952         FileUtility::clr_dir($import_path);
953         header('Location: '.$_SERVER['HTTP_REFERER']);
954         if (file_exists($full_filename)) @unlink($full_filename);
955         exit;
956 }
957 //error_reporting(AT_ERROR_REPORTING);
958
959 /* initialize DAO objects */
960 $coursesDAO = new CoursesDAO();
961 $contentDAO = new ContentDAO();
962 $testsQuestionsAssocDAO = new TestsQuestionsAssocDAO();
963 $contentTestsAssocDAO = new ContentTestsAssocDAO();
964
965 // get the course's max_quota
966 if (isset($_POST['_course_id']))
967 {
968         check_available_size($_POST['_course_id']);
969 }
970
971 $items = array(); /* all the content pages */
972 $order = array(); /* keeps track of the ordering for each content page */
973 $path  = array();  /* the hierarchy path taken in the menu to get to the current item in the manifest */
974 $dependency_files = array(); /* the file path for the dependency files */
975
976 /*
977 $items[content_id/resource_id] = array(
978                                                                         'title'
979                                                                         'real_content_id' // calculated after being inserted
980                                                                         'parent_content_id'
981                                                                         'href'
982                                                                         'ordering'
983                                                                         );
984 */
985 $ims_manifest_xml = @file_get_contents($import_path.'imsmanifest.xml');
986
987 //scan for manifest xml if it's not on the top level.
988 if ($ims_manifest_xml === false){
989         $data = rscandir($import_path);
990         $manifest_array = array();
991         foreach($data as $scanned_file){
992                 $scanned_file = realpath($scanned_file);
993                 //change the file string to an array
994                 $this_file_array = explode(DIRECTORY_SEPARATOR, $scanned_file);
995                 if(empty($manifest_array)){
996                         $manifest_array = $this_file_array;
997                 }
998                 $manifest_array = array_intersect_assoc($this_file_array, $manifest_array);
999
1000                 if (strpos($scanned_file, 'imsmanifest')!==false){
1001                         $ims_manifest_xml = @file_get_contents($scanned_file);
1002                 }
1003         }
1004         if ($ims_manifest_xml !== false){
1005                 $import_path = implode(DIRECTORY_SEPARATOR, $manifest_array) . DIRECTORY_SEPARATOR;
1006         }
1007 }
1008
1009 //if no imsmanifest.xml found in the entire package, throw error.
1010 if ($ims_manifest_xml === false) {
1011         $msg->addError('NO_IMSMANIFEST');
1012
1013         if (file_exists($import_path . 'atutor_backup_version')) {
1014                 $msg->addError('NO_IMS_BACKUP');
1015         }
1016         FileUtility::clr_dir($import_path);
1017
1018         if (isset($_GET['tile'])) {
1019                 header('Location: '.$_base_path.'tools/tile/index.php');
1020         } else if ($oauth_import) {
1021                 echo "error=".urlencode('IMS manifest file does not appear to be valid');
1022         } else {
1023                 header('Location: '.$_SERVER['HTTP_REFERER']);
1024         }
1025         if (file_exists($full_filename)) @unlink($full_filename);
1026         exit;
1027 }
1028
1029 $xml_parser = xml_parser_create();
1030
1031 xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, false); /* conform to W3C specs */
1032 xml_set_element_handler($xml_parser, 'startElement', 'endElement');
1033 xml_set_character_data_handler($xml_parser, 'characterData');
1034
1035 if (!xml_parse($xml_parser, $ims_manifest_xml, true)) {
1036         die(sprintf("XML error: %s at line %d",
1037                                 xml_error_string(xml_get_error_code($xml_parser)),
1038                                 xml_get_current_line_number($xml_parser)));
1039 }
1040 xml_parser_free($xml_parser);
1041 /* check if the glossary terms exist */
1042 /* Commented by Cindy Li on Jan 7, 2010. Transformable does not include glossary
1043 $glossary_path = '';
1044 if ($content_type == 'IMS Common Cartridge'){
1045         $glossary_path = 'resources/GlossaryItem/';
1046 //      $package_base_path = '';
1047 }
1048 if (file_exists($import_path . $glossary_path . 'glossary.xml')){
1049         $glossary_xml = @file_get_contents($import_path.$glossary_path.'glossary.xml');
1050         $element_path = array();
1051         $xml_parser = xml_parser_create();
1052
1053         // insert the glossary terms into the database (if they're not in there already)
1054         // parse the glossary.xml file and insert the terms
1055         xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, false); // conform to W3C specs
1056         xml_set_element_handler($xml_parser, 'glossaryStartElement', 'glossaryEndElement');
1057         xml_set_character_data_handler($xml_parser, 'glossaryCharacterData');
1058
1059         if (!xml_parse($xml_parser, $glossary_xml, true)) {
1060                 die(sprintf("XML error: %s at line %d",
1061                                         xml_error_string(xml_get_error_code($xml_parser)),
1062                                         xml_get_current_line_number($xml_parser)));
1063         }
1064         xml_parser_free($xml_parser);
1065         $contains_glossary_terms = true;
1066         foreach ($imported_glossary as $term => $defn) {
1067                 if (!$glossary[$term]) {
1068                         $sql = "INSERT INTO ".TABLE_PREFIX."glossary VALUES (NULL, $_SESSION[course_id], '$term', '$defn', 0)";
1069                         mysql_query($sql, $db); 
1070                 }
1071         }
1072 }
1073 */
1074 // Check if all the files exists in the manifest, iff it's a IMS CC package.
1075 if ($content_type == 'IMS Common Cartridge') {
1076         checkResources($import_path);
1077 }
1078
1079 // Check if there are any errors during parsing.
1080 if ($msg->containsErrors()) {
1081         if (isset($_GET['tile'])) {
1082                 header('Location: '.$_base_path.'tools/tile/index.php');
1083         } else if ($oauth_import) {
1084                 echo "error=".urlencode('Error at parsing IMS manifest file');
1085         } else {
1086                 header('Location: '.$_SERVER['HTTP_REFERER']);
1087         }
1088         if (file_exists($full_filename)) @unlink($full_filename);
1089         exit;
1090 }
1091
1092 // added by Cindy Li on Jan 10, 2010
1093 // generate a course_id if the import is not into an existing course
1094 if (!isset($_POST['_course_id']))
1095 {
1096         if (isset($_POST['hide_course']))
1097                 $access = 'private';
1098         else
1099                 $access = 'public';
1100         
1101         if (isset($course_primary_lang))
1102         {
1103                 $langcode_and_charset = explode('-', $course_primary_lang);
1104 //              $course_primary_lang = Utility::get3LetterLangCode($langcode_and_charset[0]);
1105                 $course_primary_lang = $langcode_and_charset[0];
1106         }
1107         
1108         $_course_id = $coursesDAO->Create($_SESSION['user_id'], 'top', $access, $course_title, $course_description, 
1109                      '', '', '', '', $course_primary_lang, '', '');
1110         
1111         check_available_size($_course_id);
1112
1113         // insert author role into table "user_courses"
1114         $userCoursesDAO = new UserCoursesDAO();
1115         $userCoursesDAO->Create($_SESSION['user_id'], $_course_id, TR_USERROLE_AUTHOR, 0);
1116 }
1117 else $_course_id = $_POST['_course_id'];
1118
1119 // end of added by Cindy Li on Jan 10, 2010
1120
1121 /* generate a unique new package base path based on the package file name and date as needed. */
1122 /* the package name will be the dir where the content for this package will be put, as a result */
1123 /* the 'content_path' field in the content table will be set to this path. */
1124 /* $package_base_name_url comes from the URL file name (NOT the file name of the actual file we open)*/
1125 if (!$package_base_name && $package_base_name_url) {
1126         $package_base_name = substr($package_base_name_url, 0, -4);
1127 } else if (!$package_base_name) {
1128         $package_base_name = substr($_FILES['file']['name'], 0, -4);
1129 }
1130
1131 $package_base_name = strtolower($package_base_name);
1132 $package_base_name = str_replace(array('\'', '"', ' ', '|', '\\', '/', '<', '>', ':'), '_' , $package_base_name);
1133 $package_base_name = preg_replace("/[^A-Za-z0-9._\-]/", '', $package_base_name);
1134
1135 $course_dir = TR_CONTENT_DIR.$_course_id.'/';
1136
1137 if (is_dir($course_dir.$package_base_name)) {
1138         $package_base_name .= '_'.date('ymdHis');
1139 }
1140
1141 if ($package_base_path) {
1142         $package_base_path = implode('/', $package_base_path);
1143 } elseif (empty($package_base_path)){
1144         $package_base_path = '';
1145 }
1146
1147 if ($xml_base_path) {
1148         $package_base_path = $xml_base_path . $package_base_path;
1149
1150         mkdir($import_path.$xml_base_path);
1151         $package_base_name = $xml_base_path . $package_base_name;
1152 }
1153
1154 /* get the top level content ordering offset */
1155 //$sql  = "SELECT MAX(ordering) AS ordering FROM ".TABLE_PREFIX."content WHERE course_id=$_SESSION[course_id] AND content_parent_id=$cid";
1156 //$result = mysql_query($sql, $db);
1157 //$row  = mysql_fetch_assoc($result);
1158 //$order_offset = intval($row['ordering']); /* it's nice to have a real number to deal with */
1159 $order_offset = $contentDAO->getMaxOrdering($_course_id, 0);
1160 $lti_offset = array();  //since we don't need lti tools, the ordering needs to be subtracted
1161 //reorder the items stack
1162 $common_path = removeCommonPath($items);
1163 $items = rehash($items);
1164 //debug($items);exit;
1165 foreach ($items as $item_id => $content_info) 
1166 {       
1167         //formatting field, default 1
1168         $content_formatting = 1;        //CONTENT_TYPE_CONTENT
1169
1170         //don't want to display glossary as a page
1171         if ($content_info['href']== $glossary_path . 'glossary.xml'){
1172                 continue;
1173         }
1174
1175         //if discussion tools, add it to the list of unhandled dts
1176         if ($content_info['type']=='imsdt_xmlv1p0'){
1177                 //if it will be taken care after (has dependency), then move along.
1178                 if (in_array($item_id, $avail_dt)){
1179                         $lti_offset[$content_info['parent_content_id']]++;
1180                         continue;
1181                 }
1182         }
1183
1184         //handle the special case of cc import, where there is no content association. The resource should
1185         //still be imported.
1186         if(!isset($content_info['parent_content_id'])){
1187                 //if this is a question bank 
1188                 if ($content_info['type']=="imsqti_xmlv1p2/imscc_xmlv1p0/question-bank"){
1189                         addQuestions($content_info['href'], $content_info, $import_path);
1190                 }
1191         }
1192
1193         //if it has no title, most likely it is not a page but just a normal item, skip it
1194         if (!isset($content_info['title'])){
1195                 continue;
1196         }
1197         
1198         //check dependency immediately, then handles it
1199         $head = '';
1200         if (is_array($content_info['dependency']) && !empty($content_info['dependency'])){
1201                 foreach($content_info['dependency'] as $dependency_ref){
1202                         //handle styles 
1203                         /** handled by get_html_head in vitals.inc.php
1204                         if (preg_match('/(.*)\.css$/', $items[$dependency_ref]['href'])){
1205                                 //calculate where this is based on our current base_href. 
1206                                 //assuming the dependency folders are siblings of the item
1207                                 $head = '<link rel="stylesheet" type="text/css" href="../'.$items[$dependency_ref]['href'].'" />';
1208                         }
1209                         */
1210                         //check if this is a discussion tool dependency
1211                         if ($items[$dependency_ref]['type']=='imsdt_xmlv1p0'){
1212                                 $items[$item_id]['forum'][$dependency_ref] = $items[$dependency_ref]['href'];
1213                         }
1214                         //check if this is a QTI dependency
1215                         if (strpos($items[$dependency_ref]['type'], 'imsqti_xmlv1p2/imscc_xmlv1p0') !== false){
1216                                 $items[$item_id]['tests'][$dependency_ref] = $items[$dependency_ref]['href'];
1217                         }
1218                 }
1219         }
1220
1221         //check file array, see if there are css. 
1222         //edited nov 26, harris
1223         //removed cuz i added link to the html_tags
1224         /*
1225         if (is_array($content_info['file']) && !empty($content_info['file'])){
1226                 foreach($content_info['file'] as $dependency_ref){
1227                         //handle styles 
1228                         if (preg_match('/(.*)\.css$/', $dependency_ref)){
1229                                 //calculate where this is based on our current base_href. 
1230                                 //assuming the dependency folders are siblings of the item
1231                                 $head = '<link rel="stylesheet" type="text/css" href="'.$dependency_ref.'" />';
1232                         }
1233                 }
1234         }
1235         */
1236
1237         // remote href
1238         if (preg_match('/^http.*:\/\//', trim($content_info['href'])) )
1239         {
1240                 $content = '<a href="'.$content_info['href'].'" target="_blank">'.$content_info['title'].'</a>';
1241         }
1242         else
1243         {
1244                 if ($content_type == 'IMS Common Cartridge'){
1245                         //to handle import with purely images but nothing else
1246                         //don't need a content base path for it.
1247                         $content_new_path = $content_info['new_path'];
1248                         $content_info['new_path'] = '';
1249                 }
1250                 if (isset($content_info['href'], $xml_base_path)) {
1251                         $content_info['href'] = $xml_base_path . $content_info['href'];
1252                 }
1253                 if (!isset($content_info['href'])) {
1254                         // this item doesn't have an identifierref. so create an empty page.
1255                         // what we called a folder according to v1.2 Content Packaging spec
1256                         // Hop over
1257                         $content = '';
1258                         $ext = '';
1259                         $last_modified = date('Y-m-d H:i:s');
1260                 } else {
1261                         //$file_info = @stat(TR_CONTENT_DIR . 'import/'.$_POST['_course_id'].'/'.$content_info['href']);
1262                         $file_info = @stat($import_path.$content_info['href']);
1263                         if ($file_info === false) {
1264                                 continue;
1265                         }
1266                 
1267                         //$path_parts = pathinfo(TR_CONTENT_DIR . 'import/'.$_POST['_course_id'].'/'.$content_info['href']);
1268                         $path_parts = pathinfo($import_path.$content_info['href']);
1269                         $ext = strtolower($path_parts['extension']);
1270
1271                         $last_modified = date('Y-m-d H:i:s', $file_info['mtime']);
1272                 }
1273                 if (in_array($ext, array('gif', 'jpg', 'bmp', 'png', 'jpeg'))) {
1274                         /* this is an image */
1275                         $content = '<img src="'.$content_info['href'].'" alt="'.$content_info['title'].'" />';
1276                 } else if ($ext == 'swf') {
1277                         /* this is flash */
1278             /* Using default size of 550 x 400 */
1279
1280                         $content = '<object type="application/x-shockwave-flash" data="' . $content_info['href'] . '" width="550" height="400"><param name="movie" value="'. $content_info['href'] .'" /></object>';
1281
1282                 } else if ($ext == 'mov') {
1283                         /* this is a quicktime movie  */
1284             /* Using default size of 550 x 400 */
1285
1286                         $content = '<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" width="550" height="400" codebase="http://www.apple.com/qtactivex/qtplugin.cab"><param name="src" value="'. $content_info['href'] . '" /><param name="autoplay" value="true" /><param name="controller" value="true" /><embed src="' . $content_info['href'] .'" width="550" height="400" controller="true" pluginspage="http://www.apple.com/quicktime/download/"></embed></object>';
1287
1288                 /* Oct 19, 2009
1289                  * commenting this whole chunk out.  It's part of my test import codes, not sure why it's here, 
1290                  * and I don't think it should be here.  Remove this whole comment after further testing and confirmation.
1291                  * @harris
1292                  *
1293                         //Mimic the array for now.
1294                         $test_attributes['resource']['href'] = $test_xml_file;
1295                         $test_attributes['resource']['type'] = isset($items[$item_id]['type'])?'imsqti_xmlv1p2':'imsqti_xmlv1p1';
1296                         $test_attributes['resource']['file'] = $items[$item_id]['file'];
1297 //                      $test_attributes['resource']['file'] = array($test_xml_file);
1298
1299                         //Get the XML file out and start importing them into our database.
1300                         //TODO: See question_import.php 287-289.
1301                         $qids = $qti_import->importQuestions($test_attributes);
1302                 
1303                  */
1304                 } else if ($ext == 'mp3') {
1305                         $content = '<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" width="200" height="15" codebase="http://www.apple.com/qtactivex/qtplugin.cab"><param name="src" value="'. $content_info['href'] . '" /><param name="autoplay" value="false" /><embed src="' . $content_info['href'] .'" width="200" height="15" autoplay="false" pluginspage="http://www.apple.com/quicktime/download/"></embed></object>';
1306                 } else if (in_array($ext, array('wav', 'au'))) {
1307                         $content = '<embed SRC="'.$content_info['href'].'" autostart="false" width="145" height="60"><noembed><bgsound src="'.$content_info['href'].'"></noembed></embed>';
1308
1309                 } else if (in_array($ext, array('txt', 'css', 'html', 'htm', 'csv', 'asc', 'tsv', 'xml', 'xsl'))) {
1310                         if ($content_type == 'IMS Common Cartridge'){
1311                                 $content_info['new_path'] = $content_new_path;
1312                         }
1313
1314                         /* this is a plain text file */
1315                         //$content = file_get_contents(TR_CONTENT_DIR . 'import/'.$_POST['_course_id'].'/'.$content_info['href']);
1316                         $content = file_get_contents($import_path.$content_info['href']);
1317                         if ($content === false) {
1318                                 /* if we can't stat() it then we're unlikely to be able to read it */
1319                                 /* so we'll never get here. */
1320                                 continue;
1321                         }
1322
1323                         // get the contents of the 'head' element
1324                         $head .= ContentUtility::getHtmlHeadByTag($content, $html_head_tags);
1325                         
1326                         // Specifically handle eXe package
1327                         // NOTE: THIS NEEDS WORK! TO FIND A WAY APPLY EXE .CSS FILES ONLY ON COURSE CONTENT PART.
1328                         // NOW USE OUR OWN .CSS CREATED SOLELY FOR EXE
1329                         $isExeContent = false;
1330
1331                         // check xml file in eXe package
1332                         if (preg_match("/<organization[ ]*identifier=\"eXe*>*/", $ims_manifest_xml))
1333                         {
1334                                 $isExeContent = true;
1335                         }
1336
1337                         // use ATutor's eXe style sheet as the ones from eXe conflicts with ATutor's style sheets
1338                         if ($isExeContent)
1339                         {
1340                                 $head = preg_replace ('/(<style.*>)(.*)(<\/style>)/ms', '\\1@import url(/docs/exestyles.css);\\3', $head);
1341                         }
1342
1343                         // end of specifically handle eXe package
1344
1345                         $content = ContentUtility::getHtmlBody($content);
1346                         if ($contains_glossary_terms) 
1347                         {
1348                                 // replace glossary content package links to real glossary mark-up using [?] [/?]
1349                                 // refer to bug 3641, edited by Harris
1350                                 $content = preg_replace('/<a href="([.\w\d\s]+[^"]+)" target="body" class="at-term">([.\w\d\s&;"]+|.*)<\/a>/i', '[?]\\2[/?]', $content);
1351                         }
1352
1353                         /* potential security risk? */
1354                         if ( strpos($content_info['href'], '..') === false && !preg_match('/((.*)\/)*tests\_[0-9]+\.xml$/', $content_info['href'])) {
1355 //                              @unlink(TR_CONTENT_DIR . 'import/'.$_POST['_course_id'].'/'.$content_info['href']);
1356                         }
1357
1358                         // overwrite content if this is discussion tool.
1359                         if ($content_info['type']=='imsdt_xmlv1p0'){
1360                                 $dt_parser = new DiscussionToolsParser();
1361                                 $xml_content = @file_get_contents($import_path . $content_info['href']);
1362                                 $dt_parser->parse($xml_content);
1363                                 $forum_obj = $dt_parser->getDt();
1364                                 $content = $forum_obj->getText();
1365                                 unset($forum_obj);
1366                                 $dt_parser->close();
1367                         }
1368                 } else if ($ext) {
1369                         /* non text file, and can't embed (example: PDF files) */
1370                         $content = '<a href="'.$content_info['href'].'">'.$content_info['title'].'</a>';
1371                 }       
1372         }
1373         $content_parent_id = $cid;
1374         if ($content_info['parent_content_id'] !== 0) {
1375                 $content_parent_id = $items[$content_info['parent_content_id']]['real_content_id'];
1376                 //if it's not there, use $cid
1377                 if (!$content_parent_id){
1378                         $content_parent_id = $cid;
1379                 }
1380         }
1381
1382         $my_offset = 0;
1383         if ($content_parent_id == $cid) {
1384                 $my_offset = $order_offset;
1385         }
1386
1387         /* replace the old path greatest common denomiator with the new package path. */
1388         /* we don't use str_replace, b/c there's no knowing what the paths may be         */
1389         /* we only want to replace the first part of the path.  
1390         */
1391         if(is_array($all_package_base_path)){
1392                 $all_package_base_path = implode('/', $all_package_base_path);
1393         }
1394
1395         if ($common_path != '') {
1396                 $content_info['new_path'] = $package_base_name . substr($content_info['new_path'], strlen($common_path));
1397         } else {
1398                 $content_info['new_path'] = $package_base_name . '/' . $content_info['new_path'];
1399         }
1400
1401         //handles weblinks
1402         if ($content_info['type']=='imswl_xmlv1p0'){
1403                 $weblinks_parser = new WeblinksParser();
1404                 $xml_content = @file_get_contents($import_path . $content_info['href']);
1405                 $weblinks_parser->parse($xml_content);
1406                 $content_info['title'] = $weblinks_parser->getTitle();
1407                 $content = $weblinks_parser->getUrl();
1408                 $content_folder_type = CONTENT_TYPE_WEBLINK;
1409                 $content_formatting = 2;
1410         }
1411 //      $head = addslashes($head);
1412 //      $content_info['title'] = addslashes($content_info['title']);
1413 //      $content_info['test_message'] = addslashes($content_info['test_message']);
1414
1415         //if this file is a test_xml, create a blank page instead, for imscc.
1416         if (preg_match('/((.*)\/)*tests\_[0-9]+\.xml$/', $content_info['href']) 
1417                 || preg_match('/imsqti\_(.*)/', $content_info['type'])) {
1418                 $content = ' ';
1419         } 
1420 //      else {
1421 //              $content = addslashes($content);
1422 //      }
1423
1424         //check for content_type
1425         if ($content_formatting!=CONTENT_TYPE_WEBLINK){
1426                 $content_folder_type = (!isset($content_info['type'])?CONTENT_TYPE_FOLDER:CONTENT_TYPE_CONTENT);
1427         }
1428         
1429         $items[$item_id]['real_content_id'] = $contentDAO->Create($_course_id, intval($content_parent_id), 
1430                             ($content_info['ordering'] + $my_offset - $lti_offset[$content_info['parent_content_id']] + 1),
1431                             0, $content_formatting, "", $content_info['new_path'], $content_info['title'],
1432                             $content, $head, 1, $content_info['test_message'], $content_folder_type);
1433
1434 //      $sql= 'INSERT INTO '.TABLE_PREFIX.'content'
1435 //            . '(course_id, 
1436 //                content_parent_id, 
1437 //                ordering,
1438 //                last_modified, 
1439 //                revision, 
1440 //                formatting, 
1441 //                release_date,
1442 //                head,
1443 //                use_customized_head,
1444 //                keywords, 
1445 //                content_path, 
1446 //                title, 
1447 //                text,
1448 //                        test_message,
1449 //                        content_type) 
1450 //             VALUES 
1451 //                           ('.$_SESSION['course_id'].','                                                                                                                      
1452 //                           .intval($content_parent_id).','            
1453 //                           .($content_info['ordering'] + $my_offset - $lti_offset[$content_info['parent_content_id']] + 1).','
1454 //                           .'"'.$last_modified.'",                                                                                                    
1455 //                            0,'
1456 //                           .$content_formatting.' ,
1457 //                            NOW(),"'
1458 //                           . $head .'",
1459 //                           1,
1460 //                            "",'
1461 //                           .'"'.$content_info['new_path'].'",'
1462 //                           .'"'.$content_info['title'].'",'
1463 //                           .'"'.$content.'",'
1464 //                               .'"'.$content_info['test_message'].'",'
1465 //                               .$content_folder_type.')';
1466 //
1467 //      $result = mysql_query($sql, $db) or die(mysql_error());
1468 //
1469 //      /* get the content id and update $items */
1470 //      $items[$item_id]['real_content_id'] = mysql_insert_id($db);
1471
1472         /* get the tests associated with this content */
1473         if (!empty($items[$item_id]['tests']) || strpos($items[$item_id]['type'], 'imsqti_xmlv1p2/imscc_xmlv1p0') !== false){
1474                 $qti_import = new QTIImport($import_path);
1475                 if (isset($items[$item_id]['tests'])){
1476                         $loop_var = $items[$item_id]['tests'];
1477                 } else {
1478                         $loop_var = $items[$item_id]['file'];
1479                 }
1480
1481                 foreach ($loop_var as $array_id => $test_xml_file){
1482                         //check if this item is the qti item object, or it is the content item obj
1483                         //switch it to qti obj if it's content item obj
1484                         if ($items[$item_id]['type'] == 'webcontent'){
1485                                 $item_qti = $items[$array_id];
1486                         } else {
1487                                 $item_qti = $items[$item_id];
1488                         }
1489                         //call subrountine to add the questions.
1490                         $qids = addQuestions($test_xml_file, $item_qti, $import_path);
1491
1492                         //import test
1493                         if ($test_title==''){
1494                                 $test_title = $content_info['title'];
1495                         }
1496
1497                         $tid = $qti_import->importTest($test_title);
1498
1499                         //associate question and tests
1500                         foreach ($qids as $order=>$qid){
1501                                 if (isset($qti_import->weights[$order])){
1502                                         $weight = round($qti_import->weights[$order]);
1503                                 } else {
1504                                         $weight = 0;
1505                                 }
1506                                 $new_order = $order + 1;
1507                                 $testsQuestionsAssocDAO->Create($tid, $qid, $weight, $new_order);
1508 //                              $sql = "INSERT INTO " . TABLE_PREFIX . "tests_questions_assoc" . 
1509 //                                              "(test_id, question_id, weight, ordering, required) " .
1510 //                                              "VALUES ($tid, $qid, $weight, $new_order, 0)";
1511 //                              $result = mysql_query($sql, $db);
1512                         }
1513
1514                         //associate content and test
1515                         $contentTestsAssocDAO->Create($items[$item_id]['real_content_id'], $tid);
1516 //                      $sql =  'INSERT INTO ' . TABLE_PREFIX . 'content_tests_assoc' . 
1517 //                                      '(content_id, test_id) ' .
1518 //                                      'VALUES (' . $items[$item_id]['real_content_id'] . ", $tid)";
1519 //                      $result = mysql_query($sql, $db);
1520                 
1521 //                      if (!$msg->containsErrors()) {
1522 //                              $msg->addFeedback('IMPORT_SUCCEEDED');
1523 //                      }
1524                 }
1525         }
1526
1527         /* get the a4a related xml */
1528         if (isset($items[$item_id]['a4a_import_enabled']) && isset($items[$item_id]['a4a']) && !empty($items[$item_id]['a4a'])) {
1529                 $a4a_import = new A4aImport($items[$item_id]['real_content_id']);
1530                 $a4a_import->setRelativePath($items[$item_id]['new_path']);
1531                 $a4a_import->importA4a($items[$item_id]['a4a']);
1532         }
1533
1534         // get the discussion tools (dependent to content)
1535         if (isset($items[$item_id]['forum']) && !empty($items[$item_id]['forum'])){
1536                 foreach($items[$item_id]['forum'] as $forum_ref => $forum_link){
1537                         $dt_parser = new DiscussionToolsParser();
1538                         $dt_import = new DiscussionToolsImport();
1539
1540                         //if this forum has not been added, parse it and add it.
1541                         if (!isset($added_dt[$forum_ref])){
1542                                 $xml_content = @file_get_contents($import_path . $forum_link);
1543                                 $dt_parser->parse($xml_content);
1544                                 $forum_obj = $dt_parser->getDt();
1545                                 $dt_import->import($forum_obj, $items[$item_id]['real_content_id'], $_course_id);
1546                                 $added_dt[$forum_ref] = $dt_import->getFid();                           
1547                         }
1548                         //associate the fid and content id
1549 //                      $dt_import->associateForum($items[$item_id]['real_content_id'], $added_dt[$forum_ref]);
1550                 }
1551         } elseif ($items[$item_id]['type']=='imsdt_xmlv1p0'){
1552                 //optimize this, repeated codes as above
1553                 $dt_parser = new DiscussionToolsParser();
1554                 $dt_import = new DiscussionToolsImport();
1555                 $xml_content = @file_get_contents($import_path . $content_info['href']);
1556                 $dt_parser->parse($xml_content);
1557                 $forum_obj = $dt_parser->getDt();
1558                 $dt_import->import($forum_obj, $items[$item_id]['real_content_id'], $_course_id);
1559                 $added_dt[$item_id] = $dt_import->getFid();
1560
1561                 //associate the fid and content id
1562 //              $dt_import->associateForum($items[$item_id]['real_content_id'], $added_dt[$item_id]);
1563         }
1564 }
1565
1566 //exit;//harris
1567 if ($package_base_path == '.') {
1568         $package_base_path = '';
1569 }
1570
1571 // create course directory
1572 if (!is_dir($course_dir)) {
1573         if (!@mkdir($course_dir, 0700)) {
1574                 $msg->addError('IMPORTDIR_FAILED');
1575         }
1576 }
1577
1578 // loop through the files outside the package folder, and copy them to its relative path
1579 /**
1580 if (is_dir($import_path.'resources')) {
1581         $handler = opendir($import_path.'resources');
1582         while ($file = readdir($handler)){
1583                 $filename = $import_path.'resources/'.$file;
1584                 if(is_file($filename)){
1585                         @rename($filename, $course_dir.$package_base_name.'/'.$file);
1586                 }
1587         }
1588         closedir($handler);
1589 }
1590 **/
1591 //--- harris edit for path thing
1592 $file = $import_path.$common_path;
1593 if (is_dir($file)) {
1594     rename($file, TR_CONTENT_DIR.$_course_id.DIRECTORY_SEPARATOR.$package_base_name);
1595 }
1596 //--- end
1597 //takes care of the condition where the whole package doesn't have any contents but question banks
1598 //also is the case of urls
1599 if(is_array($all_package_base_path)){
1600         $all_package_base_path = implode('/', $all_package_base_path);
1601
1602         if(strpos($all_package_base_path, 'http:/')===false){
1603                 if (rename($import_path.$all_package_base_path, $course_dir.$package_base_name) === false) {
1604                 if (!$msg->containsErrors()) {
1605                                 if ($oauth_import) {
1606                                         echo "error=".urlencode('Cannot move lesson directory into content directory');
1607                                 } else {
1608                                         $msg->addError('IMPORT_FAILED');
1609                                 }
1610                 }
1611             }
1612         }
1613 }
1614 //check if there are still resources missing
1615 /*
1616 foreach($items as $idetails){
1617         $temp_path = pathinfo($idetails['href']);
1618         @rename($import_path.$temp_path['dirname'], $course_dir.$package_base_name . '/' . $temp_path['dirname']);
1619 }
1620 */
1621 FileUtility::clr_dir($import_path);
1622
1623 if (file_exists($full_filename)) @unlink($full_filename);
1624
1625 if ($oauth_import) {
1626         echo 'course_id='.$_course_id;
1627 } else {
1628         if (!$msg->containsErrors()) {
1629                 $msg->addFeedback('ACTION_COMPLETED_SUCCESSFULLY');
1630         }
1631         header('Location: ../course/index.php?_course_id='.$_course_id);
1632 }
1633 exit;
1634
1635 //      if ($_POST['s_cid']){
1636 //      if (!$msg->containsErrors()) {
1637 //              $msg->addFeedback('ACTION_COMPLETED_SUCCESSFULLY');
1638 //      }
1639 //      header('Location: ../../editor/edit_content.php?cid='.intval($_POST['cid']));
1640 //      exit;
1641 //} else {
1642 //      if (!$msg->containsErrors()) {
1643 //              $msg->addFeedback('ACTION_COMPLETED_SUCCESSFULLY');
1644 //      }
1645 //      if ($_GET['tile']) {
1646 //              header('Location: '.TR_BASE_HREF.'tools/tile/index.php');
1647 //      } else {
1648 //              header('Location: ../index.php?cid='.intval($_POST['cid']));
1649 //      }
1650 //      exit;
1651 //}
1652
1653 ?>