2 /****************************************************************/
4 /****************************************************************/
5 /* Copyright (c) 2002-2007 by Greg Gay & Joel Kronenberg */
6 /* Adaptive Technology Resource Centre / University of Toronto */
9 /* This program is free software. You can redistribute it and/or*/
10 /* modify it under the terms of the GNU General Public License */
11 /* as published by the Free Software Foundation. */
12 /****************************************************************/
14 define('AT_INCLUDE_PATH', '../../include/');
15 require(AT_INCLUDE_PATH.'vitals.inc.php');
17 require(AT_INCLUDE_PATH.'lib/filemanager.inc.php'); /* for clr_dir() and preImportCallBack and dirsize() */
18 require(AT_INCLUDE_PATH.'classes/pclzip.lib.php');
20 /* make sure we own this course that we're exporting */
21 authenticate(AT_PRIV_CONTENT);
23 /* to avoid timing out on large files */
25 $_SESSION['done'] = 1;
27 $package_base_path = '';
29 $element_path = array();
30 $imported_glossary = array();
32 /* called at the start of en element */
33 /* builds the $path array which is the path from the root to the current element */
34 function startElement($parser, $name, $attrs) {
35 global $items, $path, $package_base_path;
37 global $xml_base_path;
38 static $current_identifier;
40 if ($name == 'manifest' && isset($attrs['xml:base']) && $attrs['xml:base']) {
41 $xml_base_path = $attrs['xml:base'];
42 } else if ($name == 'file') {
43 // special case for webCT content packages that don't specify the `href` attribute
44 // with the `<resource>` element.
45 // we take the `href` from the first `<file>` element.
46 if (isset($items[$current_identifier]) && ($items[$current_identifier]['href'] == '')) {
47 $items[$current_identifier]['href'] = $attrs['href'];
49 $items[$current_identifier]['href'] = $attrs['href'];
51 $temp_path = pathinfo($attrs['href']);
52 $temp_path = explode('/', $temp_path['dirname']);
54 if ($package_base_path == '') {
55 $package_base_path = $temp_path;
57 $package_base_path = array_intersect($package_base_path, $temp_path);
60 $items[$current_identifier]['new_path'] = implode('/', $temp_path);
62 } else if (($name == 'item') && ($attrs['identifierref'] != '')) {
63 $path[] = $attrs['identifierref'];
64 } else if (($name == 'item') && ($attrs['identifier'])) {
65 $path[] = $attrs['identifier'];
66 } else if (($name == 'resource') && is_array($items[$attrs['identifier']])) {
67 $current_identifier = $attrs['identifier'];
70 $items[$attrs['identifier']]['href'] = $attrs['href'];
72 $temp_path = pathinfo($attrs['href']);
73 $temp_path = explode('/', $temp_path['dirname']);
74 if (!$package_base_path) {
75 $package_base_path = $temp_path;
77 $package_base_path = array_intersect($package_base_path, $temp_path);
80 $items[$attrs['identifier']]['new_path'] = implode('/', $temp_path);
83 array_push($element_path, $name);
86 /* called when an element ends */
87 /* removed the current element from the $path */
88 function endElement($parser, $name) {
89 global $path, $element_path, $my_data;
91 if ($name == 'item') {
95 if ($element_path === array('manifest', 'metadata', 'imsmd:lom', 'imsmd:general', 'imsmd:title', 'imsmd:langstring')) {
96 global $package_base_name;
97 $package_base_name = trim($my_data);
100 array_pop($element_path);
104 /* called when there is character data within elements */
105 /* constructs the $items array using the last entry in $path as the parent element */
106 function characterData($parser, $data){
107 global $path, $items, $order, $my_data, $element_path;
109 $str_trimmed_data = trim($data);
111 if (!empty($str_trimmed_data)) {
112 $size = count($path);
114 $current_item_id = $path[$size-1];
116 $parent_item_id = $path[$size-2];
120 if (is_array($items[$current_item_id])) {
122 /* this item already exists, append the title */
123 /* this fixes {\n, \t, `, &} characters in elements */
125 /* horible kludge to fix the <ns2:objectiveDesc xmlns:ns2="http://www.utoronto.ca/atrc/tile/xsd/tile_objective"> */
127 if ($element_path[count($element_path)-1] != 'ns1:objectiveDesc') {
128 $items[$current_item_id]['title'] .= $data;
132 $order[$parent_item_id] ++;
134 $items[$current_item_id] = array('title' => $data,
135 'parent_content_id' => $parent_item_id,
136 'ordering' => $order[$parent_item_id]-1);
145 /* glossary parser: */
146 function glossaryStartElement($parser, $name, $attrs) {
147 global $element_path;
149 array_push($element_path, $name);
152 /* called when an element ends */
153 /* removed the current element from the $path */
154 function glossaryEndElement($parser, $name) {
155 global $element_path, $my_data, $imported_glossary;
156 static $current_term;
158 if ($element_path === array('glossary', 'item', 'term')) {
159 $current_term = $my_data;
161 } else if ($element_path === array('glossary', 'item', 'definition')) {
162 $imported_glossary[trim($current_term)] = trim($my_data);
165 array_pop($element_path);
169 function glossaryCharacterData($parser, $data){
175 if (!isset($_POST['submit']) && !isset($_POST['cancel'])) {
176 /* just a catch all */
178 $errors = array('FILE_MAX_SIZE', ini_get('post_max_size'));
179 $msg->addError($errors);
181 header('Location: ./index.php');
183 } else if (isset($_POST['cancel'])) {
184 $msg->addFeedback('IMPORT_CANCELLED');
186 header('Location: ./index.php');
190 $cid = intval($_POST['cid']);
192 if (isset($_POST['url']) && ($_POST['url'] != 'http://') ) {
193 if ($content = @file_get_contents($_POST['url'])) {
195 // save file to /content/
196 $filename = substr(time(), -6). '.zip';
197 $full_filename = AT_CONTENT_DIR . $filename;
199 if (!$fp = fopen($full_filename, 'w+b')) {
200 echo "Cannot open file ($filename)";
204 if (fwrite($fp, $content, strlen($content) ) === FALSE) {
205 echo "Cannot write to file ($filename)";
210 $_FILES['file']['name'] = $filename;
211 $_FILES['file']['tmp_name'] = $full_filename;
212 $_FILES['file']['size'] = strlen($content);
214 $url_parts = pathinfo($_POST['url']);
215 $package_base_name_url = $url_parts['basename'];
217 $ext = pathinfo($_FILES['file']['name']);
218 $ext = $ext['extension'];
221 $msg->addError('IMPORTDIR_IMS_NOTVALID');
222 } else if ($_FILES['file']['error'] == 1) {
223 $errors = array('FILE_MAX_SIZE', ini_get('upload_max_filesize'));
224 $msg->addError($errors);
225 } else if ( !$_FILES['file']['name'] || (!is_uploaded_file($_FILES['file']['tmp_name']) && !$_POST['url'])) {
226 $msg->addError('FILE_NOT_SELECTED');
227 } else if ($_FILES['file']['size'] == 0) {
228 $msg->addError('IMPORTFILE_EMPTY');
231 if ($msg->containsErrors()) {
232 if (isset($_GET['tile'])) {
233 header('Location: '.$_base_path.'tools/tile/index.php');
235 header('Location: index.php');
240 /* check if ../content/import/ exists */
241 $import_path = AT_CONTENT_DIR . 'import/';
242 $content_path = AT_CONTENT_DIR;
244 if (!is_dir($import_path)) {
245 if (!@mkdir($import_path, 0700)) {
246 $msg->addError('IMPORTDIR_FAILED');
250 $import_path .= $_SESSION['course_id'].'/';
251 if (is_dir($import_path)) {
252 clr_dir($import_path);
255 if (!@mkdir($import_path, 0700)) {
256 $msg->addError('IMPORTDIR_FAILED');
259 if ($msg->containsErrors()) {
260 if (isset($_GET['tile'])) {
261 header('Location: '.$_base_path.'tools/tile/index.php');
263 header('Location: index.php');
268 /* extract the entire archive into AT_COURSE_CONTENT . import/$course using the call back function to filter out php files */
270 $archive = new PclZip($_FILES['file']['tmp_name']);
271 if ($archive->extract( PCLZIP_OPT_PATH, $import_path,
272 PCLZIP_CB_PRE_EXTRACT, 'preImportCallBack') == 0) {
273 $msg->addError('IMPORT_FAILED');
274 // echo 'Error : '.$archive->errorInfo(true);
275 clr_dir($import_path);
276 header('Location: index.php');
279 error_reporting(E_ALL ^ E_NOTICE);
282 /* get the course's max_quota */
283 $sql = "SELECT max_quota FROM ".TABLE_PREFIX."courses WHERE course_id=$_SESSION[course_id]";
284 $result = mysql_query($sql, $db);
285 $q_row = mysql_fetch_assoc($result);
287 if ($q_row['max_quota'] != AT_COURSESIZE_UNLIMITED) {
289 if ($q_row['max_quota'] == AT_COURSESIZE_DEFAULT) {
290 $q_row['max_quota'] = $MaxCourseSize;
292 $totalBytes = dirsize($import_path);
293 $course_total = dirsize(AT_CONTENT_DIR . $_SESSION['course_id'].'/');
294 $total_after = $q_row['max_quota'] - $course_total - $totalBytes + $MaxCourseFloat;
296 if ($total_after < 0) {
297 /* remove the content dir, since there's no space for it */
298 $errors = array('NO_CONTENT_SPACE', number_format(-1*($total_after/AT_KBYTE_SIZE), 2 ) );
299 $msg->addError($errors);
301 clr_dir($import_path);
303 if (isset($_GET['tile'])) {
304 header('Location: '.$_base_path.'tools/tile/index.php');
306 header('Location: index.php');
313 $items = array(); /* all the content pages */
314 $order = array(); /* keeps track of the ordering for each content page */
315 $path = array(); /* the hierarchy path taken in the menu to get to the current item in the manifest */
318 $items[content_id/resource_id] = array(
320 'real_content_id' // calculated after being inserted
327 $ims_manifest_xml = @file_get_contents($import_path.'imsmanifest.xml');
329 if ($ims_manifest_xml === false) {
330 $msg->addError('NO_IMSMANIFEST');
332 if (file_exists($import_path . 'atutor_backup_version')) {
333 $msg->addError('NO_IMS_BACKUP');
336 clr_dir($import_path);
338 if (isset($_GET['tile'])) {
339 header('Location: '.$_base_path.'tools/tile/index.php');
341 header('Location: index.php');
348 // check if this is an eXe package
349 // NOTE: THIS NEEDS WORK! WHAT IS THE BEST WAY TO DETERMINE IF PACKAGE IS eXe?
350 // PERHAPS USE PARSER BELOW TO CHECK FOR ORGANIZATION?
351 $isExeContent = false;
352 $ims_organization_pos = strpos ($ims_manifest_xml, '<organization');
353 if ($ims_organization_pos !== false){
354 $ims_organization_pos = strpos ($ims_manifest_xml, 'identifier', $ims_organization_pos);
355 if ($ims_organization_pos !== false){
356 $exe_pos = strpos ($ims_manifest_xml, 'eXenew', $ims_organization_pos);
357 //if ($exe_pos < ($ims_organization_pos + '50')){
358 if ($exe_pos !== false){
359 $isExeContent = true;
367 $xml_parser = xml_parser_create();
369 xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, false); /* conform to W3C specs */
370 xml_set_element_handler($xml_parser, 'startElement', 'endElement');
371 xml_set_character_data_handler($xml_parser, 'characterData');
373 if (!xml_parse($xml_parser, $ims_manifest_xml, true)) {
374 die(sprintf("XML error: %s at line %d",
375 xml_error_string(xml_get_error_code($xml_parser)),
376 xml_get_current_line_number($xml_parser)));
380 xml_parser_free($xml_parser);
383 /* check if the glossary terms exist */
384 if (file_exists($import_path . 'glossary.xml')){
385 $glossary_xml = @file_get_contents($import_path.'glossary.xml');
386 $element_path = array();
388 $xml_parser = xml_parser_create();
390 /* insert the glossary terms into the database (if they're not in there already) */
391 /* parse the glossary.xml file and insert the terms */
392 xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, false); /* conform to W3C specs */
393 xml_set_element_handler($xml_parser, 'glossaryStartElement', 'glossaryEndElement');
394 xml_set_character_data_handler($xml_parser, 'glossaryCharacterData');
396 if (!xml_parse($xml_parser, $glossary_xml, true)) {
397 debug($glossary_xml);
398 die(sprintf("XML error: %s at line %d",
399 xml_error_string(xml_get_error_code($xml_parser)),
400 xml_get_current_line_number($xml_parser)));
402 xml_parser_free($xml_parser);
403 $contains_glossary_terms = true;
404 foreach ($imported_glossary as $term => $defn) {
405 if (!$glossary[urlencode($term)]) {
406 $sql = "INSERT INTO ".TABLE_PREFIX."glossary VALUES (NULL, $_SESSION[course_id], '$term', '$defn', 0)";
407 mysql_query($sql, $db);
413 /* generate a unique new package base path based on the package file name and date as needed. */
414 /* the package name will be the dir where the content for this package will be put, as a result */
415 /* the 'content_path' field in the content table will be set to this path. */
416 /* $package_base_name_url comes from the URL file name (NOT the file name of the actual file we open)*/
417 if (!$package_base_name && $package_base_name_url) {
418 $package_base_name = substr($package_base_name_url, 0, -4);
419 } else if (!$package_base_name) {
420 $package_base_name = substr($_FILES['file']['name'], 0, -4);
423 $package_base_name = strtolower($package_base_name);
424 $package_base_name = str_replace(array('\'', '"', ' ', '|', '\\', '/', '<', '>', ':'), '_' , $package_base_name);
425 $package_base_name = preg_replace("/[^A-Za-z0-9._\-]/", '', $package_base_name);
427 if (is_dir(AT_CONTENT_DIR . $_SESSION['course_id'].'/'.$package_base_name)) {
428 $package_base_name .= '_'.date('ymdHis');
432 if ($package_base_path) {
433 $package_base_path = implode('/', $package_base_path);
435 if ($xml_base_path) {
436 $package_base_path = $xml_base_path . $package_base_path;
438 mkdir(AT_CONTENT_DIR .$_SESSION['course_id'].'/'.$xml_base_path);
439 $package_base_name = $xml_base_path . $package_base_name;
443 /* get the top level content ordering offset */
444 $sql = "SELECT MAX(ordering) AS ordering FROM ".TABLE_PREFIX."content WHERE course_id=$_SESSION[course_id] AND content_parent_id=$cid";
445 $result = mysql_query($sql, $db);
446 $row = mysql_fetch_assoc($result);
447 $order_offset = intval($row['ordering']); /* it's nice to have a real number to deal with */
449 foreach ($items as $item_id => $content_info) {
450 if (isset($content_info['href'], $xml_base_path)) {
451 $content_info['href'] = $xml_base_path . $content_info['href'];
453 if (!isset($content_info['href'])) {
454 // this item doesn't have an identifierref. so create an empty page.
457 $last_modified = date('Y-m-d H:i:s');
459 $file_info = @stat(AT_CONTENT_DIR . 'import/'.$_SESSION['course_id'].'/'.$content_info['href']);
460 if ($file_info === false) {
464 $path_parts = pathinfo(AT_CONTENT_DIR . 'import/'.$_SESSION['course_id'].'/'.$content_info['href']);
465 $ext = strtolower($path_parts['extension']);
467 $last_modified = date('Y-m-d H:i:s', $file_info['mtime']);
469 if (in_array($ext, array('gif', 'jpg', 'bmp', 'png', 'jpeg'))) {
470 /* this is an image */
471 $content = '<img src="'.$content_info['href'].'" alt="'.$content_info['title'].'" />';
472 } else if ($ext == 'swf') {
474 /* Using default size of 550 x 400 */
476 $content = '<object type="application/x-shockwave-flash" data="' . $content_info['href'] . '" width="550" height="400"><param name="movie" value="'. $content_info['href'] .'" /></object>';
478 } else if ($ext == 'mov') {
479 /* this is a quicktime movie */
480 /* Using default size of 550 x 400 */
482 $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>';
483 } else if ($ext == 'mp3') {
484 $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>';
485 } else if (in_array($ext, array('wav', 'au'))) {
486 $content = '<embed SRC="'.$content_info['href'].'" autostart="false" width="145" height="60"><noembed><bgsound src="'.$content_info['href'].'"></noembed></embed>';
488 } else if (in_array($ext, array('txt', 'css', 'html', 'htm', 'csv', 'asc', 'tsv', 'xml', 'xsl'))) {
489 /* this is a plain text file */
490 $content = file_get_contents(AT_CONTENT_DIR . 'import/'.$_SESSION['course_id'].'/'.$content_info['href']);
491 if ($content === false) {
492 /* if we can't stat() it then we're unlikely to be able to read it */
493 /* so we'll never get here. */
497 // get the contents of the 'head' element
498 $head = get_html_head($content);
499 $content = get_html_body($content);
500 if ($contains_glossary_terms) {
501 // replace glossary content package links to real glossary mark-up using [?] [/?]
502 $content = preg_replace('/<a href="([.\w\d\s]+[^"]+)" target="body" class="at-term">([.\w\d\s&;"]+)<\/a>/i', '[?]\\2[/?]', $content);
505 /* potential security risk? */
506 if ( strpos($content_info['href'], '..') === false) {
507 @unlink(AT_CONTENT_DIR . 'import/'.$_SESSION['course_id'].'/'.$content_info['href']);
510 /* non text file, and can't embed (example: PDF files) */
511 $content = '<a href="'.$content_info['href'].'">'.$content_info['title'].'</a>';
514 $content_parent_id = $cid;
515 if ($content_info['parent_content_id'] !== 0) {
516 $content_parent_id = $items[$content_info['parent_content_id']]['real_content_id'];
520 if ($content_parent_id == $cid) {
521 $my_offset = $order_offset;
524 /* replace the old path greatest common denomiator with the new package path. */
525 /* we don't use str_replace, b/c there's no knowing what the paths may be */
526 /* we only want to replace the first part of the path. */
527 if ($package_base_path != '') {
528 $content_info['new_path'] = $package_base_name . substr($content_info['new_path'], strlen($package_base_path));
530 $content_info['new_path'] = $package_base_name;
533 $content_info['title'] = addslashes($content_info['title']);
534 $content = addslashes($content);
536 // add a 'div' around eXe content
537 if ($isExeContent == true){
538 $content = '<div class=\"execontent\">\n'.$content.'\n</div>';
541 $sql= 'INSERT INTO '.TABLE_PREFIX.'content VALUES
543 .$_SESSION['course_id'].','
544 .$content_parent_id.','
545 .($content_info['ordering'] + $my_offset + 1).','
546 .'"'.$last_modified.'",
547 0,1,NOW(),"","'.$content_info['new_path'].'",'
548 .'"'.$content_info['title'].'",'
551 $result = mysql_query($sql, $db);
553 // store the contents of the 'head' section
557 $id = mysql_insert_id($db);
558 $head = addslashes($head);
560 $sql = 'INSERT INTO '.TABLE_PREFIX.'head VALUES (NULL, '.$_SESSION['course_id'].','
563 $resultHead = mysql_query($sql, $db);
567 /* get the content id and update $items */
568 $items[$item_id]['real_content_id'] = mysql_insert_id($db);
571 if ($package_base_path == '.') {
572 $package_base_path = '';
575 if (@rename(AT_CONTENT_DIR . 'import/'.$_SESSION['course_id'].'/'.$package_base_path, AT_CONTENT_DIR .$_SESSION['course_id'].'/'.$package_base_name) === false) {
576 if (!$msg->containsErrors()) {
577 $msg->addError('IMPORT_FAILED');
580 clr_dir(AT_CONTENT_DIR . 'import/'.$_SESSION['course_id']);
582 if (isset($_POST['url'])) {
583 @unlink($full_filename);
587 if ($_POST['s_cid']){
588 if (!$msg->containsErrors()) {
589 $msg->addFeedback('ACTION_COMPLETED_SUCCESSFULLY');
591 header('Location: ../../editor/edit_content.php?cid='.intval($_POST['cid']));
594 if (!$msg->containsErrors()) {
595 $msg->addFeedback('ACTION_COMPLETED_SUCCESSFULLY');
598 header('Location: '.AT_BASE_HREF.'tools/tile/index.php');
600 header('Location: ./index.php?cid='.intval($_POST['cid']));