* Init request vars */ if (!empty($_REQUEST['relativePath']) && $_REQUEST['relativePath'] != '/' && $_REQUEST['relativePath'] != '.') { $relativePath = str_replace('..', '', $_REQUEST['relativePath']) . '/'; } else { $relativePath = '/'; } /* * Handle upload */ if ($is_allowedToEdit && isset($_FILES['sentFile']['tmp_name']) && is_uploaded_file($_FILES['sentFile']['tmp_name'])) { $imgFile = $_FILES['sentFile']; $imgFile['name'] = replace_dangerous_char($imgFile['name'], 'strict'); $imgFile['name'] = get_secure_file_name($imgFile['name']); if (claro_is_in_a_course()) { $enoughSize = enough_size($_FILES['sentFile']['size'], $pathSys, $maxFilledSpace); } else { $enoughSize = true; } if (is_image($imgFile['name']) && $enoughSize) { // rename if file already exists if (file_exists($pathSys . $relativePath . $imgFile['name'])) { $pieceList = explode('.', $imgFile['name']); $base = $pieceList[0]; $ext = $pieceList[1]; $i = 1; while (file_exists($pathSys . $relativePath . $base . '_' . $i . '.' . $ext)) { $i++; } $imgFile['name'] = $base . '_' . $i . '.' . $ext; $alertMessage = get_lang('A file with this name already exists.') . "\n" . get_lang('Your file has been renamed to %filename', array('%filename' => $imgFile['name']));
// this line will be removed if an error occurs $rankMax = 1 + intval(Database::get()->querySingle("SELECT MAX(`rank`) AS max\n FROM `lp_learnPath` WHERE `course_id` = ?d", $course_id)->max); $tempPathId = Database::get()->query("INSERT INTO `lp_learnPath`\n (`course_id`, `name`,`visible`,`rank`,`comment`)\n VALUES (?d, ?s, 0, ?d,'')", $course_id, $lpName, $rankMax)->lastInsertID; $baseWorkDir .= "path_" . $tempPathId; if (!is_dir($baseWorkDir)) { claro_mkdir($baseWorkDir, CLARO_FILE_PERMISSIONS); } // unzip package require_once "include/pclzip/pclzip.lib.php"; /* * Check if the file is valid (not to big and exists) */ if (!isset($_FILES['uploadedPackage']) || !is_uploaded_file($_FILES['uploadedPackage']['tmp_name'])) { $errorFound = true; array_push($errorMsgs, $langFileScormError); } elseif (!enough_size($_FILES['uploadedPackage']['size'], $baseWorkDir, $maxFilledSpace)) { $errorFound = true; array_push($errorMsgs, $langNoSpace); } elseif (preg_match("/.zip\$/i", $_FILES['uploadedPackage']['name'])) { array_push($okMsgs, $langOkFileReceived . basename($_FILES['uploadedPackage']['name'])); if (!function_exists('gzopen')) { $errorFound = true; array_push($errorMsgs, $langErrorNoZlibExtension); } else { $zipFile = new pclZip($_FILES['uploadedPackage']['tmp_name']); $is_allowedToUnzip = true; // default initialisation // Check the zip content (real size and file extension) $zipContentArray = $zipFile->listContent(); if ($zipContentArray == 0) { $errorFound = true;
/** * Imports a zip file (presumably AICC) into the Dokeos structure * @param string Zip file info as given by $_FILES['userFile'] * @return string Absolute path to the AICC config files directory or empty string on error */ function import_package($zip_file_info, $current_dir = '') { if ($this->debug > 0) { error_log('In aicc::import_package(' . print_r($zip_file_info, true) . ',"' . $current_dir . '") method', 0); } //ini_set('error_log','E_ALL'); $maxFilledSpace = 1000000000; $zip_file_path = $zip_file_info['tmp_name']; $zip_file_name = $zip_file_info['name']; if ($this->debug > 0) { error_log('New LP - aicc::import_package() - Zip file path = ' . $zip_file_path . ', zip file name = ' . $zip_file_name, 0); } $course_rel_dir = api_get_course_path() . '/scorm'; //scorm dir web path starting from /courses $course_sys_dir = api_get_path(SYS_COURSE_PATH) . $course_rel_dir; //absolute system path for this course $current_dir = replace_dangerous_char(trim($current_dir), 'strict'); //current dir we are in, inside scorm/ if ($this->debug > 0) { error_log('New LP - aicc::import_package() - Current_dir = ' . $current_dir, 0); } //$uploaded_filename = $_FILES['userFile']['name']; //get name of the zip file without the extension if ($this->debug > 0) { error_log('New LP - aicc::import_package() - Received zip file name: ' . $zip_file_path, 0); } $file_info = pathinfo($zip_file_name); $filename = $file_info['basename']; $extension = $file_info['extension']; $file_base_name = str_replace('.' . $extension, '', $filename); //filename without its extension $this->zipname = $file_base_name; //save for later in case we don't have a title if ($this->debug > 0) { error_log('New LP - aicc::import_package() - Base file name is : ' . $file_base_name, 0); } $new_dir = replace_dangerous_char(trim($file_base_name), 'strict'); $this->subdir = $new_dir; if ($this->debug > 0) { error_log('New LP - aicc::import_package() - Subdir is first set to : ' . $this->subdir, 0); } /* if( check_name_exist($course_sys_dir.$current_dir."/".$new_dir) ) { $dialogBox = get_lang('FileExists'); $stopping_error = true; } */ $zipFile = new pclZip($zip_file_path); // Check the zip content (real size and file extension) $zipContentArray = $zipFile->listContent(); $package_type = ''; //the type of the package. Should be 'aicc' after the next few lines $package = ''; //the basename of the config files (if 'courses.crs' => 'courses') $at_root = false; //check if the config files are at zip root $config_dir = ''; //the directory in which the config files are. May remain empty $files_found = array(); $subdir_isset = false; //the following loop should be stopped as soon as we found the right config files (.crs, .au, .des and .cst) foreach ($zipContentArray as $thisContent) { if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) { //if a php file is found, do not authorize (security risk) if ($this->debug > 1) { error_log('New LP - aicc::import_package() - Found unauthorized file: ' . $thisContent['filename'], 0); } return api_failure::set_failure('php_file_in_zip_file'); } elseif (preg_match('?.*/aicc/$?', $thisContent['filename'])) { //if a directory named 'aicc' is found, package type = aicc, but continue //because we need to find the right AICC files if ($this->debug > 1) { error_log('New LP - aicc::import_package() - Found aicc directory: ' . $thisContent['filename'], 0); } $package_type = 'aicc'; } else { //else, look for one of the files we're searching for (something.crs case insensitive) $res = array(); if (preg_match('?^(.*)\\.(crs|au|des|cst|ore|pre|cmp)$?i', $thisContent['filename'], $res)) { if ($this->debug > 1) { error_log('New LP - aicc::import_package() - Found AICC config file: ' . $thisContent['filename'] . '. Now splitting: ' . $res[1] . ' and ' . $res[2], 0); } if ($thisContent['filename'] == basename($thisContent['filename'])) { if ($this->debug > 2) { error_log('New LP - aicc::import_package() - ' . $thisContent['filename'] . ' is at root level', 0); } $at_root = true; if (!is_array($files_found[$res[1]])) { $files_found[$res[1]] = $this->config_exts; //initialise list of expected extensions (defined in class definition) } $files_found[$res[1]][strtolower($res[2])] = $thisContent['filename']; $subdir_isset = true; } else { if (!$subdir_isset) { if (preg_match('?^.*/aicc$?i', dirname($thisContent['filename']))) { //echo "Cutting subdir<br/>"; $this->subdir .= '/' . substr(dirname($thisContent['filename']), 0, -5); } else { //echo "Not cutting subdir<br/>"; $this->subdir .= '/' . dirname($thisContent['filename']); } $subdir_isset = true; } if ($this->debug > 2) { error_log('New LP - aicc::import_package() - ' . $thisContent['filename'] . ' is not at root level - recording subdir ' . $this->subdir, 0); } $config_dir = dirname($thisContent['filename']); //just the relative directory inside scorm/ if (!is_array($files_found[basename($res[1])])) { $files_found[basename($res[1])] = $this->config_exts; } $files_found[basename($res[1])][strtolower($res[2])] = basename($thisContent['filename']); } $package_type = 'aicc'; } else { if ($this->debug > 3) { error_log('New LP - aicc::import_package() - File ' . $thisContent['filename'] . ' didnt match any check', 0); } } } $realFileSize += $thisContent['size']; } if ($this->debug > 2) { error_log('New LP - aicc::import_package() - $files_found: ' . print_r($files_found, true), 0); } if ($this->debug > 1) { error_log('New LP - aicc::import_package() - Package type is now ' . $package_type, 0); } $mandatory = false; foreach ($files_found as $file_name => $file_exts) { $temp = (!empty($files_found[$file_name]['crs']) and !empty($files_found[$file_name]['au']) and !empty($files_found[$file_name]['des']) and !empty($files_found[$file_name]['cst'])); if ($temp) { if ($this->debug > 1) { error_log('New LP - aicc::import_package() - Found all config files for ' . $file_name, 0); } $mandatory = true; $package = $file_name; //store base config file name for reuse in parse_config_files() $this->config_basename = $file_name; //store filenames for reuse in parse_config_files() $this->config_files = $files_found[$file_name]; //get out, we only want one config files set break; } } if ($package_type == '' or $mandatory != true) { return api_failure::set_failure('not_aicc_content'); } if (!enough_size($realFileSize, $course_sys_dir, $maxFilledSpace)) { return api_failure::set_failure('not_enough_space'); } // it happens on Linux that $new_dir sometimes doesn't start with '/' if ($new_dir[0] != '/') { $new_dir = '/' . $new_dir; } //cut trailing slash if ($new_dir[strlen($new_dir) - 1] == '/') { $new_dir = substr($new_dir, 0, -1); } /* -------------------------------------- Uncompressing phase -------------------------------------- */ /* We need to process each individual file in the zip archive to - add it to the database - parse & change relative html links - make sure the filenames are secure (filter funny characters or php extensions) */ if (is_dir($course_sys_dir . $new_dir) or @mkdir($course_sys_dir . $new_dir)) { // PHP method - slower... if ($this->debug >= 1) { error_log('New LP - Changing dir to ' . $course_sys_dir . $new_dir, 0); } $saved_dir = getcwd(); chdir($course_sys_dir . $new_dir); $unzippingState = $zipFile->extract(); for ($j = 0; $j < count($unzippingState); $j++) { $state = $unzippingState[$j]; //TODO fix relative links in html files (?) $extension = strrchr($state["stored_filename"], "."); //if($this->debug>1){error_log('New LP - found extension '.$extension.' in '.$state['stored_filename'],0);} } if (!empty($new_dir)) { $new_dir = $new_dir . '/'; } //rename files, for example with \\ in it if ($dir = @opendir($course_sys_dir . $new_dir)) { if ($this->debug == 1) { error_log('New LP - Opened dir ' . $course_sys_dir . $new_dir, 0); } while ($file = readdir($dir)) { if ($file != '.' && $file != '..') { $filetype = "file"; if (is_dir($course_sys_dir . $new_dir . $file)) { $filetype = "folder"; } //TODO RENAMING FILES CAN BE VERY DANGEROUS AICC-WISE, avoid that as much as possible! //$safe_file=replace_dangerous_char($file,'strict'); $find_str = array('\\', '.php', '.phtml'); $repl_str = array('/', '.txt', '.txt'); $safe_file = str_replace($find_str, $repl_str, $file); if ($safe_file != $file) { //@rename($course_sys_dir.$new_dir,$course_sys_dir.'/'.$safe_file); $mydir = dirname($course_sys_dir . $new_dir . $safe_file); if (!is_dir($mydir)) { $mysubdirs = split('/', $mydir); $mybasedir = '/'; foreach ($mysubdirs as $mysubdir) { if (!empty($mysubdir)) { $mybasedir = $mybasedir . $mysubdir . '/'; if (!is_dir($mybasedir)) { @mkdir($mybasedir); if ($this->debug == 1) { error_log('New LP - Dir ' . $mybasedir . ' doesnt exist. Creating.', 0); } } } } } @rename($course_sys_dir . $new_dir . $file, $course_sys_dir . $new_dir . $safe_file); if ($this->debug == 1) { error_log('New LP - Renaming ' . $course_sys_dir . $new_dir . $file . ' to ' . $course_sys_dir . $new_dir . $safe_file, 0); } } //set_default_settings($course_sys_dir,$safe_file,$filetype); } } closedir($dir); chdir($saved_dir); } } else { return ''; } return $course_sys_dir . $new_dir . $config_dir; }
/** * Imports a zip file into the Chamilo structure * @param string $zip_file_info Zip file info as given by $_FILES['userFile'] * @return string $current_dir Absolute path to the imsmanifest.xml file or empty string on error */ public function import_package($zip_file_info, $current_dir = '') { if ($this->debug > 0) { error_log('In scorm::import_package(' . print_r($zip_file_info, true) . ',"' . $current_dir . '") method', 0); } $maxFilledSpace = DocumentManager::get_course_quota(); $zip_file_path = $zip_file_info['tmp_name']; $zip_file_name = $zip_file_info['name']; if ($this->debug > 1) { error_log('New LP - import_package() - zip file path = ' . $zip_file_path . ', zip file name = ' . $zip_file_name, 0); } $course_rel_dir = api_get_course_path() . '/scorm'; // scorm dir web path starting from /courses $course_sys_dir = api_get_path(SYS_COURSE_PATH) . $course_rel_dir; // Absolute system path for this course. $current_dir = api_replace_dangerous_char(trim($current_dir)); // Current dir we are in, inside scorm/ if ($this->debug > 1) { error_log('New LP - import_package() - current_dir = ' . $current_dir, 0); } // Get name of the zip file without the extension. if ($this->debug > 1) { error_log('New LP - Received zip file name: ' . $zip_file_path, 0); } $file_info = pathinfo($zip_file_name); $filename = $file_info['basename']; $extension = $file_info['extension']; $file_base_name = str_replace('.' . $extension, '', $filename); // Filename without its extension. $this->zipname = $file_base_name; // Save for later in case we don't have a title. if ($this->debug > 1) { error_log("New LP - base file name is : " . $file_base_name, 0); } $new_dir = api_replace_dangerous_char(trim($file_base_name)); $this->subdir = $new_dir; if ($this->debug > 1) { error_log("New LP - subdir is first set to : " . $this->subdir, 0); } $zipFile = new PclZip($zip_file_path); // Check the zip content (real size and file extension). $zipContentArray = $zipFile->listContent(); $package_type = ''; $at_root = false; $manifest = ''; $manifest_list = array(); // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?). $realFileSize = 0; foreach ($zipContentArray as $thisContent) { $thisContent['filename']; if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) { $file = $thisContent['filename']; $this->set_error_msg("File {$file} contains a PHP script"); } elseif (stristr($thisContent['filename'], 'imsmanifest.xml')) { //error_log('Found imsmanifest at '.$thisContent['filename'], 0); if ($thisContent['filename'] == basename($thisContent['filename'])) { $at_root = true; } else { if ($this->debug > 2) { error_log("New LP - subdir is now " . $this->subdir, 0); } } $package_type = 'scorm'; $manifest_list[] = $thisContent['filename']; $manifest = $thisContent['filename']; //just the relative directory inside scorm/ } else { // Do nothing, if it has not been set as scorm somewhere else, it stays as '' default. } $realFileSize += $thisContent['size']; } // Now get the shortest path (basically, the imsmanifest that is the closest to the root). $shortest_path = $manifest_list[0]; $slash_count = substr_count($shortest_path, '/'); foreach ($manifest_list as $manifest_path) { $tmp_slash_count = substr_count($manifest_path, '/'); if ($tmp_slash_count < $slash_count) { $shortest_path = $manifest_path; $slash_count = $tmp_slash_count; } } $this->subdir .= '/' . dirname($shortest_path); // Do not concatenate because already done above. $manifest = $shortest_path; if ($this->debug > 1) { error_log('New LP - Package type is now ' . $package_type, 0); } if ($package_type == '') { // && defined('CHECK_FOR_SCORM') && CHECK_FOR_SCORM) if ($this->debug > 1) { error_log('New LP - Package type is empty', 0); } return api_failure::set_failure('not_scorm_content'); } if (!enough_size($realFileSize, $course_sys_dir, $maxFilledSpace)) { if ($this->debug > 1) { error_log('New LP - Not enough space to store package', 0); } return api_failure::set_failure('not_enough_space'); } // It happens on Linux that $new_dir sometimes doesn't start with '/' if ($new_dir[0] != '/') { $new_dir = '/' . $new_dir; } if ($new_dir[strlen($new_dir) - 1] == '/') { $new_dir = substr($new_dir, 0, -1); } /* Uncompressing phase */ /* We need to process each individual file in the zip archive to - add it to the database - parse & change relative html links - make sure the filenames are secure (filter funny characters or php extensions) */ if (is_dir($course_sys_dir . $new_dir) or @mkdir($course_sys_dir . $new_dir, api_get_permissions_for_new_directories())) { // PHP method - slower... if ($this->debug >= 1) { error_log('New LP - Changing dir to ' . $course_sys_dir . $new_dir, 0); } $saved_dir = getcwd(); chdir($course_sys_dir . $new_dir); $unzippingState = $zipFile->extract(); for ($j = 0; $j < count($unzippingState); $j++) { $state = $unzippingState[$j]; // TODO: Fix relative links in html files (?) $extension = strrchr($state['stored_filename'], '.'); if ($this->debug >= 1) { error_log('New LP - found extension ' . $extension . ' in ' . $state['stored_filename'], 0); } } if (!empty($new_dir)) { $new_dir = $new_dir . '/'; } // Rename files, for example with \\ in it. if ($this->debug >= 1) { error_log('New LP - try to open: ' . $course_sys_dir . $new_dir, 0); } if ($dir = @opendir($course_sys_dir . $new_dir)) { if ($this->debug >= 1) { error_log('New LP - Opened dir ' . $course_sys_dir . $new_dir, 0); } while ($file = readdir($dir)) { if ($file != '.' && $file != '..') { $filetype = 'file'; if (is_dir($course_sys_dir . $new_dir . $file)) { $filetype = 'folder'; } // TODO: RENAMING FILES CAN BE VERY DANGEROUS SCORM-WISE, avoid that as much as possible! //$safe_file = api_replace_dangerous_char($file, 'strict'); $find_str = array('\\', '.php', '.phtml'); $repl_str = array('/', '.txt', '.txt'); $safe_file = str_replace($find_str, $repl_str, $file); if ($this->debug >= 1) { error_log('Comparing: ' . $safe_file, 0); } if ($this->debug >= 1) { error_log('and: ' . $file, 0); } if ($safe_file != $file) { $mydir = dirname($course_sys_dir . $new_dir . $safe_file); if (!is_dir($mydir)) { $mysubdirs = explode('/', $mydir); $mybasedir = '/'; foreach ($mysubdirs as $mysubdir) { if (!empty($mysubdir)) { $mybasedir = $mybasedir . $mysubdir . '/'; if (!is_dir($mybasedir)) { @mkdir($mybasedir, api_get_permissions_for_new_directories()); if ($this->debug >= 1) { error_log('New LP - Dir ' . $mybasedir . ' doesnt exist. Creating.', 0); } } } } } @rename($course_sys_dir . $new_dir . $file, $course_sys_dir . $new_dir . $safe_file); if ($this->debug >= 1) { error_log('New LP - Renaming ' . $course_sys_dir . $new_dir . $file . ' to ' . $course_sys_dir . $new_dir . $safe_file, 0); } } } } closedir($dir); chdir($saved_dir); api_chmod_R($course_sys_dir . $new_dir, api_get_permissions_for_new_directories()); if ($this->debug > 1) { error_log('New LP - changed back to init dir: ' . $course_sys_dir . $new_dir, 0); } } } else { return ''; } return $course_sys_dir . $new_dir . $manifest; }
/** * unzip safly a zipfile * * @author Hugues Peeters <*****@*****.**> * * @param string $fileName file name of zip * @param string $filePath file path of zip * @param string $extractPath * @param integer $maxFilledSpace (byte) count of byte size aivailable * @param boolean $allowPHP whether True the file can't contain php or phtml files * @return true * @throws claro_failure on error */ function treat_secure_file_unzip($fileName, $filePath, $extractPath, $maxFilledSpace, $allowPHP = false) { $zipFile = new pclZip($fileName); // Check the zip content (real size and file extension) $zipContentArray = $zipFile->listContent(); if (!is_array($zipContentArray)) { return false; } foreach ($zipContentArray as $thisContent) { if (!$allowPHP) { if (preg_match('~.(php.?|phtml)$~i', $thisContent['filename'])) { return claro_failure::set_failure(get_lang('The zip file can not contain .PHP files')); } } if (!isset($realFileSize)) { $realFileSize = 0; } $realFileSize += $thisContent['size']; } if (!enough_size($realFileSize, $extractPath, $maxFilledSpace)) { return claro_failure::set_failure(get_lang('The upload has failed. There is not enough space in your directory')); } $extractedFileNameList = $zipFile->extract(PCLZIP_OPT_PATH, $extractPath . $filePath, PCLZIP_OPT_SET_CHMOD, CLARO_FILE_PERMISSIONS); if (is_array($extractedFileNameList)) { return $extractedFileNameList; } else { return false; } }
/** * Manages all the unzipping process of an uploaded file * * @author Hugues Peeters <*****@*****.**> * * @param array $uploaded_file - follows the $_FILES Structure * @param string $upload_path - destination of the upload. * This path is to append to $base_work_dir * @param string $base_work_dir - base working directory of the module * @param int $max_filled_space - amount of bytes to not exceed in the base * working directory * * @return boolean true if it succeeds false otherwise */ function unzip_uploaded_file($uploaded_file, $upload_path, $base_work_dir, $max_filled_space) { $zip_file = new PclZip($uploaded_file['tmp_name']); // Check the zip content (real size and file extension) if (file_exists($uploaded_file['tmp_name'])) { $zip_content_array = $zip_file->listContent(); $ok_scorm = false; $realFileSize = 0; foreach ($zip_content_array as &$this_content) { if (preg_match('~.(php.*|phtml)$~i', $this_content['filename'])) { return api_failure::set_failure('php_file_in_zip_file'); } elseif (stristr($this_content['filename'], 'imsmanifest.xml')) { $ok_scorm = true; } elseif (stristr($this_content['filename'], 'LMS')) { $ok_plantyn_scorm1 = true; } elseif (stristr($this_content['filename'], 'REF')) { $ok_plantyn_scorm2 = true; } elseif (stristr($this_content['filename'], 'SCO')) { $ok_plantyn_scorm3 = true; } elseif (stristr($this_content['filename'], 'AICC')) { $ok_aicc_scorm = true; } $realFileSize += $this_content['size']; } if ($ok_plantyn_scorm1 && $ok_plantyn_scorm2 && $ok_plantyn_scorm3 || $ok_aicc_scorm) { $ok_scorm = true; } if (!$ok_scorm && defined('CHECK_FOR_SCORM') && CHECK_FOR_SCORM) { return api_failure::set_failure('not_scorm_content'); } if (!enough_size($realFileSize, $base_work_dir, $max_filled_space)) { return api_failure::set_failure('not_enough_space'); } // It happens on Linux that $upload_path sometimes doesn't start with '/' if ($upload_path[0] != '/' && substr($base_work_dir, -1, 1) != '/') { $upload_path = '/' . $upload_path; } if ($upload_path[strlen($upload_path) - 1] == '/') { $upload_path = substr($upload_path, 0, -1); } /* Uncompressing phase */ /* The first version, using OS unzip, is not used anymore because it does not return enough information. We need to process each individual file in the zip archive to - add it to the database - parse & change relative html links */ if (PHP_OS == 'Linux' && !get_cfg_var('safe_mode') && false) { // *** UGent, changed by OC *** // Shell Method - if this is possible, it gains some speed exec("unzip -d \"" . $base_work_dir . $upload_path . "/\"" . $uploaded_file['name'] . " " . $uploaded_file['tmp_name']); } else { // PHP method - slower... $save_dir = getcwd(); chdir($base_work_dir . $upload_path); $unzippingState = $zip_file->extract(); for ($j = 0; $j < count($unzippingState); $j++) { $state = $unzippingState[$j]; // Fix relative links in html files $extension = strrchr($state['stored_filename'], '.'); } if ($dir = @opendir($base_work_dir . $upload_path)) { while ($file = readdir($dir)) { if ($file != '.' && $file != '..') { $filetype = 'file'; if (is_dir($base_work_dir . $upload_path . '/' . $file)) { $filetype = 'folder'; } $safe_file = api_replace_dangerous_char($file, 'strict'); @rename($base_work_dir . $upload_path . '/' . $file, $base_work_dir . $upload_path . '/' . $safe_file); set_default_settings($upload_path, $safe_file, $filetype); } } closedir($dir); } else { error_log('Could not create directory ' . $base_work_dir . $upload_path . ' to unzip files'); } chdir($save_dir); // Back to previous dir position } } return true; }
function doImport($course_code, $webDir, $scoFileSize, $scoFileName, $displayExtraMessages = false) { global $langUnamedPath; global $langFileScormError; global $langNotice; global $langMaxFileSize; global $langNoSpace; global $langOkFileReceived; global $langErrorNoZlibExtension; global $langErrorReadingZipFile; global $langZipNoPhp; global $langErrortExtractingManifest; global $langErrorFileMustBeZip; global $langErrorOpeningManifest; global $langOkManifestFound; global $langErrorReadingManifest; global $langOkManifestRead; global $langErrorAssetNotFound; global $langErrorNoModuleInPackage; global $langErrorSql; global $langOkChapterHeadAdded; global $langUnamedModule; global $langDefaultModuleComment; global $langDefaultModuleAddedComment; global $langOkModuleAdded; global $langOkDefaultTitleUsed; global $langDefaultLearningPathComment; global $langOkDefaultCommentUsed; global $langSuccessOk; global $langError; global $langInstalled; global $langNotInstalled; global $langBack; global $errorFound; global $elementsPile; global $itemsPile; global $manifestData; global $iterator; global $course_code; global $course_id; global $langErrorValidatingManifest; global $urlServer; $pwd = getcwd(); // init msg arays $okMsgs = array(); $errorMsgs = array(); $maxFilledSpace = 100000000; $courseDir = "/courses/" . $course_code . "/scormPackages/"; $tempDir = "/courses/" . $course_code . "/temp/"; $baseWorkDir = $webDir . $courseDir; // path_id $tempWorkDir = $webDir . $tempDir; if (!is_dir($baseWorkDir)) { claro_mkdir($baseWorkDir, CLARO_FILE_PERMISSIONS); } // arrays used to store inserted ids $insertedModule_id = array(); $insertedAsset_id = array(); $lpName = $langUnamedPath; // we need a new path_id for this learning path so we prepare a line in DB // this line will be removed if an error occurs $rankMax = 1 + intval(Database::get()->querySingle("SELECT MAX(`rank`) AS max FROM `lp_learnPath` WHERE `course_id` = ?d", $course_id)->max); $tempPathId = Database::get()->query("INSERT INTO `lp_learnPath` (`course_id`, `name`,`visible`,`rank`,`comment`) VALUES (?d, ?s, 0, ?d,'')", $course_id, $lpName, $rankMax)->lastInsertID; $baseWorkDir .= "path_" . $tempPathId; if (!is_dir($baseWorkDir)) { claro_mkdir($baseWorkDir, CLARO_FILE_PERMISSIONS); } // unzip package require_once("include/pclzip/pclzip.lib.php"); /* * Check the file size doesn't exceed * the maximum file size authorized in the directory */ if (!enough_size($scoFileSize, $baseWorkDir, $maxFilledSpace)) { $errorFound = true; array_push($errorMsgs, $langNoSpace); } /* * Unzipping stage */ elseif (preg_match("/.zip$/i", $scoFileName)) { array_push($okMsgs, $langOkFileReceived . basename($scoFileName)); if (!function_exists('gzopen')) { $errorFound = true; array_push($errorMsgs, $langErrorNoZlibExtension); } else { $zipFile = new pclZip($tempWorkDir . $scoFileName); $is_allowedToUnzip = true; // default initialisation // Check the zip content (real size and file extension) $zipContentArray = $zipFile->listContent(); if ($zipContentArray == 0) { $errorFound = true; array_push($errorMsgs, $langErrorReadingZipFile); } $pathToManifest = ""; // empty by default because we can expect that the manifest.xml is in the root of zip file $pathToManifestFound = false; $realFileSize = 0; foreach ($zipContentArray as $thisContent) { if (preg_match('/.(php[[:digit:]]?|phtml)$/i', $thisContent['filename'])) { $errorFound = true; array_push($errorMsgs, $langZipNoPhp); $is_allowedToUnzip = false; break; } if (strtolower(substr($thisContent['filename'], -15)) == "imsmanifest.xml") { // this check exists to find the less deep imsmanifest.xml in the zip if there are several imsmanifest.xml // if this is the first imsmanifest.xml we found OR path to the new manifest found is shorter (less deep) if (!$pathToManifestFound || ( count(explode('/', $thisContent['filename'])) < count(explode('/', $pathToManifest . "imsmanifest.xml")) ) ) { $pathToManifest = substr($thisContent['filename'], 0, -15); $pathToManifestFound = true; } } $realFileSize += $thisContent['size']; } if (!isset($alreadyFilledSpace)) { $alreadyFilledSpace = 0; } if (($realFileSize + $alreadyFilledSpace) > $maxFilledSpace) { // check the real size. $errorFound = true; array_push($errorMsgs, $langNoSpace); $is_allowedToUnzip = false; } if ($is_allowedToUnzip && !$errorFound) { // PHP extraction of zip file using zlib chdir($baseWorkDir); $unzippingState = $zipFile->extract(PCLZIP_OPT_BY_NAME, $pathToManifest . "imsmanifest.xml", PCLZIP_OPT_PATH, '', PCLZIP_OPT_REMOVE_PATH, $pathToManifest); if ($unzippingState == 0) { $errorFound = true; array_push($errorMsgs, $langErrortExtractingManifest); } } //end of if ($is_allowedToUnzip) } // end of if (!function_exists... } else { $errorFound = true; array_push($errorMsgs, $langErrorFileMustBeZip . ": " . basename($scoFileName)); } // find xmlmanifest (must be in root else ==> cancel operation, delete files) // parse xml manifest to find : // package name - learning path name // SCO list // start asset path if (!$errorFound) { $elementsPile = array(); // array used to remember where we are in the arborescence of the XML file $itemsPile = array(); // array used to remember parents items // declaration of global arrays used for extracting needed info from manifest for the new modules/SCO $manifestData = array(); // for global data of the learning path $manifestData['items'] = array(); // item tags content (attributes + some child elements data (title for an example) $manifestData['scos'] = array(); // for path of start asset id of each new module to create $iterator = 0; // will be used to increment position of paths in manifestData['scosPaths"] // and to have the names at the same pos if found //$xml_parser = xml_parser_create(); $xml_parser = xml_parser_create('utf-8'); xml_set_element_handler($xml_parser, "startElement", "endElement"); xml_set_character_data_handler($xml_parser, "elementData"); // this file has to exist in a SCORM conformant package // this file must be in the root the sent zip $file = "imsmanifest.xml"; if (!($fp = @fopen($file, "r"))) { $errorFound = true; array_push($errorMsgs, $langErrorOpeningManifest); } else { if (!isset($manifestPath)) { $manifestPath = ""; } array_push($okMsgs, $langOkManifestFound . $manifestPath . "imsmanifest.xml"); while ($data = str_replace("\n", "", fread($fp, 4096))) { // fix for fread breaking thing // msg from "ml at csite dot com" 02-Jul-2003 02:29 on http://www.php.net/xml // preg expression has been modified to match tag with inner attributes if (!isset($cache)) { $cache = ""; } $data = $cache . $data; if (!feof($fp)) { // search fo opening, closing, empty tags (with or without attributes) if (preg_match_all("/<[^\>]*.>/", $data, $regs)) { $lastTagname = $regs[0][count($regs[0]) - 1]; $split = false; for ($i = strlen($data) - strlen($lastTagname); $i >= strlen($lastTagname); $i--) { if ($lastTagname == substr($data, $i, strlen($lastTagname))) { $cache = substr($data, $i, strlen($data)); $data = substr($data, 0, $i); $split = true; break; } } } if (!$split) { $cache = $data; } } // end of fix if (!xml_parse($xml_parser, $data, feof($fp))) { // if reading of the xml file in not successfull : // set errorFound, set error msg, break while statement $errorFound = true; array_push($errorMsgs, $langErrorReadingManifest); // Since manifest.xml cannot be parsed, test versus IMS CP 1.4.4 XSD (compatible with all SCORM packages as well) require_once 'include/validateXML.php'; libxml_use_internal_errors(true); $xml = new DOMDocument(); $xml->load($manifestPath."imsmanifest.xml"); if (!$xml->schemaValidate($urlServer . 'modules/learnPath/export/imscp_v1p2.xsd')) { $messages = libxml_display_errors(); array_push($errorMsgs, $langErrorValidatingManifest . $messages); } break; } } // close file fclose($fp); } // liberate parser ressources xml_parser_free($xml_parser); } //if (!$errorFound) // check if all starts assets files exist in the zip file if (!$errorFound) { array_push($okMsgs, $langOkManifestRead); if (sizeof($manifestData['items']) > 0) { // if there is items in manifest we look for sco type resources referenced in idientifierref foreach ($manifestData['items'] as $item) { if (!isset($item['identifierref']) || $item['identifierref'] == '') { break; // skip if no ressource reference in item (item is probably a chapter head) } // find the file in the zip file $scoPathFound = false; for ($i = 0; $i < sizeof($zipContentArray); $i++) { if (isset($manifestData['scos'][$item['identifierref']]['xml:base'])) { $extraPath = $manifestData['scos'][$item['identifierref']]['xml:base']; } else if (isset($manifestData['assets'][$item['identifierref']]['xml:base'])) { $extraPath = $manifestData['assets'][$item['identifierref']]['xml:base']; } else { $extraPath = ""; } if (isset($zipContentArray[$i]["filename"]) && ( ( isset($manifestData['scos'][$item['identifierref']]['href']) && $zipContentArray[$i]["filename"] == $pathToManifest . $extraPath . $manifestData['scos'][$item['identifierref']]['href']) || (isset($manifestData['assets'][$item['identifierref']]['href']) && $zipContentArray[$i]["filename"] == $pathToManifest . $extraPath . $manifestData['assets'][$item['identifierref']]['href']) ) ) { $scoPathFound = true; break; } } if (!$scoPathFound) { $errorFound = true; array_push($errorMsgs, $langErrorAssetNotFound . $manifestData['scos'][$item['identifierref']]['href']); break; } } } //if (sizeof ...) elseif (sizeof($manifestData['scos']) > 0) { // if there ie no items in the manifest file // check for scos in resources foreach ($manifestData['scos'] as $sco) { // find the file in the zip file // create a fake item so that the rest of the procedure (add infos of in db) can remains the same $manifestData['items'][$sco['href']]['identifierref'] = $sco['href']; $manifestData['items'][$sco['href']]['parameters'] = ''; $manifestData['items'][$sco['href']]['isvisible'] = "true"; $manifestData['items'][$sco['href']]['title'] = $sco['title']; $manifestData['items'][$sco['href']]['description'] = $sco['description']; $manifestData['items'][$attributes['IDENTIFIER']]['parent'] = 0; $scoPathFound = false; for ($i = 0; $i < sizeof($zipContentArray); $i++) { if ($zipContentArray[$i]["filename"] == $sco['href']) { $scoPathFound = true; break; } } if (!$scoPathFound) { $errorFound = true; array_push($errorMsgs, $langErrorAssetNotFound . $sco['href']); break; } } } // if sizeof (...ΰ else { $errorFound = true; array_push($errorMsgs, $langErrorNoModuleInPackage); } }// if errorFound // unzip all files // && // insert corresponding entries in database if (!$errorFound) { // PHP extraction of zip file using zlib chdir($baseWorkDir); // PCLZIP_OPT_PATH is the path where files will be extracted ( '' ) // PLZIP_OPT_REMOVE_PATH suppress a part of the path of the file ( $pathToManifest ) // the result is that the manifest is in th eroot of the path_# directory and all files will have a path related to the root $unzippingState = $zipFile->extract(PCLZIP_OPT_PATH, '', PCLZIP_OPT_REMOVE_PATH, $pathToManifest); // insert informations in DB : // - 1 learning path ( already added because we needed its id to create the package directory ) // - n modules // - n asset as start asset of modules if (sizeof($manifestData['items']) == 0) { $errorFound = true; array_push($errorMsgs, $langErrorNoModuleInPackage); } else { $i = 0; $insertedLPMid = array(); // array of learnPath_module_id && order of related group $inRootRank = 1; // default rank for root module (parent == 0) foreach ($manifestData['items'] as $item) { if (isset($item['parent']) && isset($insertedLPMid[$item['parent']])) { $parent = $insertedLPMid[$item['parent']]['LPMid']; $rank = $insertedLPMid[$item['parent']]['rank'] ++; } else { $parent = 0; $rank = $inRootRank++; } //------------------------------------------------------------------------------- // add chapter head //------------------------------------------------------------------------------- if ((!isset($item['identifierref']) || $item['identifierref'] == '') && isset($item['title']) && $item['title'] != '') { // add title as a module $chapterTitle = $item['title']; // array of all inserted module ids $insertedModule_id[$i] = Database::get()->query("INSERT INTO `lp_module` (`course_id`, `name`, `comment`, `contentType`, `launch_data`) VALUES (?d, ?s, '', ?s,'')", $course_id, $chapterTitle, CTLABEL_)->lastInsertID; if (!$insertedModule_id[$i]) { $errorFound = true; array_push($errorMsgs, $langErrorSql); break; } // visibility if (isset($item['isvisible']) && $item['isvisible'] != '') { $visibility = ($item['isvisible'] == "true") ? 1 : 0; } else { $visibility = 1; // IMS consider that the default value of 'isvisible' is true } // add title module in the learning path // finally : insert in learning path // get the inserted id of the learnPath_module rel to allow 'parent' link in next inserts $insertedLPMid[$item['itemIdentifier']]['LPMid'] = Database::get()->query("INSERT INTO `lp_rel_learnPath_module` (`learnPath_id`, `module_id`,`rank`, `visible`, `parent`) VALUES (?d, ?d, ?d, ?d, ?d)", $tempPathId, $insertedModule_id[$i], $rank, $visibility, $parent)->lastInsertID; $insertedLPMid[$item['itemIdentifier']]['rank'] = 1; if (!$insertedLPMid[$item['itemIdentifier']]['LPMid']) { $errorFound = true; array_push($errorMsgs, $langErrorSql); break; } if (!$errorFound) { array_push($okMsgs, $langOkChapterHeadAdded . "<i>" . $chapterTitle . "</i>"); } $i++; continue; } // use found title of module or use default title if (!isset($item['title']) || $item['title'] == '') { $moduleName = $langUnamedModule; } else { $moduleName = $item['title']; } // set description as comment or default comment // look fo description in item description or in sco (resource) description // don't remember why I checked for parameters string ... so comment it if ((!isset($item['description']) || $item['description'] == '' ) && (!isset($manifestData['scos'][$item['identifierref']]['description']) /* || $manifestData['scos'][$item['identifierref']]['parameters'] == '' */ ) ) { $description = $langDefaultModuleComment; } else { if (isset($item['description']) && $item['description'] != '') { $description = $item['description']; } else { $description = $manifestData['scos'][$item['identifierref']]['description']; } } // insert modules and their start asset // create new module if (!isset($item['datafromlms'])) { $item['datafromlms'] = ""; } // elegxoume an to contentType prepei na einai scorm h asset if (isset($manifestData['scos'][$item['identifierref']]['contentTypeFlag']) && $manifestData['scos'][$item['identifierref']]['contentTypeFlag'] == CTSCORMASSET_) { $contentType = CTSCORMASSET_; } else { $contentType = CTSCORM_; } // array of all inserted module ids $insertedModule_id[$i] = Database::get()->query("INSERT INTO `lp_module` (`course_id`, `name`, `comment`, `contentType`, `launch_data`) VALUES (?d, ?s, ?s, ?s, ?s)", $course_id, $moduleName, $description, $contentType, $item['datafromlms'])->lastInsertID; if (!$insertedModule_id[$i]) { $errorFound = true; array_push($errorMsgs, $langErrorSql); break; } // build asset path // a $manifestData['scos'][$item['identifierref']] __SHOULD__ not exist if a $manifestData['assets'][$item['identifierref']] exists // so according to IMS we can say that one is empty if the other is filled, so we concat them without more verification than if the var exists. // suppress notices if (!isset($manifestData['xml:base']['manifest'])) { $manifestData['xml:base']['manifest'] = ""; } if (!isset($manifestData['xml:base']['ressources'])) { $manifestData['xml:base']['ressources'] = ""; } if (!isset($manifestData['scos'][$item['identifierref']]['href'])) { $manifestData['scos'][$item['identifierref']]['href'] = ""; } if (!isset($manifestData['assets'][$item['identifierref']]['href'])) { $manifestData['assets'][$item['identifierref']]['href'] = ""; } if (!isset($manifestData['scos'][$item['identifierref']]['parameters'])) { $manifestData['scos'][$item['identifierref']]['parameters'] = ""; } if (!isset($manifestData['assets'][$item['identifierref']]['parameters'])) { $manifestData['assets'][$item['identifierref']]['parameters'] = ""; } if (!isset($manifestData['items'][$item['itemIdentifier']]['parameters'])) { $manifestData['items'][$item['itemIdentifier']]['parameters'] = ""; } if (isset($manifestData['scos'][$item['identifierref']]['xml:base'])) { $extraPath = $manifestData['scos'][$item['identifierref']]['xml:base']; } else if (isset($manifestData['assets'][$item['identifierref']]['xml:base'])) { $extraPath = $manifestData['assets'][$item['identifierref']]['xml:base']; } else { $extraPath = ""; } $assetPath = "/" . $manifestData['xml:base']['manifest'] . $manifestData['xml:base']['ressources'] . $extraPath . $manifestData['scos'][$item['identifierref']]['href'] . $manifestData['assets'][$item['identifierref']]['href'] . $manifestData['scos'][$item['identifierref']]['parameters'] . $manifestData['assets'][$item['identifierref']]['parameters'] . $manifestData['items'][$item['itemIdentifier']]['parameters']; // create new asset // array of all inserted asset ids $insertedAsset_id[$i] = Database::get()->query("INSERT INTO `lp_asset` (`path` , `module_id` , `comment`) VALUES (?s, ?d, '')", $assetPath, $insertedModule_id[$i])->lastInsertID; if (!$insertedAsset_id[$i]) { $errorFound = true; array_push($errorMsgs, $langErrorSql); break; } // update of module with correct start asset id Database::get()->query("UPDATE `lp_module` SET `startAsset_id` = ?d WHERE `module_id` = ?d AND `course_id` = ?d", $insertedAsset_id[$i], $insertedModule_id[$i], $course_id); // visibility if (isset($item['isvisible']) && $item['isvisible'] != '') { ( $item['isvisible'] == "true" ) ? $visibility = 1 : $visibility = 0; } else { $visibility = 1; // IMS consider that the default value of 'isvisible' is true } // finally : insert in learning path // get the inserted id of the learnPath_module rel to allow 'parent' link in next inserts $insertedLPMid[$item['itemIdentifier']]['LPMid'] = Database::get()->query("INSERT INTO `lp_rel_learnPath_module` (`learnPath_id`, `module_id`, `specificComment`, `rank`, `visible`, `lock`, `parent`) VALUES (?d, ?d, ?s, ?d, ?d, 'OPEN', ?d)", $tempPathId, $insertedModule_id[$i], $langDefaultModuleAddedComment, $rank, $visibility, $parent)->lastInsertID; $insertedLPMid[$item['itemIdentifier']]['rank'] = 1; if (!$insertedLPMid[$item['itemIdentifier']]['LPMid']) { $errorFound = true; array_push($errorMsgs, $langErrorSql); break; } if (!$errorFound) { array_push($okMsgs, $langOkModuleAdded . "<i>" . $moduleName . "</i>"); } $i++; }//foreach } // if sizeof($manifestData['items'] == 0 ) } // if errorFound // last step // - delete all added files/directories/records in db // or // - update the learning path record if ($errorFound) { // delete all database entries of this "module" // delete modules and assets (build query) // delete assets $sqlDelAssets = "DELETE FROM `lp_asset` WHERE 1 = 0"; foreach ($insertedAsset_id as $insertedAsset) { $sqlDelAssets .= " OR `asset_id` = " . intval($insertedAsset); } Database::get()->query($sqlDelAssets); // delete modules $sqlDelModules = "DELETE FROM `lp_module` WHERE 1 = 0"; foreach ($insertedModule_id as $insertedModule) { $sqlDelModules .= " OR ( `module_id` = " . intval($insertedModule) . " AND `course_id` = " . intval($course_id) . " )"; } Database::get()->query($sqlDelModules); // delete learningPath_module Database::get()->query("DELETE FROM `lp_rel_learnPath_module` WHERE `learnPath_id` = ?d", $tempPathId); // delete learning path Database::get()->query("DELETE FROM `lp_learnPath` WHERE `learnPath_id` = ?d AND `course_id` = ?d", $tempPathId, $course_id); // delete the directory (and files) of this learning path and all its content claro_delete_file($baseWorkDir); } else { // finalize insertion : update the empty learning path insert that was made to find its id $rankMax = 1 + intval(Database::get()->querySingle("SELECT MAX(`rank`) AS max FROM `lp_learnPath` WHERE `course_id` = ?d", $course_id)->max); if (isset($manifestData['packageTitle'])) { $lpName = $manifestData['packageTitle']; } else { array_push($okMsgs, $langOkDefaultTitleUsed); } if (isset($manifestData['packageDesc'])) { $lpComment = $manifestData['packageDesc']; } else { $lpComment = $langDefaultLearningPathComment; array_push($okMsgs, $langOkDefaultCommentUsed); } Database::get()->query("UPDATE `lp_learnPath` SET `rank` = ?d, `name` = ?s, `comment` = ?s, `visible` = 1 WHERE `learnPath_id` = ?d AND `course_id` = ?d", $rankMax, $lpName, $lpComment, $tempPathId, $course_id); } /* -------------------------------------- status messages -------------------------------------- */ $importMessages = "\n<p>\n"; //$importMessages .= "<!-- Messages -->"; foreach ($okMsgs as $msg) { $importMessages .= "\n<b>[</b><span class=\"correct\">$langSuccessOk</span><b>]</b> " . $msg . "<br />"; } foreach ($errorMsgs as $msg) { $importMessages .= "\n<b>[</b><span class=\"error\">$langError</span><b>]</b> " . $msg . "<br />"; } $importMessages .= "\n\n"; //$importMessages .= "<!-- End messages -->"; // installation completed or not message if (!$errorFound) { $importMessages .= "\n<br /><center><b>" . $langInstalled . "</b></center>"; if ($displayExtraMessages == true) { $importMessages .= "\n<br /><br ><center><a href=\"learningPathAdmin.php?course=$course_code&path_id=" . $tempPathId . "\">" . $lpName . "</a></center>"; } $importMessages .= "\n<br /><br >"; } else { $importMessages .= "\n<br /><center><b>" . $langNotInstalled . "</b></center>"; } //$importMessages .= "\n<br /><a href=\"index.php?course=$course_code\">$langBack</a></p>"; $importMessages .= "\n<br /></p>"; chdir($pwd); return array($importMessages, $tempPathId); }