/** * Exports the learning path as a SCORM package. This is the main function that * gathers the content, transforms it, writes the imsmanifest.xml file, zips the * whole thing and returns the zip. * * This method needs to be called in PHP5, as it will fail with non-adequate * XML package (like the ones for PHP4), and it is *not* a static method, so * you need to call it on a learnpath object. * @TODO The method might be redefined later on in the scorm class itself to avoid * creating a SCORM structure if there is one already. However, if the initial SCORM * path has been modified, it should use the generic method here below. * @TODO link this function with the export_lp() function in the same class * @param string Optional name of zip file. If none, title of learnpath is * domesticated and trailed with ".zip" * @return string Returns the zip package string, or null if error */ function scorm_export() { global $_course; global $charset; if (!class_exists('DomDocument')) { error_log('DOM functions not supported for PHP version below 5.0', 0); $this->error = 'PHP DOM functions not supported for PHP versions below 5.0'; return null; } //remove memory and time limits as much as possible as this might be a long process... if (function_exists('ini_set')) { $mem = ini_get('memory_limit'); if (substr($mem, -1, 1) == 'M') { $mem_num = substr($mem, 0, -1); if ($mem_num < 128) { ini_set('memory_limit', '128M'); } } else { ini_set('memory_limit', '128M'); } ini_set('max_execution_time', 600); //}else{ //error_log('Scorm export: could not change memory and time limits',0); } //Create the zip handler (this will remain available throughout the method) $archive_path = api_get_path(SYS_ARCHIVE_PATH); $sys_course_path = api_get_path(SYS_COURSE_PATH); $temp_dir_short = uniqid(); $temp_zip_dir = $archive_path . "/" . $temp_dir_short; $temp_zip_file = $temp_zip_dir . "/" . md5(time()) . ".zip"; $zip_folder = new PclZip($temp_zip_file); $current_course_path = api_get_path(SYS_COURSE_PATH) . api_get_course_path(); $root_path = $main_path = api_get_path(SYS_PATH); $files_cleanup = array(); //place to temporarily stash the zipfiles //create the temp dir if it doesn't exist //or do a cleanup befor creating the zipfile if (!is_dir($temp_zip_dir)) { mkdir($temp_zip_dir); } else { //cleanup: check the temp dir for old files and delete them $handle = opendir($temp_zip_dir); while (false !== ($file = readdir($handle))) { if ($file != "." && $file != "..") { unlink("{$temp_zip_dir}/{$file}"); } } closedir($handle); } $zip_files = $zip_files_abs = $zip_files_dist = array(); if (is_dir($current_course_path . '/scorm/' . $this->path) && is_file($current_course_path . '/scorm/' . $this->path . '/imsmanifest.xml')) { // remove the possible . at the end of the path $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path; $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir . '/scorm/' . $dest_path_to_lp); $perm = api_get_setting('permissions_for_new_directories'); $perm = octdec(!empty($perm) ? $perm : '0770'); mkdir($dest_path_to_scorm_folder, $perm, true); $zip_files_dist = copyr($current_course_path . '/scorm/' . $this->path, $dest_path_to_scorm_folder, array('imsmanifest'), $zip_files); } //Build a dummy imsmanifest structure. Do not add to the zip yet (we still need it) //This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content //Aggregation Model official document, secion "2.3 Content Packaging" $xmldoc = new DOMDocument('1.0', $this->encoding); $root = $xmldoc->createElement('manifest'); $root->setAttribute('identifier', 'SingleCourseManifest'); $root->setAttribute('version', '1.1'); $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2'); $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2'); $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); $root->setAttribute('xsi:schemaLocation', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd'); //Build mandatory sub-root container elements $metadata = $xmldoc->createElement('metadata'); $md_schema = $xmldoc->createElement('schema', 'ADL SCORM'); $metadata->appendChild($md_schema); $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2'); $metadata->appendChild($md_schemaversion); $root->appendChild($metadata); $organizations = $xmldoc->createElement('organizations'); $resources = $xmldoc->createElement('resources'); //Build the only organization we will use in building our learnpaths $organizations->setAttribute('default', 'dokeos_scorm_export'); $organization = $xmldoc->createElement('organization'); $organization->setAttribute('identifier', 'dokeos_scorm_export'); //to set the title of the SCORM entity (=organization), we take the name given //in Dokeos and convert it to HTML entities using the Dokeos charset (not the //learning path charset) as it is the encoding that defines how it is stored //in the database. Then we convert it to HTML entities again as the "&" character //alone is not authorized in XML (must be &) //The title is then decoded twice when extracting (see scorm::parse_manifest) $org_title = $xmldoc->createElement('title', htmlentities(api_htmlentities($this->get_name(), ENT_QUOTES, $charset))); $organization->appendChild($org_title); //For each element, add it to the imsmanifest structure, then add it to the zip. //Always call the learnpathItem->scorm_export() method to change it to the SCORM //format $link_updates = array(); foreach ($this->items as $index => $item) { if (!in_array($item->type, array(TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION))) { //get included documents from this item if ($item->type == 'sco') { $inc_docs = $item->get_resources_from_source(null, api_get_path(SYS_COURSE_PATH) . api_get_course_path() . '/' . 'scorm/' . $this->path . '/' . $item->get_path()); } else { $inc_docs = $item->get_resources_from_source(); } //give a child element <item> to the <organization> element $my_item_id = $item->get_id(); $my_item = $xmldoc->createElement('item'); $my_item->setAttribute('identifier', 'ITEM_' . $my_item_id); $my_item->setAttribute('identifierref', 'RESOURCE_' . $my_item_id); $my_item->setAttribute('isvisible', 'true'); //give a child element <title> to the <item> element $my_title = $xmldoc->createElement('title', htmlspecialchars($item->get_title(), ENT_QUOTES, $this->encoding)); $my_item->appendChild($my_title); //give a child element <adlcp:prerequisites> to the <item> element $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $this->get_scorm_prereq_string($my_item_id)); $my_prereqs->setAttribute('type', 'aicc_script'); $my_item->appendChild($my_prereqs); //give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported //$xmldoc->createElement('adlcp:maxtimeallowed',''); //give a child element <adlcp:timelimitaction> to the <item> element - not yet supported //$xmldoc->createElement('adlcp:timelimitaction',''); //give a child element <adlcp:datafromlms> to the <item> element - not yet supported //$xmldoc->createElement('adlcp:datafromlms',''); //give a child element <adlcp:masteryscore> to the <item> element $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score()); $my_item->appendChild($my_masteryscore); //attach this item to the organization element or hits parent if there is one if (!empty($item->parent) && $item->parent != 0) { $children = $organization->childNodes; $possible_parent =& $this->get_scorm_xml_node($children, 'ITEM_' . $item->parent); if (is_object($possible_parent)) { $possible_parent->appendChild($my_item); } else { if ($this->debug > 0) { error_log('Parent ITEM_' . $item->parent . ' of item ITEM_' . $my_item_id . ' not found'); } } } else { if ($this->debug > 0) { error_log('No parent'); } $organization->appendChild($my_item); } //get the path of the file(s) from the course directory root $my_file_path = $item->get_file_path('scorm/' . $this->path . '/'); $my_xml_file_path = api_htmlentities($my_file_path, ENT_QUOTES, $this->encoding); $my_sub_dir = dirname($my_file_path); $my_xml_sub_dir = api_htmlentities($my_sub_dir, ENT_QUOTES, $this->encoding); //give a <resource> child to the <resources> element $my_resource = $xmldoc->createElement('resource'); $my_resource->setAttribute('identifier', 'RESOURCE_' . $item->get_id()); $my_resource->setAttribute('type', 'webcontent'); $my_resource->setAttribute('href', $my_xml_file_path); //adlcp:scormtype can be either 'sco' or 'asset' if ($item->type == 'sco') { $my_resource->setAttribute('adlcp:scormtype', 'sco'); } else { $my_resource->setAttribute('adlcp:scormtype', 'asset'); } //xml:base is the base directory to find the files declared in this resource $my_resource->setAttribute('xml:base', ''); //give a <file> child to the <resource> element $my_file = $xmldoc->createElement('file'); $my_file->setAttribute('href', $my_xml_file_path); $my_resource->appendChild($my_file); //dependency to other files - not yet supported $i = 1; foreach ($inc_docs as $doc_info) { if (count($doc_info) < 1 or empty($doc_info[0])) { continue; } $my_dep = $xmldoc->createElement('resource'); $res_id = 'RESOURCE_' . $item->get_id() . '_' . $i; $my_dep->setAttribute('identifier', $res_id); $my_dep->setAttribute('type', 'webcontent'); $my_dep->setAttribute('adlcp:scormtype', 'asset'); $my_dep_file = $xmldoc->createElement('file'); //check type of URL //error_log(__LINE__.'Now dealing with '.$doc_info[0].' of type '.$doc_info[1].'-'.$doc_info[2],0); if ($doc_info[1] == 'remote') { //remote file. Save url as is $my_dep_file->setAttribute('href', $doc_info[0]); $my_dep->setAttribute('xml:base', ''); } elseif ($doc_info[1] == 'local') { switch ($doc_info[2]) { case 'url': //local URL - save path as url for now, don't zip file $abs_path = api_get_path(SYS_PATH) . str_replace(api_get_path(WEB_PATH), '', $doc_info[0]); $current_dir = dirname($abs_path); $file_path = realpath($abs_path); $my_dep_file->setAttribute('href', $file_path); $my_dep->setAttribute('xml:base', ''); if (strstr($file_path, $main_path) !== false) { //the calculated real path is really inside the dokeos root path //reduce file path to what's under the DocumentRoot $file_path = substr($file_path, strlen($root_path) - 1); //echo $file_path;echo '<br><br>'; //error_log(__LINE__.'Reduced url path: '.$file_path,0); $zip_files_abs[] = $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); $my_dep_file->setAttribute('href', $file_path); $my_dep->setAttribute('xml:base', ''); } else { if (empty($file_path)) { /* $document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH),api_get_path(REL_PATH))); if(strpos($document_root,-1)=='/') { $document_root = substr(0, -1, $document_root); } */ $file_path = $_SERVER['DOCUMENT_ROOT'] . $abs_path; $file_path = str_replace('//', '/', $file_path); if (file_exists($file_path)) { $file_path = substr($file_path, strlen($current_dir)); // we get the relative path $zip_files[] = $my_sub_dir . '/' . $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); $my_dep_file->setAttribute('href', $file_path); $my_dep->setAttribute('xml:base', ''); } } } break; case 'abs': //absolute path from DocumentRoot. Save file and leave path as is in the zip $my_dep_file->setAttribute('href', $doc_info[0]); $my_dep->setAttribute('xml:base', ''); //$current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/'; //the next lines fix a bug when using the "subdir" mode of Dokeos, whereas //an image path would be constructed as /var/www/subdir/subdir/img/foo.bar $abs_img_path_without_subdir = $doc_info[0]; $relp = api_get_path(REL_PATH); //the url-append config param $pos = strpos($abs_img_path_without_subdir, $relp); if ($pos === 0) { $abs_img_path_without_subdir = '/' . substr($abs_img_path_without_subdir, strlen($relp)); } //$file_path = realpath(api_get_path(SYS_PATH).$doc_info[0]); $file_path = realpath(api_get_path(SYS_PATH) . $abs_img_path_without_subdir); $file_path = str_replace('\\', '/', $file_path); $file_path = str_replace('//', '/', $file_path); //error_log(__LINE__.'Abs path: '.$file_path,0); //prepare the current directory path (until just under 'document') with a trailing slash $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path . '/'; //check if the current document is in that path if (strstr($file_path, $cur_path) !== false) { //the document is in that path, now get the relative path //to the containing document $orig_file_path = dirname($cur_path . $my_file_path) . '/'; $relative_path = ''; if (strstr($file_path, $cur_path) !== false) { $relative_path = substr($file_path, strlen($orig_file_path)); $file_path = substr($file_path, strlen($cur_path)); } else { //this case is still a problem as it's difficult to calculate a relative path easily //might still generate wrong links //$file_path = substr($file_path,strlen($cur_path)); //calculate the directory path to the current file (without trailing slash) $my_relative_path = dirname($file_path); $my_relative_file = basename($file_path); //calculate the directory path to the containing file (without trailing slash) $my_orig_file_path = substr($orig_file_path, 0, -1); $dotdot = ''; $subdir = ''; while (strstr($my_relative_path, $my_orig_file_path) === false && strlen($my_orig_file_path) > 1 && strlen($my_relative_path) > 1) { $my_relative_path2 = dirname($my_relative_path); $my_orig_file_path = dirname($my_orig_file_path); $subdir = substr($my_relative_path, strlen($my_relative_path2) + 1) . "/" . $subdir; $dotdot += '../'; $my_relative_path = $my_relative_path2; } $relative_path = $dotdot . $subdir . $my_relative_file; } //put the current document in the zip (this array is the array //that will manage documents already in the course folder - relative) $zip_files[] = $file_path; //update the links to the current document in the containing document (make them relative) $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $relative_path); $my_dep_file->setAttribute('href', $file_path); $my_dep->setAttribute('xml:base', ''); } elseif (strstr($file_path, $main_path) !== false) { //the calculated real path is really inside the dokeos root path //reduce file path to what's under the DocumentRoot $file_path = substr($file_path, strlen($root_path)); //echo $file_path;echo '<br><br>'; //error_log('Reduced path: '.$file_path,0); $zip_files_abs[] = $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); $my_dep_file->setAttribute('href', 'document/' . $file_path); $my_dep->setAttribute('xml:base', ''); } else { if (empty($file_path)) { /* $document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH),api_get_path(REL_PATH))); if(strpos($document_root,-1)=='/') { $document_root = substr(0, -1, $document_root); } */ $file_path = $_SERVER['DOCUMENT_ROOT'] . $doc_info[0]; $file_path = str_replace('//', '/', $file_path); if (file_exists($file_path)) { $file_path = substr($file_path, strlen($current_dir)); // we get the relative path $zip_files[] = $my_sub_dir . '/' . $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); $my_dep_file->setAttribute('href', 'document/' . $file_path); $my_dep->setAttribute('xml:base', ''); } } } break; case 'rel': //path relative to the current document. Save xml:base as current document's directory and save file in zip as subdir.file_path if (substr($doc_info[0], 0, 2) == '..') { //relative path going up $current_dir = dirname($current_course_path . '/' . $item->get_file_path()) . '/'; $file_path = realpath($current_dir . $doc_info[0]); //error_log($file_path.' <-> '.$main_path,0); if (strstr($file_path, $main_path) !== false) { //the calculated real path is really inside the dokeos root path //reduce file path to what's under the DocumentRoot $file_path = substr($file_path, strlen($root_path)); //error_log('Reduced path: '.$file_path,0); $zip_files_abs[] = $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); $my_dep_file->setAttribute('href', 'document/' . $file_path); $my_dep->setAttribute('xml:base', ''); } } else { $zip_files[] = $my_sub_dir . '/' . $doc_info[0]; $my_dep_file->setAttribute('href', $doc_info[0]); $my_dep->setAttribute('xml:base', $my_xml_sub_dir); } break; default: $my_dep_file->setAttribute('href', $doc_info[0]); $my_dep->setAttribute('xml:base', ''); break; } } $my_dep->appendChild($my_dep_file); $resources->appendChild($my_dep); $dependency = $xmldoc->createElement('dependency'); $dependency->setAttribute('identifierref', $res_id); $my_resource->appendChild($dependency); $i++; } //$my_dependency = $xmldoc->createElement('dependency'); //$my_dependency->setAttribute('identifierref',''); $resources->appendChild($my_resource); $zip_files[] = $my_file_path; //error_log('File '.$my_file_path. ' added to $zip_files',0); } else { // if the item is a quiz or a link or whatever non-exportable, we include a step indicating it if ($item->type == TOOL_LINK) { $my_item = $xmldoc->createElement('item'); $my_item->setAttribute('identifier', 'ITEM_' . $item->get_id()); $my_item->setAttribute('identifierref', 'RESOURCE_' . $item->get_id()); $my_item->setAttribute('isvisible', 'true'); //give a child element <title> to the <item> element $my_title = $xmldoc->createElement('title', htmlspecialchars($item->get_title(), ENT_QUOTES)); $my_item->appendChild($my_title); //give a child element <adlcp:prerequisites> to the <item> element $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string()); $my_prereqs->setAttribute('type', 'aicc_script'); $my_item->appendChild($my_prereqs); //give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported //$xmldoc->createElement('adlcp:maxtimeallowed',''); //give a child element <adlcp:timelimitaction> to the <item> element - not yet supported //$xmldoc->createElement('adlcp:timelimitaction',''); //give a child element <adlcp:datafromlms> to the <item> element - not yet supported //$xmldoc->createElement('adlcp:datafromlms',''); //give a child element <adlcp:masteryscore> to the <item> element $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score()); $my_item->appendChild($my_masteryscore); //attach this item to the organization element or its parent if there is one if (!empty($item->parent) && $item->parent != 0) { $children = $organization->childNodes; for ($i = 0; $i < $children->length; $i++) { $item_temp = $children->item($i); if ($item_temp->nodeName == 'item') { if ($item_temp->getAttribute('identifier') == 'ITEM_' . $item->parent) { $item_temp->appendChild($my_item); } } } } else { $organization->appendChild($my_item); } $my_file_path = 'link_' . $item->get_id() . '.html'; $sql = 'SELECT url, title FROM ' . Database::get_course_table(TABLE_LINK) . ' WHERE id=' . $item->path; $rs = Database::query($sql, __FILE__, __LINE__); if ($link = Database::fetch_array($rs)) { $url = $link['url']; $title = stripslashes($link['title']); $links_to_create[$my_file_path] = array('title' => $title, 'url' => $url); $my_xml_file_path = api_htmlentities($my_file_path, ENT_QUOTES, $this->encoding); $my_sub_dir = dirname($my_file_path); $my_xml_sub_dir = api_htmlentities($my_sub_dir, ENT_QUOTES, $this->encoding); //give a <resource> child to the <resources> element $my_resource = $xmldoc->createElement('resource'); $my_resource->setAttribute('identifier', 'RESOURCE_' . $item->get_id()); $my_resource->setAttribute('type', 'webcontent'); $my_resource->setAttribute('href', $my_xml_file_path); //adlcp:scormtype can be either 'sco' or 'asset' $my_resource->setAttribute('adlcp:scormtype', 'asset'); //xml:base is the base directory to find the files declared in this resource $my_resource->setAttribute('xml:base', ''); //give a <file> child to the <resource> element $my_file = $xmldoc->createElement('file'); $my_file->setAttribute('href', $my_xml_file_path); $my_resource->appendChild($my_file); $resources->appendChild($my_resource); } } elseif ($item->type == TOOL_QUIZ) { require_once api_get_path(SYS_CODE_PATH) . 'exercice/exercise.class.php'; $exe_id = $item->path; //should be using ref when everything will be cleaned up in this regard $exe = new Exercise(); $exe->read($exe_id); $my_item = $xmldoc->createElement('item'); $my_item->setAttribute('identifier', 'ITEM_' . $item->get_id()); $my_item->setAttribute('identifierref', 'RESOURCE_' . $item->get_id()); $my_item->setAttribute('isvisible', 'true'); //give a child element <title> to the <item> element $my_title = $xmldoc->createElement('title', htmlspecialchars($item->get_title(), ENT_QUOTES, $this->encoding)); $my_item->appendChild($my_title); $my_max_score = $xmldoc->createElement('max_score', $item->get_max()); //$my_item->appendChild($my_max_score); //give a child element <adlcp:prerequisites> to the <item> element $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string()); $my_prereqs->setAttribute('type', 'aicc_script'); $my_item->appendChild($my_prereqs); //give a child element <adlcp:masteryscore> to the <item> element $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score()); $my_item->appendChild($my_masteryscore); //attach this item to the organization element or hits parent if there is one if (!empty($item->parent) && $item->parent != 0) { $children = $organization->childNodes; for ($i = 0; $i < $children->length; $i++) { $item_temp = $children->item($i); if ($item_temp->nodeName == 'item') { if ($item_temp->getAttribute('identifier') == 'ITEM_' . $item->parent) { $item_temp->appendChild($my_item); } } } } else { $organization->appendChild($my_item); } //include export scripts require_once api_get_path(SYS_CODE_PATH) . 'exercice/export/scorm/scorm_export.php'; //get the path of the file(s) from the course directory root //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/'); $my_file_path = 'quiz_' . $item->get_id() . '.html'; //write the contents of the exported exercise into a (big) html file //to later pack it into the exported SCORM. The file will be removed afterwards $contents = export_exercise($exe_id, true); $tmp_file_path = $archive_path . $temp_dir_short . '/' . $my_file_path; $res = file_put_contents($tmp_file_path, $contents); if ($res === false) { error_log('Could not write into file ' . $tmp_file_path . ' ' . __FILE__ . ' ' . __LINE__, 0); } $files_cleanup[] = $tmp_file_path; //error_log($tmp_path);die(); $my_xml_file_path = api_htmlentities($my_file_path, ENT_QUOTES, $this->encoding); $my_sub_dir = dirname($my_file_path); $my_xml_sub_dir = api_htmlentities($my_sub_dir, ENT_QUOTES, $this->encoding); //give a <resource> child to the <resources> element $my_resource = $xmldoc->createElement('resource'); $my_resource->setAttribute('identifier', 'RESOURCE_' . $item->get_id()); $my_resource->setAttribute('type', 'webcontent'); $my_resource->setAttribute('href', $my_xml_file_path); //adlcp:scormtype can be either 'sco' or 'asset' $my_resource->setAttribute('adlcp:scormtype', 'sco'); //xml:base is the base directory to find the files declared in this resource $my_resource->setAttribute('xml:base', ''); //give a <file> child to the <resource> element $my_file = $xmldoc->createElement('file'); $my_file->setAttribute('href', $my_xml_file_path); $my_resource->appendChild($my_file); //get included docs $inc_docs = $item->get_resources_from_source(null, $tmp_file_path); //dependency to other files - not yet supported $i = 1; foreach ($inc_docs as $doc_info) { if (count($doc_info) < 1 or empty($doc_info[0])) { continue; } $my_dep = $xmldoc->createElement('resource'); $res_id = 'RESOURCE_' . $item->get_id() . '_' . $i; $my_dep->setAttribute('identifier', $res_id); $my_dep->setAttribute('type', 'webcontent'); $my_dep->setAttribute('adlcp:scormtype', 'asset'); $my_dep_file = $xmldoc->createElement('file'); //check type of URL //error_log(__LINE__.'Now dealing with '.$doc_info[0].' of type '.$doc_info[1].'-'.$doc_info[2],0); if ($doc_info[1] == 'remote') { //remote file. Save url as is $my_dep_file->setAttribute('href', $doc_info[0]); $my_dep->setAttribute('xml:base', ''); } elseif ($doc_info[1] == 'local') { switch ($doc_info[2]) { case 'url': //local URL - save path as url for now, don't zip file //save file but as local file (retrieve from URL) $abs_path = api_get_path(SYS_PATH) . str_replace(api_get_path(WEB_PATH), '', $doc_info[0]); $current_dir = dirname($abs_path); $file_path = realpath($abs_path); $my_dep_file->setAttribute('href', 'document/' . $file_path); $my_dep->setAttribute('xml:base', ''); if (strstr($file_path, $main_path) !== false) { //the calculated real path is really inside the dokeos root path //reduce file path to what's under the DocumentRoot $file_path = substr($file_path, strlen($root_path)); //echo $file_path;echo '<br><br>'; //error_log('Reduced path: '.$file_path,0); $zip_files_abs[] = $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => 'document/' . $file_path); $my_dep_file->setAttribute('href', 'document/' . $file_path); $my_dep->setAttribute('xml:base', ''); } else { if (empty($file_path)) { /* $document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH),api_get_path(REL_PATH))); if(strpos($document_root,-1)=='/') { $document_root = substr(0, -1, $document_root); } */ $file_path = $_SERVER['DOCUMENT_ROOT'] . $abs_path; $file_path = str_replace('//', '/', $file_path); if (file_exists($file_path)) { $file_path = substr($file_path, strlen($current_dir)); // we get the relative path $zip_files[] = $my_sub_dir . '/' . $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => 'document/' . $file_path); $my_dep_file->setAttribute('href', 'document/' . $file_path); $my_dep->setAttribute('xml:base', ''); } } } break; case 'abs': //absolute path from DocumentRoot. Save file and leave path as is in the zip $current_dir = dirname($current_course_path . '/' . $item->get_file_path()) . '/'; $file_path = realpath($doc_info[0]); $my_dep_file->setAttribute('href', $file_path); $my_dep->setAttribute('xml:base', ''); if (strstr($file_path, $main_path) !== false) { //the calculated real path is really inside the dokeos root path //reduce file path to what's under the DocumentRoot $file_path = substr($file_path, strlen($root_path)); //echo $file_path;echo '<br><br>'; //error_log('Reduced path: '.$file_path,0); $zip_files_abs[] = $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); $my_dep_file->setAttribute('href', 'document/' . $file_path); $my_dep->setAttribute('xml:base', ''); } else { if (empty($file_path)) { /* $document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH),api_get_path(REL_PATH))); if(strpos($document_root,-1)=='/') { $document_root = substr(0, -1, $document_root); } */ $file_path = $_SERVER['DOCUMENT_ROOT'] . $doc_info[0]; $file_path = str_replace('//', '/', $file_path); if (file_exists($file_path)) { $file_path = substr($file_path, strlen($current_dir)); // we get the relative path $zip_files[] = $my_sub_dir . '/' . $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); $my_dep_file->setAttribute('href', 'document/' . $file_path); $my_dep->setAttribute('xml:base', ''); } } } break; case 'rel': //path relative to the current document. Save xml:base as current document's directory and save file in zip as subdir.file_path if (substr($doc_info[0], 0, 2) == '..') { //relative path going up $current_dir = dirname($current_course_path . '/' . $item->get_file_path()) . '/'; $file_path = realpath($current_dir . $doc_info[0]); //error_log($file_path.' <-> '.$main_path,0); if (strstr($file_path, $main_path) !== false) { //the calculated real path is really inside the dokeos root path //reduce file path to what's under the DocumentRoot $file_path = substr($file_path, strlen($root_path)); $file_path_dest = $file_path; //file path is courses/DOKEOS/document/.... $info_file_path = explode('/', $file_path); if ($info_file_path[0] == 'courses') { //add character "/" in file path $file_path_dest = '/' . $file_path; } //error_log('Reduced path: '.$file_path,0); $zip_files_abs[] = $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path_dest); $my_dep_file->setAttribute('href', 'document/' . $file_path); $my_dep->setAttribute('xml:base', ''); } } else { $zip_files[] = $my_sub_dir . '/' . $doc_info[0]; $my_dep_file->setAttribute('href', $doc_info[0]); $my_dep->setAttribute('xml:base', $my_xml_sub_dir); } break; default: $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/ $my_dep->setAttribute('xml:base', ''); break; } } $my_dep->appendChild($my_dep_file); $resources->appendChild($my_dep); $dependency = $xmldoc->createElement('dependency'); $dependency->setAttribute('identifierref', $res_id); $my_resource->appendChild($dependency); $i++; } $resources->appendChild($my_resource); $zip_files[] = $my_file_path; } else { //get the path of the file(s) from the course directory root $my_file_path = 'non_exportable.html'; $my_xml_file_path = api_htmlentities($my_file_path, ENT_COMPAT, $this->encoding); $my_sub_dir = dirname($my_file_path); $my_xml_sub_dir = api_htmlentities($my_sub_dir, ENT_COMPAT, $this->encoding); //give a <resource> child to the <resources> element $my_resource = $xmldoc->createElement('resource'); $my_resource->setAttribute('identifier', 'RESOURCE_' . $item->get_id()); $my_resource->setAttribute('type', 'webcontent'); $my_resource->setAttribute('href', 'document/' . $my_xml_file_path); //adlcp:scormtype can be either 'sco' or 'asset' $my_resource->setAttribute('adlcp:scormtype', 'asset'); //xml:base is the base directory to find the files declared in this resource $my_resource->setAttribute('xml:base', ''); //give a <file> child to the <resource> element $my_file = $xmldoc->createElement('file'); $my_file->setAttribute('href', 'document/' . $my_xml_file_path); $my_resource->appendChild($my_file); $resources->appendChild($my_resource); } } } $organizations->appendChild($organization); $root->appendChild($organizations); $root->appendChild($resources); $xmldoc->appendChild($root); //todo: add a readme file here, with a short description and a link to the Reload player //then add the file to the zip, then destroy the file (this is done automatically) // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138 //error_log(print_r($zip_files,true),0); foreach ($zip_files as $file_path) { if (empty($file_path)) { continue; } //error_log(__LINE__.'getting document from '.$sys_course_path.$_course['path'].'/'.$file_path.' removing '.$sys_course_path.$_course['path'].'/',0); $dest_file = $archive_path . $temp_dir_short . '/' . $file_path; $this->create_path($dest_file); //error_log('copy '.api_get_path('SYS_COURSE_PATH').$_course['path'].'/'.$file_path.' to '.api_get_path('SYS_ARCHIVE_PATH').$temp_dir_short.'/'.$file_path,0); //echo $main_path.$file_path.'<br>'; @copy($sys_course_path . $_course['path'] . '/' . $file_path, $dest_file); //check if the file needs a link update if (in_array($file_path, array_keys($link_updates))) { $string = file_get_contents($dest_file); unlink($dest_file); foreach ($link_updates[$file_path] as $old_new) { //error_log('Replacing '.$old_new['orig'].' by '.$old_new['dest'].' in '.$file_path,0); //this is an ugly hack that allows .flv files to be found by the flv player that // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs // to find the flv to play in document/main/, so we replace main/ in the flv path by // ../../.. to return from inc/lib/flv_player to the document/main path if (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 5) == 'main/') { $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']); } elseif (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 6) == 'video/') { $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']); } if (substr($old_new['dest'], 0, 1) == '/') { $old_new['dest'] = substr($old_new['dest'], 1); } $string = str_replace($old_new['orig'], $old_new['dest'], $string); $string = str_replace(str_replace('https', 'http', $old_new['orig']), $old_new['dest'], $string); } file_put_contents($dest_file, $string); } } foreach ($zip_files_abs as $file_path) { if (empty($file_path)) { continue; } //error_log(__LINE__.'checking existence of '.$main_path.$file_path.'',0); if (!is_file($main_path . $file_path) || !is_readable($main_path . $file_path)) { continue; } //error_log(__LINE__.'getting document from '.$main_path.$file_path.' removing '.api_get_path('SYS_COURSE_PATH').$_course['path'].'/',0); $dest_file = $archive_path . $temp_dir_short . '/document/' . $file_path; $this->create_path($dest_file); //error_log('Created path '.api_get_path(SYS_ARCHIVE_PATH).$temp_dir_short.'/document/'.$file_path,0); //error_log('copy '.api_get_path(SYS_COURSE_PATH).$_course['path'].'/'.$file_path.' to '.api_get_path(SYS_ARCHIVE_PATH).$temp_dir_short.'/'.$file_path,0); //echo $main_path.$file_path.' - '.$dest_file.'<br>'; copy($main_path . $file_path, $dest_file); //check if the file needs a link update if (in_array($file_path, array_keys($link_updates))) { $string = file_get_contents($dest_file); unlink($dest_file); foreach ($link_updates[$file_path] as $old_new) { //error_log('Replacing '.$old_new['orig'].' by '.$old_new['dest'].' in '.$file_path,0); //this is an ugly hack that allows .flv files to be found by the flv player that // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs // to find the flv to play in document/main/, so we replace main/ in the flv path by // ../../.. to return from inc/lib/flv_player to the document/main path if (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 5) == 'main/') { $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']); } if (substr($old_new['dest'], 0, 1) == '/') { $old_new['dest'] = substr($old_new['dest'], 1); } $string = str_replace($old_new['orig'], $old_new['dest'], $string); $string = str_replace(str_replace('https', 'http', $old_new['orig']), $old_new['dest'], $string); } file_put_contents($dest_file, $string); } } if (is_array($links_to_create)) { foreach ($links_to_create as $file => $link) { $file_content = '<html><body><div style="text-align:center"><a href="' . $link['url'] . '">' . $link['title'] . '</a></div></body></html>'; file_put_contents($archive_path . $temp_dir_short . '/' . $file, $file_content); } } // add non exportable message explanation $lang_not_exportable = get_lang('ThisItemIsNotExportable'); $file_content = <<<EOD <html> \t<head> \t\t<style> \t\t\t.error-message { \t\t\t\tfont-family: arial, verdana, helvetica, sans-serif; \t\t\t\tborder-width: 1px; \t\t\t\tborder-style: solid; \t\t\t\tleft: 50%; \t\t\t\tmargin: 10px auto; \t\t\t\tmin-height: 30px; \t\t\t\tpadding: 5px; \t\t\t\tright: 50%; \t\t\t\twidth: 500px; \t\t\t\tbackground-color: #FFD1D1; \t\t\t\tborder-color: #FF0000; \t\t\t\tcolor: #000; \t\t\t} \t\t</style> \t<body> \t\t<div class="error-message"> \t\t\t{$lang_not_exportable} \t\t</div> \t</body> </html> EOD; if (!is_dir($archive_path . $temp_dir_short . '/document')) { @mkdir($archive_path . $temp_dir_short . '/document'); } file_put_contents($archive_path . $temp_dir_short . '/document/non_exportable.html', $file_content); //Add the extra files that go along with a SCORM package $main_code_path = api_get_path(SYS_CODE_PATH) . 'newscorm/packaging/'; $extra_files = scandir($main_code_path); foreach ($extra_files as $extra_file) { if (strpos($extra_file, '.') === 0) { continue; } else { $dest_file = $archive_path . $temp_dir_short . '/' . $extra_file; $this->create_path($dest_file); copy($main_code_path . $extra_file, $dest_file); } } //Finalize the imsmanifest structure, add to the zip, then return the zip $xmldoc->save($archive_path . '/' . $temp_dir_short . '/imsmanifest.xml'); $zip_folder->add($archive_path . '/' . $temp_dir_short, PCLZIP_OPT_REMOVE_PATH, $archive_path . '/' . $temp_dir_short . '/'); //clean possible temporary files foreach ($files_cleanup as $file) { $res = unlink($file); if ($res === false) { error_log('Could not delete temp file ' . $file . ' ' . __FILE__ . ' ' . __LINE__, 0); } } //Send file to client //$name = 'scorm_export_'.$this->lp_id.'.zip'; require_once api_get_path(LIBRARY_PATH) . 'fileUpload.lib.php'; $name = preg_replace('([^a-zA-Z0-9_\\.])', '-', html_entity_decode($this->get_name(), ENT_QUOTES)) . '.zip'; DocumentManager::file_send_for_download($temp_zip_file, true, $name); }
$page = 1; } if (!empty($_GET['gradebook']) && $_GET['gradebook'] == 'view') { $_SESSION['gradebook'] = Security::remove_XSS($_GET['gradebook']); $gradebook = $_SESSION['gradebook']; } elseif (empty($_GET['gradebook'])) { unset($_SESSION['gradebook']); $gradebook = ''; } if (!empty($gradebook) && $gradebook == 'view') { $interbreadcrumb[] = array('url' => '../gradebook/' . $_SESSION['gradebook_dest'], 'name' => get_lang('ToolGradebook')); } $nameTools = get_lang('Exercices'); if ($is_allowedToEdit && !empty($choice) && $choice == 'exportqti2') { require_once 'export/qti2/qti2_export.php'; $export = export_exercise($exerciseId, true); $archive_path = api_get_path(SYS_ARCHIVE_PATH); $temp_dir_short = api_get_unique_id(); $temp_zip_dir = $archive_path . "/" . $temp_dir_short; if (!is_dir($temp_zip_dir)) { mkdir($temp_zip_dir, api_get_permissions_for_new_directories()); } $temp_zip_file = $temp_zip_dir . "/" . api_get_unique_id() . ".zip"; $temp_xml_file = $temp_zip_dir . "/qti2export_" . $exerciseId . '.xml'; file_put_contents($temp_xml_file, $export); $zip_folder = new PclZip($temp_zip_file); $zip_folder->add($temp_xml_file, PCLZIP_OPT_REMOVE_ALL_PATH); $name = 'qti2_export_' . $exerciseId . '.zip'; //DocumentManager::string_send_for_download($export,true,'qti2export_'.$exerciseId.'.xml'); DocumentManager::file_send_for_download($temp_zip_file, true, $name); unlink($temp_zip_file);
/** * This function exports the given item * @param integer Id from learnpath_items table * @param integer Item id * @param string Itm type * @param boolean Shall the SCORM communications features be added? (true). Default: false. * @return void (outputs a zip file) * @todo Try using the SCORM communications addition (adding a button and several javascript calls to the SCORM API) elsewhere than just in the export feature, so it doesn't look like an incoherent feature */ function exportitem($id, $item_id, $item_type, $add_scorm_communications = false) { $course_id = api_get_course_int_id(); global $circle1_files, $expdir, $_course, $_SESSION, $GLOBALS; global $timeNoSecFormat, $dateFormatLong, $language_interface, $langPubl, $langDone, $langThisCourseDescriptionIsEmpty, $lg_course_description, $lg_introduction_text, $_cid, $langHotPotatoesFinished, $lg_author, $lg_date, $lg_groups, $lg_users, $lg_ass, $lg_dropbox, $test, $langQuestion; $libp = api_get_path(SYS_CODE_PAH); include_once $libp . 'exercice/exercise.class.php'; include_once $libp . 'question.class.php'; include_once $libp . 'answer.class.php'; $langLasting = ''; //avoid code parser warning include_once '../resourcelinker/resourcelinker.inc.php'; $LPname = display_addedresource_link_in_learnpath($item_type, $item_id, '', $id, 'builder', 'nolink'); $expcontent = "<!--\n This is an exported file from Chamilo Learning Path belonging to a Scorm compliant content package.\n Do not modify or replace individually.\n\n Export module author : Denes Nagy <*****@*****.**>\n\n -->\n\n "; // Files needed for communicating with the scos. $scocomfiles = "<script type='text/javascript' src='../js/APIWrapper.js'></script>" . "<script type='text/javascript' src='../js/SCOFunctions.js'></script>"; $expcontent .= '<html><head><link rel="stylesheet" href="../css/default.css" type="text/css" media="screen,projection" />' . $scocomfiles . '</head><body>'; $donebutton .= "<script type='text/javascript'>\n /* <![CDATA[ */\n loadPage();\n var studentName = '!';\n var lmsStudentName = doLMSGetValue( 'cmi.core.student_name' );\n if ( lmsStudentName != '' )\n {\n studentName = ' ' + lmsStudentName + '!';\n }\n /* ]]> */\n </script>\n <br /><br />\n <form><input type=\"hidden\" name=\"SQMSESSID\" value=\"36812c2dea7d8d6e708d5e6a2f09b0b9\" />\n <table cols='3'\twidth='100%' align='center'>\n <tr>\n <td\talign='middle'><input type = 'button' value\t= ' " . $langDone . " ' onclick = \"javascript: doQuit('completed');\" id='button2' name='button2'></td>\n </tr>\n </table>\n </form>"; /** * Switch between the different element types, namely: * - Agenda * - Ad_Valvas * - Course_description * - Document * - Introduction_text * - HotPotatoes * - Exercise * - Post * - Forum ] * - Thread ] * - Dropbox ] * - Assignments ] These elements are all replaced by a simple message in the exported document. * - Groups ] * - Users ] * - Link _self * - Link _blank */ switch ($item_type) { // AGENDA BEGIN case 'Agenda': // 1. Get agenda event data from the database table. $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA); $sql = "SELECT * FROM " . $TABLEAGENDA . " where c_id = {$course_id} AND (id={$item_id})"; $result = Database::query($sql); // 2. Prepare table output. $expcontent .= "<table class=\"data_table\" >"; $barreMois = ''; // 3. For each event corresponding to this agenda, do the following: while ($myrow = Database::fetch_array($result)) { $start_date_local = api_get_local_time($myrow['start_date'], null, date_default_timezone_get()); //3.1 Make the blue month bar appear only once. if ($barreMois != api_format_date($start_date_local, "%m")) { // 3.1.1 Update the check value for the month bar. $barreMois = api_format_date($start_date_local, "%m"); // 3.1.2 Display the month bar. $expcontent .= "<tr><td id=\"title\" colspan=\"2\" class=\"month\" valign=\"top\">" . api_format_date($start_date_local, "%B %Y") . "</td></tr>"; } // 3.2 Display the agenda items (of this month): the date, hour and title. $db_date = (int) api_format_date($start_date_local, "%d"); if ($_GET['day'] != $db_date) { // 3.2.1.a If the day given in the URL (might not be set) is different from this element's day, use style 'data'. $expcontent .= "<tr><td class=\"data\" colspan='2'>"; } else { // 3.2.1.b Else (same day) use style 'datanow'. $expcontent .= "<tr><td class=\"datanow\" colspan='2'>"; } // 3.2.2 Mark an anchor for this date. $expcontent .= "<a name=\"" . $db_date . "\"></a>"; // anchoring // 3.2.3 Write the date and time of this event to the export string. $expcontent .= api_format_date($start_date_local); // 3.2.4 If a duration is set, write it, otherwise ignore. if ($myrow['duration'] == '') { $expcontent .= "<br />"; } else { $expcontent .= " / " . $langLasting . " " . $myrow['duration'] . "<br />"; //langLasting comes from lang/x/agenda.inc.php } // 3.2.5 Write the title. $expcontent .= $myrow['title']; $expcontent .= "</td></tr>"; // 3.2.6 Prepare the content of the agenda item. $content = $myrow['content']; // 3.2.7 Make clickable??? $content = Text::make_clickable($content); // 3.2.8 Write the prepared content to the export string. $expcontent .= "<tr><td class=\"text\" colspan='2'>"; $expcontent .= $content; $expcontent .= "</td></tr>"; // Displaying the agenda item of this month: the added resources. // This part is not included into LP export. /*if (check_added_resources("Agenda", $myrow['id'])) { $content.= "<tr><td colspan='2'>"; $content.= "<i>".get_lang('AddedResources')."</i><br />"; display_added_resources("Agenda", $myrow['id']); $content.= "</td></tr>"; }*/ } // 4. Finish the export string. $expcontent .= "<tr></table>"; break; // ANNOUNCEMENT BEGIN // ANNOUNCEMENT BEGIN case 'Ad_Valvas': // 1. Get the announcement data from the database $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT); $sql = "SELECT * FROM {$tbl_announcement} WHERE c_id = {$course_id} AND id='{$item_id}'"; $result = Database::query($sql); // 2. Initialise export string $expcontent .= "<table class=\"data_table\">"; // 3. For each announcement matching the query while ($myrow = Database::fetch_array($result)) { // 3.1 Get the __ field data. $content = $myrow[1]; //$content = nl2br($content); // 3.2 Prepare the data for export. $content = Text::make_clickable($content); // 3.3 Get a UNIX(?<-mktime) Timestamp of the end_date for this announcement. $last_post_datetime = $myrow['end_date']; // post time format datetime of database layer (MySQL is assumed) list($last_post_date, $last_post_time) = split(' ', $last_post_datetime); list($year, $month, $day) = explode('-', $last_post_date); list($hour, $min) = explode(':', $last_post_time); $announceDate = mktime($hour, $min, 0, $month, $day, $year); // 3.4 Compare the end date to the last login date of the user (mark it in red if he has not already read it). if ($announceDate > $_SESSION['user_last_login_datetime']) { $colorBecauseNew = " color=\"red\" "; } else { $colorBecauseNew = ' '; } // 3.5 Write this content to the export string (formatted HTML array). $expcontent .= "<tr>\n" . "<td class=\"cell_header\">\n" . "<font " . $colorBecauseNew . ">" . $langPubl . " : " . api_convert_and_format_date($last_post_datetime, null, date_default_timezone_get()) . "</font>\n" . "</td>\n" . "</tr>\n" . "<tr>\n" . "<td>\n" . $content . "</td>\n" . "</tr>\n"; } // while loop // 4 Finish the export string. $expcontent .= "</table>"; break; // Course_description BEGIN // Course_description BEGIN case 'Course_description': // 1. Get course description data from database. $tbl_course_description = Database::get_course_table(TABLE_COURSE_DESCRIPTION); $result = Database::query("SELECT id, title, content FROM " . $tbl_course_description . " WHERE c_id = {$course_id} ORDER BY id"); // 2. Check this element. if (Database::num_rows($result)) { // 2.a This course has one (or more) description in the database. $expcontent .= "<hr noshade=\"noshade\" size=\"1\" />"; // 2.a.1 For each description available for this course. while ($row = Database::fetch_array($result)) { // 2.a.1.1 Write title to export string. $expcontent .= "<h4>" . $row['title'] . "</h4>"; // 2.a.1.2 Prepare content. $content = Text::make_clickable(nl2br($row['content'])); // 2.a.1.3 Write content to the export string. $expcontent .= $content; } } else { // 2.b This course has no description available. $expcontent .= "<br /><h4>{$langThisCourseDescriptionIsEmpty}</h4>"; } break; // DOCUMENT BEGIN // DOCUMENT BEGIN case 'Document': // 1. Get the document data from the database. $tbl_document = Database::get_course_table(TABLE_DOCUMENT); $sql_query = "SELECT * FROM {$tbl_document} WHERE c_id = {$course_id} AND id={$item_id}"; $sql_result = Database::query($sql_query); $myrow = Database::fetch_array($sql_result); // 2. Get the origin path of the document to treat it internally. $orig = api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document' . $myrow['path']; // 3. Make some kind of strange transformation to get the destination filepath ??? $pathname = explode('/', $myrow['path']); $last = count($pathname) - 1; $filename = 'data/' . $filename . $pathname[$last]; $copyneeded = true; // Html files do not need to be copied as the ok button is inserted into them, // so don't copy directly. $extension = explode('.', $pathname[$last]); // This old condition was WRONG for names like design.html.old. Instead, we now get the extension // by using preg_match to match case-insensitive (probably faster than 4 conditions). //if (($extension[1]=='htm') or ($extension[1]=='html') or ($extension[1]=='HTM') or ($extension[1]=='HTML')) { // 4. Check the file extension. if (preg_match('/.*(\\.htm(l)?)$/i', $pathname[$last])) { // 4.a If this file ends with ".htm(l)", we consider it's an HTML file. // src tag check begin // We now check if there is any src attribute in htm(l) files, if yes, we have to also export // the target file (swf, mp3, video,...) of that src tag. // In case of absolute links (http://) this is not neccessary, but makes no error. // However still missing : relative links case with subdirs -> the necessary dirs are not created in the exported package. // 4.a.1 Get the file contents into $file. $file = file_get_contents($orig); // 4.a.2 Get all the src links in this file. //preg_match_all("|((?i)src=\".*\" )|U",$file,$match); $match = GetSRCTags($orig); // 4.a.3 For each src tag found, do the following: for ($i = 0; $i < count($match); $i++) { // 4.a.3.1 Get the tag (split from the key). list($key, $srctag) = each($match); $src = $srctag; // 4.a.3.2 Check the link kind (web or absolute/relative). if (stristr($src, 'http') === false) { // 4.a.3.2.a Do something only if relative (otherwise the user will be able to see it too anyway). // 4.a.3.2.a.1 Get a proper URL and remove all './' $src = urldecode($src); //mp3 //$src=str_replace('./','',$src); $src = preg_replace('/^\\.\\//', '', $src); // 4.a.3.2.a.2 Remove the player link from the URL (only use the mp3 file). $src = str_replace('mp3player.swf?son=', '', $src); //mp3 // 4.a.3.2.a.3 Remove funny link parts. $src = str_replace('?0', '', $src); //mp3 // The previous lines are used when creating docs with Chamilo Document tool's htmlarea // Rows marked by 'mp3' are needed because the mp3 plugin inserts the swf-mp3 links in a very strange way // and we can decode them with those 3 lines, hoping this will not cause errors in case of other htmls, // created by any other software. // 4.a.3.2.a.4 Prepare source and destination paths. $source = api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document' . dirname($myrow['path']) . '/' . $src; $dest = $expdir . '/data/' . $src; //CopyNCreate($source,$dest); rcopy($source, $dest); } //else...? } // src tag check end // sco communication insertion begin // 4.a.4 If we want to add SCORM actions and a "Done" button, do the following: if ($add_scorm_communications === true) { if ($bodyclose = strpos($file, '</body>')) { $file = substr_replace($file, $scocomfiles . $donebutton, $bodyclose, 7); } elseif ($htmlclose = strpos($file, '</html>')) { $file = substr_replace($file, $scocomfiles . $donebutton, $htmlclose, 7); $file .= '</html>'; } else { $file .= $scocomfiles . $donebutton; } } //sco communication insertion end // 4.a.5 Replace the file's name by adding the element's ID before htm. // This will not work with uppercase HTML though. Maybe use the preg_replace syntax proposed... $filename = str_replace('.htm', $id . '.htm', $filename); //$filename=preg_replace('/.*(\.htm(l)?)$/i',$id.$1,$filename); // 4.a.6 Export these contents to a file and set the circle1_files array for later reuse. exporttofile($filename, $LPname, $id, $file); // The file has been copied, so ask not to copy it again. $copyneeded = false; } //if (htm(l) files) end // 5. If we still need to copy the file (e.g. it was not an HTML file), then copy and set circle1_files for later reuse. if ($copyneeded) { copy($orig, $expdir . '/' . $filename); $circle1_files[0][] = $filename; $circle1_files[1][] = $LPname; $circle1_files[2][] = $id; } //echo $orig; return; // Introduction_text BEGIN // Introduction_text BEGIN case 'Introduction_text': // 1 Get the introduction text data from the database. $TBL_INTRO = Database::get_course_tool_intro_table(); // Modified by Ivan Tcholakov, 15-SEP-2008. //$result = Database::query("SELECT * FROM ".$TBL_INTRO." WHERE id=1"); $result = Database::query("SELECT * FROM " . $TBL_INTRO . " WHERE c_id = {$course_id} AND id='course_homepage'"); // $myrow = Database::fetch_array($result); $intro = $myrow['intro_text']; // 2 Write introduction text to the export string. $expcontent .= '<br />' . $intro; break; // HotPotatoes BEGIN // HotPotatoes BEGIN case 'HotPotatoes': // 1. Get HotPotatoes data from the document table. $tbl_document = Database::get_course_table(TABLE_DOCUMENT); $result = Database::query("SELECT * FROM {$tbl_document} WHERE c_id = {$course_id} AND id={$item_id}"); $myrow = Database::fetch_array($result); // 2. Get the document path. $testfile = api_get_path(SYS_COURSE_PATH) . $_course['path'] . "/document" . urldecode($myrow['path']); // 3. Get the document contents into a string. $content = file_get_contents($testfile); // 4. Get the document filename (just the file, no path) - would probably be better to use PHP native function. $pathname = explode('/', $myrow['path']); $last = count($pathname) - 1; $filename = 'data/' . $filename . $pathname[$last]; // 4beta - get all linked files and copy them (procedure copied from documents type). // Get all the src links in this file. $match = GetSRCTags($testfile); // For each src tag found, do the following: foreach ($match as $src) { //Check the link kind (web or absolute/relative) if (stristr($src, 'http') === false) { // Do something only if relative (otherwise the user will be able to see it too anyway). // Get a proper URL and remove all './' $src = urldecode($src); //mp3 $src = str_replace('./', '', $src); // Remove the player link from the URL (only use the mp3 file). $src = str_replace('mp3player.swf?son=', '', $src); //mp3 // Remove funny link parts. $src = str_replace('?0', '', $src); //mp3 // The previous lines are used when creating docs with Chamilo Document tool's htmlarea. // Rows marked by 'mp3' are needed because the mp3 plugin inserts the swf-mp3 links in a very strange way // and we can decode them with those 3 lines, hoping this will not cause errors in case of other htmls, // created by any other software. // Prepare source and destination paths. $source = api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document' . dirname($myrow['path']) . '/' . $src; $dest = $expdir . '/data/' . $src; //CopyNCreate($source,$dest); rcopy($source, $dest); } //else...? } // 5. Prepare the special "close window" for this test. $closewindow = "<html><head><link rel='stylesheet' type='text/css' href='../css/default.css'></head><body>" . "<br /><div class='message'>{$langHotPotatoesFinished}</div></body></html>"; // Finish is the function of HP to save scores, we insert our scorm function calls to its beginning // 'Score' is the variable that tracks the score in HP tests. // 6. $mit = "function Finish(){"; $js_content = "var SaveScoreVariable = 0; // This variable is included by Chamilo LP export\n" . "function mySaveScore() // This function is included by Chamilo LP export\n" . "{\n" . " if (SaveScoreVariable==0)\n" . "\t\t{\n" . "\t SaveScoreVariable = 1;\n" . " exitPageStatus = true;\n" . " computeTime();\n" . " doLMSSetValue( 'cmi.core.score.raw', Score );\n" . " doLMSSetValue( 'cmi.core.lesson_status', 'completed' );\n" . " doLMSCommit();\n" . " doLMSFinish();\n" . "\t\t}\n" . "}\n" . "function Finish(){\n" . " mySaveScore();"; $start = "<script type='text/javascript'> loadPage(); </script>"; // 7. Replace the current MIT function call by our set of functions. In clear, transform HP to SCORM. $content = str_replace($mit, $js_content, $content); // 8. Finally, add the API loading calls (although that might have been done first). $content = str_replace("</script>", "</script>" . $scocomfiles . $start, $content); // 9. Change the filename to add the database ID and export to a new file, // setting the circle1_files array for later reuse. $filename = str_replace('.htm', $id . '.htm', $filename); exporttofile($filename, $LPname, $id, $content); return; // Chamilo test BEGIN // Chamilo test BEGIN case 'Exercise': //1 Use the export_exercise() function to do the job of constructing the question's HTML table $expcontent .= export_exercise($item_id); break; // POST BEGIN // POST BEGIN case 'Post': // 1. Get the forum post data from the database. $tbl_posts = Database::get_course_table(TABLE_FORUM_POST); $tbl_posts_text = Database::get_course_table(TOOL_FORUM_POST_TEXT_TABLE); $result = Database::query("SELECT * FROM {$tbl_posts} where c_id = {$course_id} AND post_id={$item_id}"); $myrow = Database::fetch_array($result); // Grabbing the title of the post. $sql_titel = "SELECT * FROM {$tbl_posts_text} WHERE c_id = {$course_id} AND post_id=" . $myrow['post_id']; $result_titel = Database::query($sql_titel); $myrow_titel = Database::fetch_array($result_titel); $posternom = $myrow['nom']; $posterprenom = $myrow['prenom']; $posttime = $myrow['post_time']; $posttext = $myrow_titel['post_text']; $posttitle = $myrow_titel['post_title']; $posttext = str_replace('"', "'", $posttext); //2 Export contents as an HTML table $expcontent .= "<table border='0' cellpadding='3' cellspacing='1' width='100%'>\n <tr>\n <td colspan='2' bgcolor='#e6e6e6'><b>{$posttitle}</b><br />{$posttext}</td>\n </tr>\n <tr>\n <td colspan='2'></td>\n </tr>\n <tr>\n <td bgcolor='#cccccc' align='left'>{$lg_author} : {$posterprenom} {$posternom}</td>\n <td align='right' bgcolor='#cccccc'>{$lg_date} : {$posttime}</td>\n </tr>\n <tr><td colspan='2' height='10'></td></tr>\n </table>"; break; // NOT IMPLEMENTED ITEMS BEGIN // NOT IMPLEMENTED ITEMS BEGIN case 'Forum': case 'Thread': case 'Dropbox': case 'Assignments': case 'Groups': case 'Users': // 1. Instead of building something, put an info message. $langItemMissing1 = "There was a "; $langItemMissing2 = "page (step) here in the original Chamilo Learning Path."; $expcontent .= "<div class='message'>{$langItemMissing1} {$item_type} {$langItemMissing2}</div>"; break; // Link BEGIN // Link BEGIN case 'Link _self': case 'Link _blank': // 1. Get the link data from the database. $TABLETOOLLINK = Database::get_course_link_table(); $result = Database::query("SELECT * FROM {$TABLETOOLLINK} WHERE c_id = {$course_id} AND id={$item_id}"); $myrow = Database::fetch_array($result); $thelink = $myrow['url']; // 2. Check the link type (open in blank page or in current page). if ($item_type == 'Link _blank') { $target = '_blank'; } // 3. Write the link to the export string. $expcontent .= "<a href='{$thelink}?SQMSESSID=36812c2dea7d8d6e708d5e6a2f09b0b9' target='" . $target . "'>{$LPname}</a>"; // 4. Change the element type for later changes (this is lost, however, so useless here). $item_type = 'Link'; // To put this to the filename. //$LPname="<a href='$thelink?SQMSESSID=36812c2dea7d8d6e708d5e6a2f09b0b9' target=".$target.">$LPname</a>"; // I am still not sure about Link export : to export them as files or they can appear in the TOC at once ? // To enable the second possibility, unrem the row $LPname=... break; } // Now we add the Done button and the initialize function : loadpage() // not in the case of Documents, HotP if ($item_type != 'Exercise' and $add_scorm_communications === true) { $expcontent .= $donebutton; } // End the export string with valid HTML tags. $expcontent .= "</body></html>"; // Prepare new file name. $filename = $item_type . $id . ".htm"; // Write the export content to the new file. exporttofile('data/' . $filename, $LPname, $id, $expcontent); }
/** * // TODO: The output encoding should be equal to the system encoding. * * Exports the learning path as a SCORM package. This is the main function that * gathers the content, transforms it, writes the imsmanifest.xml file, zips the * whole thing and returns the zip. * * This method needs to be called in PHP5, as it will fail with non-adequate * XML package (like the ones for PHP4), and it is *not* a static method, so * you need to call it on a learnpath object. * @TODO The method might be redefined later on in the scorm class itself to avoid * creating a SCORM structure if there is one already. However, if the initial SCORM * path has been modified, it should use the generic method here below. * @TODO link this function with the export_lp() function in the same class * @param string Optional name of zip file. If none, title of learnpath is * domesticated and trailed with ".zip" * @return string Returns the zip package string, or null if error */ public function scorm_export() { $_course = api_get_course_info(); $course_id = api_get_course_int_id(); // Remove memory and time limits as much as possible as this might be a long process... if (function_exists('ini_set')) { api_set_memory_limit('128M'); ini_set('max_execution_time', 600); } // Create the zip handler (this will remain available throughout the method). $archive_path = api_get_path(SYS_ARCHIVE_PATH); $sys_course_path = api_get_path(SYS_COURSE_PATH); $temp_dir_short = uniqid(); $temp_zip_dir = $archive_path.'/'.$temp_dir_short; $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip'; $zip_folder = new PclZip($temp_zip_file); $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path(); $root_path = $main_path = api_get_path(SYS_PATH); $files_cleanup = array(); // Place to temporarily stash the zip file. // create the temp dir if it doesn't exist // or do a cleanup before creating the zip file. if (!is_dir($temp_zip_dir)) { mkdir($temp_zip_dir, api_get_permissions_for_new_directories()); } else { // Cleanup: Check the temp dir for old files and delete them. $handle = opendir($temp_zip_dir); while (false !== ($file = readdir($handle))) { if ($file != '.' && $file != '..') { unlink("$temp_zip_dir/$file"); } } closedir($handle); } $zip_files = $zip_files_abs = $zip_files_dist = array(); if (is_dir($current_course_path.'/scorm/'.$this->path) && is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')) { // Remove the possible . at the end of the path. $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path; $dest_path_to_scorm_folder = str_replace('//','/',$temp_zip_dir.'/scorm/'.$dest_path_to_lp); mkdir($dest_path_to_scorm_folder, api_get_permissions_for_new_directories(), true); $zip_files_dist = copyr($current_course_path.'/scorm/'.$this->path, $dest_path_to_scorm_folder, array('imsmanifest'), $zip_files); } // Build a dummy imsmanifest structure. // Do not add to the zip yet (we still need it). // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content // Aggregation Model official document, section "2.3 Content Packaging". // We are going to build a UTF-8 encoded manifest. Later we will recode it to the desired (and supported) encoding. $xmldoc = new DOMDocument('1.0'); $root = $xmldoc->createElement('manifest'); $root->setAttribute('identifier', 'SingleCourseManifest'); $root->setAttribute('version', '1.1'); $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2'); $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2'); $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); $root->setAttribute('xsi:schemaLocation', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd'); // Build mandatory sub-root container elements. $metadata = $xmldoc->createElement('metadata'); $md_schema = $xmldoc->createElement('schema', 'ADL SCORM'); $metadata->appendChild($md_schema); $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2'); $metadata->appendChild($md_schemaversion); $root->appendChild($metadata); $organizations = $xmldoc->createElement('organizations'); $resources = $xmldoc->createElement('resources'); // Build the only organization we will use in building our learnpaths. $organizations->setAttribute('default', 'chamilo_scorm_export'); $organization = $xmldoc->createElement('organization'); $organization->setAttribute('identifier', 'chamilo_scorm_export'); // To set the title of the SCORM entity (=organization), we take the name given // in Chamilo and convert it to HTML entities using the Chamilo charset (not the // learning path charset) as it is the encoding that defines how it is stored // in the database. Then we convert it to HTML entities again as the "&" character // alone is not authorized in XML (must be &). // The title is then decoded twice when extracting (see scorm::parse_manifest). $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name())); $organization->appendChild($org_title); $folder_name = 'document'; // Removes the learning_path/scorm_folder path when exporting see #4841 $path_to_remove = null; $result = $this->generate_lp_folder($_course); if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) { $path_to_remove = 'document'.$result['dir']; $path_to_replace = $folder_name.'/'; } //Fixes chamilo scorm exports if ($this->ref == 'chamilo_scorm_export') { $path_to_remove = 'scorm/'.$this->path.'/document/'; } // For each element, add it to the imsmanifest structure, then add it to the zip. // Always call the learnpathItem->scorm_export() method to change it to the SCORM format. $link_updates = array(); $links_to_create = array(); foreach ($this->items as $index => $item) { if (!in_array($item->type, array(TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION))) { // Get included documents from this item. if ($item->type == 'sco') { $inc_docs = $item->get_resources_from_source( null, api_get_path(SYS_COURSE_PATH) . api_get_course_path() . '/' . 'scorm/' . $this->path . '/' . $item->get_path() ); } else { $inc_docs = $item->get_resources_from_source(); } // Give a child element <item> to the <organization> element. $my_item_id = $item->get_id(); $my_item = $xmldoc->createElement('item'); $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id); $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id); $my_item->setAttribute('isvisible', 'true'); // Give a child element <title> to the <item> element. $my_title = $xmldoc->createElement('title', htmlspecialchars(api_utf8_encode($item->get_title()), ENT_QUOTES, 'UTF-8')); $my_item->appendChild($my_title); // Give a child element <adlcp:prerequisites> to the <item> element. $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $this->get_scorm_prereq_string($my_item_id)); $my_prereqs->setAttribute('type', 'aicc_script'); $my_item->appendChild($my_prereqs); // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported. //$xmldoc->createElement('adlcp:maxtimeallowed',''); // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported. //$xmldoc->createElement('adlcp:timelimitaction',''); // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported. //$xmldoc->createElement('adlcp:datafromlms',''); // Give a child element <adlcp:masteryscore> to the <item> element. $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score()); $my_item->appendChild($my_masteryscore); // Attach this item to the organization element or hits parent if there is one. if (!empty($item->parent) && $item->parent != 0) { $children = $organization->childNodes; $possible_parent = &$this->get_scorm_xml_node($children, 'ITEM_'.$item->parent); if (is_object($possible_parent)) { $possible_parent->appendChild($my_item); } else { if ($this->debug > 0) { error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found'); } } } else { if ($this->debug > 0) { error_log('No parent'); } $organization->appendChild($my_item); } // Get the path of the file(s) from the course directory root. $my_file_path = $item->get_file_path('scorm/'.$this->path.'/'); if (!empty($path_to_remove)) { //From docs $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path); //From quiz if ($this->ref == 'chamilo_scorm_export') { $path_to_remove = 'scorm/'.$this->path.'/'; $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path); } } else { $my_xml_file_path = $my_file_path; } $my_sub_dir = dirname($my_file_path); $my_sub_dir = str_replace('\\', '/', $my_sub_dir); $my_xml_sub_dir = $my_sub_dir; // Give a <resource> child to the <resources> element $my_resource = $xmldoc->createElement('resource'); $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id()); $my_resource->setAttribute('type', 'webcontent'); $my_resource->setAttribute('href', $my_xml_file_path); // adlcp:scormtype can be either 'sco' or 'asset'. if ($item->type == 'sco') { $my_resource->setAttribute('adlcp:scormtype', 'sco'); } else { $my_resource->setAttribute('adlcp:scormtype', 'asset'); } // xml:base is the base directory to find the files declared in this resource. $my_resource->setAttribute('xml:base', ''); // Give a <file> child to the <resource> element. $my_file = $xmldoc->createElement('file'); $my_file->setAttribute('href', $my_xml_file_path); $my_resource->appendChild($my_file); // Dependency to other files - not yet supported. $i = 1; foreach ($inc_docs as $doc_info) { if (count($doc_info) < 1 || empty($doc_info[0])) { continue; } $my_dep = $xmldoc->createElement('resource'); $res_id = 'RESOURCE_'.$item->get_id().'_'.$i; $my_dep->setAttribute('identifier', $res_id); $my_dep->setAttribute('type', 'webcontent'); $my_dep->setAttribute('adlcp:scormtype', 'asset'); $my_dep_file = $xmldoc->createElement('file'); // Check type of URL. //error_log(__LINE__.'Now dealing with '.$doc_info[0].' of type '.$doc_info[1].'-'.$doc_info[2], 0); if ($doc_info[1] == 'remote') { // Remote file. Save url as is. $my_dep_file->setAttribute('href', $doc_info[0]); $my_dep->setAttribute('xml:base', ''); } elseif ($doc_info[1] == 'local') { switch ($doc_info[2]) { case 'url': // Local URL - save path as url for now, don't zip file. $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]); $current_dir = dirname($abs_path); $current_dir = str_replace('\\', '/', $current_dir); $file_path = realpath($abs_path); $file_path = str_replace('\\', '/', $file_path); $my_dep_file->setAttribute('href', $file_path); $my_dep->setAttribute('xml:base', ''); if (strstr($file_path, $main_path) !== false) { // The calculated real path is really inside Chamilo's root path. // Reduce file path to what's under the DocumentRoot. $file_path = substr($file_path, strlen($root_path) - 1); //echo $file_path;echo '<br /><br />'; //error_log(__LINE__.'Reduced url path: '.$file_path, 0); $zip_files_abs[] = $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); $my_dep_file->setAttribute('href', $file_path); $my_dep->setAttribute('xml:base', ''); } elseif (empty($file_path)) { /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH))); if (strpos($document_root, -1) == '/') { $document_root = substr(0, -1, $document_root); }*/ $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path; $file_path = str_replace('//', '/', $file_path); if (file_exists($file_path)) { $file_path = substr($file_path, strlen($current_dir)); // We get the relative path. $zip_files[] = $my_sub_dir.'/'.$file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); $my_dep_file->setAttribute('href', $file_path); $my_dep->setAttribute('xml:base', ''); } } break; case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip. $my_dep_file->setAttribute('href', $doc_info[0]); $my_dep->setAttribute('xml:base', ''); // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar $abs_img_path_without_subdir = $doc_info[0]; $relp = api_get_path(REL_PATH); // The url-append config param. $pos = strpos($abs_img_path_without_subdir, $relp); if ($pos === 0) { $abs_img_path_without_subdir = '/'.substr($abs_img_path_without_subdir, strlen($relp)); } $file_path = realpath(api_get_path(SYS_PATH).$abs_img_path_without_subdir); $file_path = str_replace('\\', '/', $file_path); $file_path = str_replace('//', '/', $file_path); // Prepare the current directory path (until just under 'document') with a trailing slash. $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/'; // Check if the current document is in that path. if (strstr($file_path, $cur_path) !== false) { // The document is in that path, now get the relative path // to the containing document. $orig_file_path = dirname($cur_path.$my_file_path).'/'; $orig_file_path = str_replace('\\', '/', $orig_file_path); $relative_path = ''; if (strstr($file_path, $cur_path) !== false) { //$relative_path = substr($file_path, strlen($orig_file_path)); $relative_path = str_replace($cur_path, '', $file_path); $file_path = substr($file_path, strlen($cur_path)); } else { // This case is still a problem as it's difficult to calculate a relative path easily // might still generate wrong links. //$file_path = substr($file_path,strlen($cur_path)); // Calculate the directory path to the current file (without trailing slash). $my_relative_path = dirname($file_path); $my_relative_path = str_replace('\\', '/', $my_relative_path); $my_relative_file = basename($file_path); // Calculate the directory path to the containing file (without trailing slash). $my_orig_file_path = substr($orig_file_path, 0, -1); $dotdot = ''; $subdir = ''; while (strstr($my_relative_path, $my_orig_file_path) === false && (strlen($my_orig_file_path) > 1) && (strlen($my_relative_path) > 1)) { $my_relative_path2 = dirname($my_relative_path); $my_relative_path2 = str_replace('\\', '/', $my_relative_path2); $my_orig_file_path = dirname($my_orig_file_path); $my_orig_file_path = str_replace('\\', '/', $my_orig_file_path); $subdir = substr($my_relative_path, strlen($my_relative_path2) + 1).'/'.$subdir; $dotdot += '../'; $my_relative_path = $my_relative_path2; } $relative_path = $dotdot.$subdir.$my_relative_file; } // Put the current document in the zip (this array is the array // that will manage documents already in the course folder - relative). $zip_files[] = $file_path; // Update the links to the current document in the containing document (make them relative). $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $relative_path); $my_dep_file->setAttribute('href', $file_path); $my_dep->setAttribute('xml:base', ''); } elseif (strstr($file_path, $main_path) !== false) { // The calculated real path is really inside Chamilo's root path. // Reduce file path to what's under the DocumentRoot. $file_path = substr($file_path, strlen($root_path)); //echo $file_path;echo '<br /><br />'; //error_log('Reduced path: '.$file_path, 0); $zip_files_abs[] = $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); $my_dep_file->setAttribute('href', 'document/'.$file_path); $my_dep->setAttribute('xml:base', ''); } elseif (empty($file_path)) { /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH))); if(strpos($document_root,-1) == '/') { $document_root = substr(0, -1, $document_root); }*/ $file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0]; $file_path = str_replace('//', '/', $file_path); $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]); $current_dir = dirname($abs_path); $current_dir = str_replace('\\', '/', $current_dir); if (file_exists($file_path)) { $file_path = substr($file_path, strlen($current_dir)); // We get the relative path. $zip_files[] = $my_sub_dir.'/'.$file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); $my_dep_file->setAttribute('href','document/'.$file_path); $my_dep->setAttribute('xml:base', ''); } } break; case 'rel': // Path relative to the current document. // Save xml:base as current document's directory and save file in zip as subdir.file_path if (substr($doc_info[0], 0, 2) == '..') { // Relative path going up. $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/'; $current_dir = str_replace('\\', '/', $current_dir); $file_path = realpath($current_dir.$doc_info[0]); $file_path = str_replace('\\', '/', $file_path); //error_log($file_path.' <-> '.$main_path,0); if (strstr($file_path, $main_path) !== false) { // The calculated real path is really inside Chamilo's root path. // Reduce file path to what's under the DocumentRoot. $file_path = substr($file_path, strlen($root_path)); //error_log('Reduced path: '.$file_path, 0); $zip_files_abs[] = $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); $my_dep_file->setAttribute('href', 'document/'.$file_path); $my_dep->setAttribute('xml:base', ''); } } else { $zip_files[] = $my_sub_dir.'/'.$doc_info[0]; $my_dep_file->setAttribute('href', $doc_info[0]); $my_dep->setAttribute('xml:base', $my_xml_sub_dir); } break; default: $my_dep_file->setAttribute('href', $doc_info[0]); $my_dep->setAttribute('xml:base', ''); break; } } $my_dep->appendChild($my_dep_file); $resources->appendChild($my_dep); $dependency = $xmldoc->createElement('dependency'); $dependency->setAttribute('identifierref', $res_id); $my_resource->appendChild($dependency); $i++; } $resources->appendChild($my_resource); $zip_files[] = $my_file_path; } else { // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it. switch ($item->type) { case TOOL_LINK: $my_item = $xmldoc->createElement('item'); $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id()); $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id()); $my_item->setAttribute('isvisible', 'true'); // Give a child element <title> to the <item> element. $my_title = $xmldoc->createElement('title', htmlspecialchars(api_utf8_encode($item->get_title()), ENT_QUOTES, 'UTF-8')); $my_item->appendChild($my_title); // Give a child element <adlcp:prerequisites> to the <item> element. $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string()); $my_prereqs->setAttribute('type', 'aicc_script'); $my_item->appendChild($my_prereqs); // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported. //$xmldoc->createElement('adlcp:maxtimeallowed', ''); // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported. //$xmldoc->createElement('adlcp:timelimitaction', ''); // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported. //$xmldoc->createElement('adlcp:datafromlms', ''); // Give a child element <adlcp:masteryscore> to the <item> element. $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score()); $my_item->appendChild($my_masteryscore); // Attach this item to the organization element or its parent if there is one. if (!empty($item->parent) && $item->parent != 0) { $children = $organization->childNodes; for ($i = 0; $i < $children->length; $i++) { $item_temp = $children->item($i); if ($item_temp -> nodeName == 'item') { if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) { $item_temp -> appendChild($my_item); } } } } else { $organization->appendChild($my_item); } $my_file_path = 'link_'.$item->get_id().'.html'; $sql = 'SELECT url, title FROM '.Database :: get_course_table(TABLE_LINK).' WHERE c_id = '.$course_id.' AND id='.$item->path; $rs = Database::query($sql); if ($link = Database :: fetch_array($rs)) { $url = $link['url']; $title = stripslashes($link['title']); $links_to_create[$my_file_path] = array('title' => $title, 'url' => $url); $my_xml_file_path = $my_file_path; $my_sub_dir = dirname($my_file_path); $my_sub_dir = str_replace('\\', '/', $my_sub_dir); $my_xml_sub_dir = $my_sub_dir; // Give a <resource> child to the <resources> element. $my_resource = $xmldoc->createElement('resource'); $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id()); $my_resource->setAttribute('type', 'webcontent'); $my_resource->setAttribute('href', $my_xml_file_path); // adlcp:scormtype can be either 'sco' or 'asset'. $my_resource->setAttribute('adlcp:scormtype', 'asset'); // xml:base is the base directory to find the files declared in this resource. $my_resource->setAttribute('xml:base', ''); // give a <file> child to the <resource> element. $my_file = $xmldoc->createElement('file'); $my_file->setAttribute('href', $my_xml_file_path); $my_resource->appendChild($my_file); $resources->appendChild($my_resource); } break; case TOOL_QUIZ: require_once api_get_path(SYS_CODE_PATH).'exercice/exercise.class.php'; $exe_id = $item->path; // Should be using ref when everything will be cleaned up in this regard. $exe = new Exercise(); $exe->read($exe_id); $my_item = $xmldoc->createElement('item'); $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id()); $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id()); $my_item->setAttribute('isvisible', 'true'); // Give a child element <title> to the <item> element. $my_title = $xmldoc->createElement('title', htmlspecialchars(api_utf8_encode($item->get_title()), ENT_QUOTES, 'UTF-8')); $my_item->appendChild($my_title); $my_max_score = $xmldoc->createElement('max_score', $item->get_max()); //$my_item->appendChild($my_max_score); // Give a child element <adlcp:prerequisites> to the <item> element. $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string()); $my_prereqs->setAttribute('type','aicc_script'); $my_item->appendChild($my_prereqs); // Give a child element <adlcp:masteryscore> to the <item> element. $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score()); $my_item->appendChild($my_masteryscore); // Attach this item to the organization element or hits parent if there is one. if (!empty($item->parent) && $item->parent != 0) { $children = $organization->childNodes; for ($i = 0; $i < $children->length; $i++) { $item_temp = $children->item($i); if ($item_temp -> nodeName == 'item') { if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) { $item_temp -> appendChild($my_item); } } } } else { $organization->appendChild($my_item); } // Include export scripts. require_once api_get_path(SYS_CODE_PATH).'exercice/export/scorm/scorm_export.php'; // Get the path of the file(s) from the course directory root //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/'); $my_file_path = 'quiz_'.$item->get_id().'.html'; // Write the contents of the exported exercise into a (big) html file // to later pack it into the exported SCORM. The file will be removed afterwards. $contents = export_exercise($exe_id, true); $tmp_file_path = $archive_path.$temp_dir_short.'/'.$my_file_path; $res = file_put_contents($tmp_file_path, $contents); if ($res === false) { error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0); } $files_cleanup[] = $tmp_file_path; //error_log($tmp_path); die(); //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_QUOTES, 'UTF-8'); $my_xml_file_path = $my_file_path; $my_sub_dir = dirname($my_file_path); $my_sub_dir = str_replace('\\', '/', $my_sub_dir); //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_QUOTES, 'UTF-8'); $my_xml_sub_dir = $my_sub_dir; // Give a <resource> child to the <resources> element. $my_resource = $xmldoc->createElement('resource'); $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id()); $my_resource->setAttribute('type', 'webcontent'); $my_resource->setAttribute('href', $my_xml_file_path); // adlcp:scormtype can be either 'sco' or 'asset'. $my_resource->setAttribute('adlcp:scormtype', 'sco'); // xml:base is the base directory to find the files declared in this resource. $my_resource->setAttribute('xml:base', ''); // Give a <file> child to the <resource> element. $my_file = $xmldoc->createElement('file'); $my_file->setAttribute('href', $my_xml_file_path); $my_resource->appendChild($my_file); // Get included docs. $inc_docs = $item->get_resources_from_source(null,$tmp_file_path); // Dependency to other files - not yet supported. $i = 1; foreach ($inc_docs as $doc_info) { if (count($doc_info) < 1 || empty($doc_info[0])) { continue; } $my_dep = $xmldoc->createElement('resource'); $res_id = 'RESOURCE_'.$item->get_id().'_'.$i; $my_dep->setAttribute('identifier', $res_id); $my_dep->setAttribute('type', 'webcontent'); $my_dep->setAttribute('adlcp:scormtype', 'asset'); $my_dep_file = $xmldoc->createElement('file'); // Check type of URL. if ($doc_info[1] == 'remote') { // Remote file. Save url as is. $my_dep_file->setAttribute('href', $doc_info[0]); $my_dep->setAttribute('xml:base', ''); } elseif ($doc_info[1] == 'local') { switch ($doc_info[2]) { case 'url': // Local URL - save path as url for now, don't zip file. // Save file but as local file (retrieve from URL). $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]); $current_dir = dirname($abs_path); $current_dir = str_replace('\\', '/', $current_dir); $file_path = realpath($abs_path); $file_path = str_replace('\\', '/', $file_path); $my_dep_file->setAttribute('href', 'document/'.$file_path); $my_dep->setAttribute('xml:base', ''); if (strstr($file_path, $main_path) !== false) { // The calculated real path is really inside the chamilo root path. // Reduce file path to what's under the DocumentRoot. $file_path = substr($file_path, strlen($root_path)); //echo $file_path;echo '<br /><br />'; //error_log('Reduced path: '.$file_path, 0); $zip_files_abs[] = $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => 'document/'.$file_path); $my_dep_file->setAttribute('href', 'document/'.$file_path); $my_dep->setAttribute('xml:base', ''); } elseif (empty($file_path)) { /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH),api_get_path(REL_PATH))); if (strpos($document_root,-1) == '/') { $document_root = substr(0, -1, $document_root); }*/ $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path; $file_path = str_replace('//', '/', $file_path); if (file_exists($file_path)) { $file_path = substr($file_path, strlen($current_dir)); // We get the relative path. $zip_files[] = $my_sub_dir.'/'.$file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => 'document/'.$file_path); $my_dep_file->setAttribute('href', 'document/'.$file_path); $my_dep->setAttribute('xml:base', ''); } } break; case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip. $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/'; $current_dir = str_replace('\\', '/', $current_dir); $file_path = realpath($doc_info[0]); $file_path = str_replace('\\', '/', $file_path); $my_dep_file->setAttribute('href', $file_path); $my_dep->setAttribute('xml:base', ''); if (strstr($file_path,$main_path) !== false) { // The calculated real path is really inside the chamilo root path. // Reduce file path to what's under the DocumentRoot. $file_path = substr($file_path, strlen($root_path)); //echo $file_path;echo '<br /><br />'; //error_log('Reduced path: '.$file_path, 0); $zip_files_abs[] = $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); $my_dep_file->setAttribute('href', 'document/'.$file_path); $my_dep->setAttribute('xml:base', ''); } elseif (empty($file_path)) { /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH))); if (strpos($document_root,-1) == '/') { $document_root = substr(0, -1, $document_root); }*/ $file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0]; $file_path = str_replace('//', '/', $file_path); if (file_exists($file_path)) { $file_path = substr($file_path,strlen($current_dir)); // We get the relative path. $zip_files[] = $my_sub_dir.'/'.$file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); $my_dep_file->setAttribute('href', 'document/'.$file_path); $my_dep->setAttribute('xml:base', ''); } } break; case 'rel': // Path relative to the current document. Save xml:base as current document's directory and save file in zip as subdir.file_path if (substr($doc_info[0], 0, 2) == '..') { // Relative path going up. $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/'; $current_dir = str_replace('\\', '/', $current_dir); $file_path = realpath($current_dir.$doc_info[0]); $file_path = str_replace('\\', '/', $file_path); //error_log($file_path.' <-> '.$main_path, 0); if (strstr($file_path, $main_path) !== false) { // The calculated real path is really inside Chamilo's root path. // Reduce file path to what's under the DocumentRoot. $file_path = substr($file_path, strlen($root_path)); $file_path_dest = $file_path; // File path is courses/CHAMILO/document/.... $info_file_path = explode('/', $file_path); if ($info_file_path[0] == 'courses') { // Add character "/" in file path. $file_path_dest = 'document/'.$file_path; } //error_log('Reduced path: '.$file_path, 0); $zip_files_abs[] = $file_path; $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path_dest); $my_dep_file->setAttribute('href', 'document/'.$file_path); $my_dep->setAttribute('xml:base', ''); } } else { $zip_files[] = $my_sub_dir.'/'.$doc_info[0]; $my_dep_file->setAttribute('href', $doc_info[0]); $my_dep->setAttribute('xml:base', $my_xml_sub_dir); } break; default: $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/ $my_dep->setAttribute('xml:base', ''); break; } } $my_dep->appendChild($my_dep_file); $resources->appendChild($my_dep); $dependency = $xmldoc->createElement('dependency'); $dependency->setAttribute('identifierref', $res_id); $my_resource->appendChild($dependency); $i++; } $resources->appendChild($my_resource); $zip_files[] = $my_file_path; break; default: // Get the path of the file(s) from the course directory root $my_file_path = 'non_exportable.html'; //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8'); $my_xml_file_path = $my_file_path; $my_sub_dir = dirname($my_file_path); $my_sub_dir = str_replace('\\', '/', $my_sub_dir); //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8'); $my_xml_sub_dir = $my_sub_dir; // Give a <resource> child to the <resources> element. $my_resource = $xmldoc->createElement('resource'); $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id()); $my_resource->setAttribute('type', 'webcontent'); $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path); // adlcp:scormtype can be either 'sco' or 'asset'. $my_resource->setAttribute('adlcp:scormtype', 'asset'); // xml:base is the base directory to find the files declared in this resource. $my_resource->setAttribute('xml:base', ''); // Give a <file> child to the <resource> element. $my_file = $xmldoc->createElement('file'); $my_file->setAttribute('href', 'document/'.$my_xml_file_path); $my_resource->appendChild($my_file); $resources->appendChild($my_resource); break; } } } $organizations->appendChild($organization); $root->appendChild($organizations); $root->appendChild($resources); $xmldoc->appendChild($root); // TODO: Add a readme file here, with a short description and a link to the Reload player // then add the file to the zip, then destroy the file (this is done automatically). // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138 global $_configuration; $append = $_configuration['url_append']; foreach ($zip_files as $file_path) { if (empty($file_path)) { continue; } $dest_file = $archive_path.$temp_dir_short.'/'.$file_path; if (!empty($path_to_remove) && !empty($path_to_replace)) { $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file); } $this->create_path($dest_file); @copy($sys_course_path.$_course['path'].'/'.$file_path, $dest_file); // Check if the file needs a link update. if (in_array($file_path, array_keys($link_updates))) { $string = file_get_contents($dest_file); unlink($dest_file); foreach ($link_updates[$file_path] as $old_new) { // This is an ugly hack that allows .flv files to be found by the flv player that // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs // to find the flv to play in document/main/, so we replace main/ in the flv path by // ../../.. to return from inc/lib/flv_player to the document/main path. if (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 5) == 'main/') { $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']); } elseif (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 6) == 'video/') { $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']); } //Fix to avoid problems with default_course_document if (strpos("main/default_course_document", $old_new['dest'] === false)) { //$newDestination = str_replace('document/', $mult.'document/', $old_new['dest']); $newDestination = $old_new['dest']; } else { $newDestination = str_replace('document/', '', $old_new['dest']); } $string = str_replace($old_new['orig'], $newDestination, $string); //Add files inside the HTMLs $new_path = str_replace('/courses/', '', $old_new['orig']); $destinationFile = $archive_path.$temp_dir_short.'/'.$old_new['dest']; if (file_exists($sys_course_path.$new_path)) { copy($sys_course_path.$new_path, $destinationFile); } } file_put_contents($dest_file, $string); } } foreach ($zip_files_abs as $file_path) { if (empty($file_path)) { continue; } if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) { continue; } $dest_file = $archive_path.$temp_dir_short.'/document/'.$file_path; $this->create_path($dest_file); copy($main_path.$file_path, $dest_file); // Check if the file needs a link update. if (in_array($file_path, array_keys($link_updates))) { $string = file_get_contents($dest_file); unlink($dest_file); foreach ($link_updates[$file_path] as $old_new) { // This is an ugly hack that allows .flv files to be found by the flv player that // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs // to find the flv to play in document/main/, so we replace main/ in the flv path by // ../../.. to return from inc/lib/flv_player to the document/main path. if (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 5) == 'main/') { $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']); } $string = str_replace($old_new['orig'], $old_new['dest'], $string); } file_put_contents($dest_file, $string); } } if (is_array($links_to_create)) { foreach ($links_to_create as $file => $link) { $file_content = '<!DOCTYPE html><head> <meta charset="'.api_get_language_isocode().'" /> <title>'.$link['title'].'</title> </head> <body dir="'.api_get_text_direction().'"> <div style="text-align:center"> <a href="'.$link['url'].'">'.$link['title'].'</a></div> </body> </html>'; file_put_contents($archive_path.$temp_dir_short.'/'.$file, $file_content); } } // Add non exportable message explanation. $lang_not_exportable = get_lang('ThisItemIsNotExportable'); $file_content = '<!DOCTYPE html><head> <meta charset="'.api_get_language_isocode().'" /> <title>'.$lang_not_exportable.'</title> <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" /> </head> <body dir="'.api_get_text_direction().'">'; $file_content .= <<<EOD <style> .error-message { font-family: arial, verdana, helvetica, sans-serif; border-width: 1px; border-style: solid; left: 50%; margin: 10px auto; min-height: 30px; padding: 5px; right: 50%; width: 500px; background-color: #FFD1D1; border-color: #FF0000; color: #000; } </style> <body> <div class="error-message"> $lang_not_exportable </div> </body> </html> EOD; if (!is_dir($archive_path.$temp_dir_short.'/document')) { @mkdir($archive_path.$temp_dir_short.'/document', api_get_permissions_for_new_directories()); } file_put_contents($archive_path.$temp_dir_short.'/document/non_exportable.html', $file_content); // Add the extra files that go along with a SCORM package. $main_code_path = api_get_path(SYS_CODE_PATH).'newscorm/packaging/'; $extra_files = scandir($main_code_path); foreach ($extra_files as $extra_file) { if (strpos($extra_file, '.') === 0) continue; else { $dest_file = $archive_path . $temp_dir_short . '/' . $extra_file; $this->create_path($dest_file); copy($main_code_path.$extra_file, $dest_file); } } // Finalize the imsmanifest structure, add to the zip, then return the zip. $manifest = @$xmldoc->saveXML(); $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now. file_put_contents($archive_path.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest); $zip_folder->add($archive_path.'/'.$temp_dir_short, PCLZIP_OPT_REMOVE_PATH, $archive_path.'/'.$temp_dir_short.'/'); // Clean possible temporary files. foreach ($files_cleanup as $file) { $res = unlink($file); if ($res === false) { error_log('Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__, 0); } } // Send file to client. require_once api_get_path(LIBRARY_PATH).'fileUpload.lib.php'; $name = replace_dangerous_char($this->get_name()).'.zip'; DocumentManager::file_send_for_download($temp_zip_file, true, $name); }