scorm.class.php 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  1. <?php
  2. /**
  3. * Defines the scorm class, which is meant to contain the scorm items (nuclear elements)
  4. * @package dokeos.learnpath.scorm
  5. * @author Yannick Warnier <ywarnier@beeznest.org>
  6. */
  7. /**
  8. * Defines the "scorm" child of class "learnpath"
  9. * @package dokeos.learnpath.scorm
  10. */
  11. require_once('scormItem.class.php');
  12. require_once('scormMetadata.class.php');
  13. require_once('scormOrganization.class.php');
  14. require_once('scormResource.class.php');
  15. class scorm extends learnpath {
  16. var $manifest = array();
  17. var $resources = array();
  18. var $resources_att = array();
  19. var $organizations = array();
  20. var $organizations_att = array();
  21. var $metadata = array();
  22. var $idrefs = array(); //will hold the references to resources for each item ID found
  23. var $refurls = array(); //for each resource found, stores the file url/uri
  24. var $subdir = ''; //path between the scorm/ directory and the imsmanifest.xml e.g. maritime_nav/maritime_nav. This is the path that will be used in the lp_path when importing a package
  25. var $items = array();
  26. var $zipname = ''; //keeps the zipfile safe for the object's life so that we can use it if no title avail
  27. var $lastzipnameindex = 0; //keeps an index of the number of uses of the zipname so far
  28. var $manifest_encoding = 'UTF-8';
  29. var $debug = 0;
  30. /**
  31. * Class constructor. Based on the parent constructor.
  32. * @param string Course code
  33. * @param integer Learnpath ID in DB
  34. * @param integer User ID
  35. */
  36. function scorm($course_code=null,$resource_id=null,$user_id=null) {
  37. if($this->debug>0){error_log('New LP - scorm::scorm('.$course_code.','.$resource_id.','.$user_id.') - In scorm constructor',0);}
  38. if(!empty($course_code) and !empty($resource_id) and !empty($user_id))
  39. {
  40. parent::learnpath($course_code, $resource_id, $user_id);
  41. }else{
  42. //do nothing but still build the scorm object
  43. }
  44. }
  45. /**
  46. * Opens a resource
  47. * @param integer Database ID of the resource
  48. */
  49. function open($id)
  50. {
  51. if($this->debug>0){error_log('New LP - scorm::open() - In scorm::open method',0);}
  52. // redefine parent method
  53. }
  54. /**
  55. * Possible SCO status: see CAM doc 2.3.2.5.1: passed, completed, browsed, failed, not attempted, incomplete
  56. */
  57. /**
  58. * Prerequisites: see CAM doc 2.3.2.5.1 for pseudo-code
  59. */
  60. /**
  61. * Parses an imsmanifest.xml file and puts everything into the $manifest array
  62. * @param string Path to the imsmanifest.xml file on the system. If not defined, uses the base path of the course's scorm dir
  63. * @return array Structured array representing the imsmanifest's contents
  64. */
  65. function parse_manifest($file='')
  66. {
  67. if($this->debug>0){error_log('In scorm::parse_manifest('.$file.')',0);}
  68. if(empty($file)){
  69. //get the path of the imsmanifest file
  70. }
  71. if(is_file($file) and is_readable($file))
  72. {
  73. $v = substr(phpversion(),0,1);
  74. if($v == 4){
  75. if($this->debug>0){error_log('In scorm::parse_manifest() - Parsing using PHP4 method',0);}
  76. $var = file_get_contents($file);
  77. //quick ugly hack to remove xml:id attributes from the file (break the system if xslt not installed)
  78. $var = preg_replace('/xml:id=["\']id_\d{1,4}["\']/i','',$var);
  79. $doc = xmldoc($var);
  80. //error_reporting(E_ALL);
  81. //$doc = xmldocfile($file);
  82. if(!empty($doc->encoding)){
  83. $this->manifest_encoding = strtoupper($doc->encoding);
  84. }
  85. if($this->debug>1){error_log('New LP - Called xmldoc() (encoding:'.strtoupper($doc->encoding).')',0);}
  86. if(!$doc)
  87. {
  88. if($this->debug>1){error_log('New LP - File '.$file.' is not an XML file',0);}
  89. //$this->setErrorMsg("File $file is not an XML file");
  90. return null;
  91. }else{
  92. if($this->debug>1){error_log('New LP - File '.$file.' is XML',0);}
  93. $root = $doc->root();
  94. $attributes = $root->attributes();
  95. for($a=0; $a<sizeof($attributes);$a++)
  96. {//<manifest> element attributes
  97. if($attributes[$a]->type == XML_ATTRIBUTE_NODE){
  98. $this->manifest[$attributes[$a]->name] = $attributes[$a]->value;
  99. }
  100. }
  101. $this->manifest['name'] = $root->name;
  102. $children = $root->children();
  103. for($b=0; $b<sizeof($children);$b++)
  104. {
  105. //<manifest> element children (can be <metadata>, <organizations> or <resources> )
  106. if($children[$b]->type == XML_ELEMENT_NODE){
  107. switch($children[$b]->tagname){
  108. case 'metadata':
  109. //parse items from inside the <metadata> element
  110. $this->metadata = new scormMetadata('manifest',$children[$b]);
  111. break;
  112. case 'organizations':
  113. //contains the course structure - this element appears 1 and only 1 time in a package imsmanifest. It contains at least one 'organization' sub-element
  114. $orgs_attribs = $children[$b]->attributes();
  115. for($c=0; $c<sizeof($orgs_attribs);$c++)
  116. {//attributes of the <organizations> element
  117. if($orgs_attribs[$c]->type == XML_ATTRIBUTE_NODE){
  118. $this->manifest['organizations'][$orgs_attribs[$c]->name] = $orgs_attribs[$c]->value;
  119. }
  120. }
  121. $orgs_nodes = $children[$b]->children();
  122. $i = 0;
  123. $found_an_org = false;
  124. foreach($orgs_nodes as $c => $dummy)
  125. {
  126. //<organization> elements - can contain <item>, <metadata> and <title>
  127. //Here we are at the 'organization' level. There might be several organization tags but
  128. //there is generally only one.
  129. //There are generally three children nodes we are looking for inside and organization:
  130. //-title
  131. //-item (may contain other item tags or may appear several times inside organization)
  132. //-metadata (relative to the organization)
  133. $found_an_org = false;
  134. $orgnode =& $orgs_nodes[$c];
  135. switch($orgnode->type){
  136. case XML_TEXT_NODE:
  137. //ignore here
  138. break;
  139. case XML_ATTRIBUTE_NODE:
  140. //just in case ther would be interesting attributes inside the organization tag. There shouldn't
  141. //as this is a node-level, not a data level
  142. //$manifest['organizations'][$i][$orgnode->name] = $orgnode->value;
  143. //$found_an_org = true;
  144. break;
  145. case XML_ELEMENT_NODE:
  146. //<item>,<metadata> or <title> (or attributes)
  147. $organizations_attributes = $orgnode->attributes();
  148. foreach($organizations_attributes as $d1 => $dummy)
  149. {
  150. //$manifest['organizations'][$i]['attributes'][$organizations_attributes[$d1]->name] = $organizations_attributes[$d1]->value;
  151. //$found_an_org = true;
  152. $this->organizations_att[$organizations_attributes[$d1]->name] = $organizations_attributes[$d1]->value;
  153. }
  154. $oOrganization = new scormOrganization('manifest',$orgnode);
  155. if($oOrganization->identifier != ''){
  156. $name = $oOrganization->get_name();
  157. if(empty($name)){
  158. //if the org title is empty, use zip file name
  159. $myname = $this->zipname;
  160. if($this->lastzipnameindex != 0){
  161. $myname = $myname + $this->lastzipnameindex;
  162. $this->lastzipnameindex++;
  163. }
  164. $oOrganization->set_name($this->zipname);
  165. }
  166. $this->organizations[$oOrganization->identifier] = $oOrganization;
  167. }
  168. break;
  169. }
  170. }
  171. break;
  172. case 'resources':
  173. $resources_attribs = $children[$b]->attributes();
  174. for($c=0; $c<sizeof($resources_attribs);$c++)
  175. {
  176. if($resources_attribs[$c]->type == XML_ATTRIBUTE_NODE){
  177. $this->manifest['resources'][$resources_attribs[$c]->name] = $resources_attribs[$c]->value;
  178. }
  179. }
  180. $resources_nodes = $children[$b]->children();
  181. $i = 0;
  182. foreach($resources_nodes as $c => $dummy)
  183. {
  184. /*
  185. $my_res = scorm::parse_items($resources_nodes[$c]);
  186. scorm::parse_items_get_refurls($resources_nodes[$c],$refurls);
  187. if(count($my_res)>0)
  188. {
  189. $manifest['resources'][$i] = $my_res;
  190. }
  191. */
  192. $oResource = new scormResource('manifest',$resources_nodes[$c]);
  193. if($oResource->identifier != ''){
  194. $this->resources[$oResource->identifier] = $oResource;
  195. $i++;
  196. }
  197. }
  198. //contains links to physical resources
  199. break;
  200. case 'manifest':
  201. //only for sub-manifests
  202. break;
  203. }
  204. }
  205. }
  206. }
  207. unset($doc);
  208. }elseif($v==5){
  209. if($this->debug>0){error_log('In scorm::parse_manifest() - Parsing using PHP5 method',0);}
  210. $doc = new DOMDocument();
  211. $res = $doc->load($file);
  212. if($res===false){
  213. if($this->debug>0){error_log('New LP - In scorm::parse_manifest() - Exception thrown when loading '.$file.' in DOMDocument',0);}
  214. //throw exception?
  215. return null;
  216. }
  217. if(!empty($doc->xmlEncoding)){
  218. $this->manifest_encoding = strtoupper($doc->xmlEncoding);
  219. }
  220. if($this->debug>1){error_log('New LP - Called (encoding:'.$doc->xmlEncoding.')',0);}
  221. $root = $doc->documentElement;
  222. if($root->hasAttributes()){
  223. $attributes = $root->attributes;
  224. if($attributes->length !== 0){
  225. foreach($attributes as $attrib)
  226. {//<manifest> element attributes
  227. $this->manifest[$attrib->name] = $attrib->value;
  228. }
  229. }
  230. }
  231. $this->manifest['name'] = $root->tagName;
  232. if($root->hasChildNodes()){
  233. $children = $root->childNodes;
  234. if($children->length !== 0){
  235. foreach($children as $child)
  236. {
  237. //<manifest> element children (can be <metadata>, <organizations> or <resources> )
  238. if($child->nodeType == XML_ELEMENT_NODE){
  239. switch($child->tagName){
  240. case 'metadata':
  241. //parse items from inside the <metadata> element
  242. $this->metadata = new scormMetadata('manifest',$child);
  243. break;
  244. case 'organizations':
  245. //contains the course structure - this element appears 1 and only 1 time in a package imsmanifest. It contains at least one 'organization' sub-element
  246. $orgs_attribs = $child->attributes;
  247. foreach($orgs_attribs as $orgs_attrib)
  248. {//attributes of the <organizations> element
  249. if($orgs_attrib->nodeType == XML_ATTRIBUTE_NODE){
  250. $this->manifest['organizations'][$orgs_attrib->name] = $orgs_attrib->value;
  251. }
  252. }
  253. $orgs_nodes = $child->childNodes;
  254. $i = 0;
  255. $found_an_org = false;
  256. foreach($orgs_nodes as $orgnode)
  257. {
  258. //<organization> elements - can contain <item>, <metadata> and <title>
  259. //Here we are at the 'organization' level. There might be several organization tags but
  260. //there is generally only one.
  261. //There are generally three children nodes we are looking for inside and organization:
  262. //-title
  263. //-item (may contain other item tags or may appear several times inside organization)
  264. //-metadata (relative to the organization)
  265. $found_an_org = false;
  266. switch($orgnode->nodeType){
  267. case XML_TEXT_NODE:
  268. //ignore here
  269. break;
  270. case XML_ATTRIBUTE_NODE:
  271. //just in case there would be interesting attributes inside the organization tag. There shouldn't
  272. //as this is a node-level, not a data level
  273. //$manifest['organizations'][$i][$orgnode->name] = $orgnode->value;
  274. //$found_an_org = true;
  275. break;
  276. case XML_ELEMENT_NODE:
  277. //<item>,<metadata> or <title> (or attributes)
  278. $organizations_attributes = $orgnode->attributes;
  279. foreach($organizations_attributes as $orgs_attr)
  280. {
  281. $this->organizations_att[$orgs_attr->name] = $orgs_attr->value;
  282. }
  283. $oOrganization = new scormOrganization('manifest',$orgnode);
  284. if($oOrganization->identifier != ''){
  285. $name = $oOrganization->get_name();
  286. if(empty($name)){
  287. //if the org title is empty, use zip file name
  288. $myname = $this->zipname;
  289. if($this->lastzipnameindex != 0){
  290. $myname = $myname + $this->lastzipnameindex;
  291. $this->lastzipnameindex++;
  292. }
  293. $oOrganization->set_name($this->zipname);
  294. }
  295. $this->organizations[$oOrganization->identifier] = $oOrganization;
  296. }
  297. break;
  298. }
  299. }
  300. break;
  301. case 'resources':
  302. if($child->hasAttributes()){
  303. $resources_attribs = $child->attributes;
  304. foreach($resources_attribs as $res_attr)
  305. {
  306. if($res_attr->type == XML_ATTRIBUTE_NODE){
  307. $this->manifest['resources'][$res_attr->name] = $res_attr->value;
  308. }
  309. }
  310. }
  311. if($child->hasChildNodes()){
  312. $resources_nodes = $child->childNodes;
  313. $i = 0;
  314. foreach($resources_nodes as $res_node)
  315. {
  316. $oResource = new scormResource('manifest',$res_node);
  317. if($oResource->identifier != ''){
  318. $this->resources[$oResource->identifier] = $oResource;
  319. $i++;
  320. }
  321. }
  322. }
  323. //contains links to physical resources
  324. break;
  325. case 'manifest':
  326. //only for sub-manifests
  327. break;
  328. }
  329. }
  330. }
  331. }
  332. }
  333. unset($doc);
  334. }else{
  335. if($this->debug>0){error_log('In scorm::parse_manifest() - PHP version is not 4 nor 5, cannot parse',0);}
  336. $this->setErrorMsg("Parsing impossible because PHP version is not 4 nor 5");
  337. return null;
  338. }
  339. }else{
  340. if($this->debug>1){error_log('New LP - Could not open/read file '.$file,0);}
  341. $this->setErrorMsg("File $file could not be read");
  342. return null;
  343. }
  344. //TODO close the DOM handler
  345. return $this->manifest;
  346. }
  347. /**
  348. * Import the scorm object (as a result from the parse_manifest function) into the database structure
  349. * @param string Unique course code
  350. * @return bool Returns -1 on error
  351. */
  352. function import_manifest($course_code){
  353. if($this->debug>0){error_log('New LP - Entered import_manifest('.$course_code.')',0);}
  354. //get table names
  355. $new_lp = 'lp';
  356. $new_lp_item = 'lp_item';
  357. //The previous method wasn't safe to get the database name, so do it manually with the course_code
  358. $sql = "SELECT * FROM ".Database::get_main_table(MAIN_COURSE_TABLE)." WHERE code='$course_code'";
  359. $res = api_sql_query($sql,__FILE__,__LINE__);
  360. if(Database::num_rows($res)<1){ error_log('Database for '.$course_code.' not found '.__FILE__.' '.__LINE__,0);return -1;}
  361. $row = Database::fetch_array($res);
  362. $dbname = Database::get_course_table_prefix().$row['db_name'].Database::get_database_glue();
  363. $new_lp = Database::get_course_table('lp');
  364. $new_lp_item = Database::get_course_table('lp_item');
  365. foreach($this->organizations as $id => $dummy)
  366. {
  367. $oOrganization =& $this->organizations[$id];
  368. //prepare and execute insert queries
  369. //-for learnpath
  370. //-for items
  371. //-for views?
  372. $myname = $oOrganization->get_name();
  373. $this->manifest_encoding = 'UTF-8';
  374. if($this->manifest_encoding != 'ISO-8859-1'){
  375. $myname = mb_convert_encoding($myname,'ISO-8859-1',$this->manifest_encoding);
  376. //error_log('New LP - Converting name from '.$this->manifest_encoding.' to ISO-8859-1',0);
  377. }
  378. $sql = "INSERT INTO $new_lp (lp_type, name, ref, description, path, force_commit, default_view_mod, default_encoding, js_lib)" .
  379. "VALUES (2,'".$myname."', '".$oOrganization->get_ref()."','','".$this->subdir."', 1, 'embedded', '".$this->manifest_encoding."','scorm_api.php')";
  380. if($this->debug>1){error_log('New LP - In import_manifest(), inserting path: '. $sql,0);}
  381. $res = api_sql_query($sql,__FILE__,__LINE__);
  382. $lp_id = Database::get_last_insert_id();
  383. $this->lp_id = $lp_id;
  384. //now insert all elements from inside that learning path
  385. //make sure we also get the href and sco/asset from the resources
  386. $list = $oOrganization->get_flat_items_list();
  387. $parents_stack = array(0);
  388. $parent = 0;
  389. $previous = 0;
  390. $level = 0;
  391. foreach($list as $item){
  392. if($item['level'] > $level){
  393. //push something into the parents array
  394. array_push($parents_stack,$previous);
  395. $parent = $previous;
  396. }elseif($item['level'] < $level){
  397. $diff = $level - $item['level'];
  398. //pop something out of the parents array
  399. for($j=1;$j<=$diff;$j++){
  400. $outdated_parent = array_pop($parents_stack);
  401. }
  402. $parent = array_pop($parents_stack); //just save that value, then add it back
  403. array_push($parents_stack,$parent);
  404. }
  405. $path = '';
  406. $type = 'dir';
  407. if(isset($this->resources[$item['identifierref']])){
  408. $oRes =& $this->resources[$item['identifierref']];
  409. $path = @$oRes->get_path();
  410. if(!empty($path)){
  411. $temptype = $oRes->get_scorm_type();
  412. if(!empty($temptype)){
  413. $type = $temptype;
  414. }
  415. }
  416. }
  417. $level = $item['level'];
  418. $field_add = '';
  419. $value_add = '';
  420. if(!empty($item['masteryscore'])){
  421. $field_add = 'mastery_score, ';
  422. $value_add = $item['masteryscore'].',';
  423. }
  424. $title = mysql_real_escape_string($item['title']);
  425. //if($this->manifest_encoding != 'ISO-8859-1'){
  426. //$title = mb_convert_encoding($title,'ISO-8859-1',$this->manifest_encoding);
  427. //}
  428. $identifier = mysql_real_escape_string($item['identifier']);
  429. $prereq = mysql_real_escape_string($item['prerequisites']);
  430. $sql_item = "INSERT INTO $new_lp_item " .
  431. "(lp_id,item_type,ref,title," .
  432. "path,min_score,max_score, $field_add" .
  433. "parent_item_id,previous_item_id,next_item_id," .
  434. "prerequisite,display_order) " .
  435. "VALUES " .
  436. "($lp_id, '$type','".$identifier."','".$title."'," .
  437. "'$path',0,100, $value_add" .
  438. "$parent, $previous, 0, " .
  439. "'$prereq', ".$item['rel_order'] .
  440. ")";
  441. $res_item = api_sql_query($sql_item);
  442. if($this->debug>1){error_log('New LP - In import_manifest(), inserting item : '.$sql_item.' : '.mysql_error(),0);}
  443. $item_id = Database::get_last_insert_id();
  444. //now update previous item to change next_item_id
  445. $upd = "UPDATE $new_lp_item SET next_item_id = $item_id WHERE id = $previous";
  446. $upd_res = api_sql_query($upd);
  447. //update previous item id
  448. $previous = $item_id;
  449. }
  450. }
  451. }
  452. /**
  453. * Intermediate to import_package only to allow import from local zip files
  454. * @param string Path to the zip file, from the dokeos sys root
  455. * @param string Current path (optional)
  456. * @return string Absolute path to the imsmanifest.xml file or empty string on error
  457. */
  458. function import_local_package($file_path,$current_dir='')
  459. {
  460. //todo prepare info as given by the $_FILES[''] vector
  461. $file_info = array();
  462. $file_info['tmp_name'] = $file_path;
  463. $file_info['name'] = basename($file_path);
  464. //call the normal import_package function
  465. return $this->import_package($file_info,$current_dir);
  466. }
  467. /**
  468. * Imports a zip file into the Dokeos structure
  469. * @param string Zip file info as given by $_FILES['userFile']
  470. * @return string Absolute path to the imsmanifest.xml file or empty string on error
  471. */
  472. function import_package($zip_file_info,$current_dir = '')
  473. {
  474. if($this->debug>0){error_log('In scorm::import_package('.print_r($zip_file_info,true).',"'.$current_dir.'") method',0);}
  475. $maxFilledSpace = 1000000000;
  476. $zip_file_path = $zip_file_info['tmp_name'];
  477. $zip_file_name = $zip_file_info['name'];
  478. if($this->debug==1){error_log('New LP - import_package() - zip file path = '.$zip_file_path.', zip file name = '.$zip_file_name,0);}
  479. $course_rel_dir = api_get_course_path().'/scorm'; //scorm dir web path starting from /courses
  480. $course_sys_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir; //absolute system path for this course
  481. $current_dir = replace_dangerous_char(trim($current_dir),'strict'); //current dir we are in, inside scorm/
  482. if($this->debug==1){error_log('New LP - import_package() - current_dir = '.$current_dir,0);}
  483. //$uploaded_filename = $_FILES['userFile']['name'];
  484. //get name of the zip file without the extension
  485. if($this->debug==1){error_log('New LP - Received zip file name: '.$zip_file_path,0);}
  486. $file_info = pathinfo($zip_file_name);
  487. $filename = $file_info['basename'];
  488. $extension = $file_info['extension'];
  489. $file_base_name = str_replace('.'.$extension,'',$filename); //filename without its extension
  490. $this->zipname = $file_base_name; //save for later in case we don't have a title
  491. if($this->debug==1){error_log("New LP - base file name is : ".$file_base_name,0);}
  492. $new_dir = replace_dangerous_char(trim($file_base_name),'strict');
  493. $this->subdir = $new_dir;
  494. if($this->debug==1){error_log("New LP - subdir is first set to : ".$this->subdir,0);}
  495. /*
  496. if( check_name_exist($course_sys_dir.$current_dir."/".$new_dir) )
  497. {
  498. $dialogBox = get_lang('FileExists');
  499. $stopping_error = true;
  500. }
  501. */
  502. $zipFile = new pclZip($zip_file_path);
  503. // Check the zip content (real size and file extension)
  504. $zipContentArray = $zipFile->listContent();
  505. $package_type='';
  506. $at_root = false;
  507. $manifest = '';
  508. //the following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?)
  509. foreach($zipContentArray as $thisContent)
  510. {
  511. if ( preg_match('~.(php.*|phtml)$~i', $thisContent['filename']) )
  512. {
  513. return api_failure::set_failure('php_file_in_zip_file');
  514. }
  515. elseif(stristr($thisContent['filename'],'imsmanifest.xml'))
  516. {
  517. if($thisContent['filename'] == basename($thisContent['filename'])){
  518. $at_root = true;
  519. }else{
  520. $this->subdir .= '/'.dirname($thisContent['filename']);
  521. if($this->debug==1){error_log("New LP - subdir is now ".$this->subdir,0);}
  522. }
  523. $manifest = $thisContent['filename']; //just the relative directory inside scorm/
  524. $package_type = 'scorm';
  525. break;//exit the foreach loop
  526. }
  527. /*
  528. elseif(stristr($thisContent['filename'],'LMS'))
  529. {
  530. $package_type = 'scorm_plantyn';
  531. break;//exit the foreach loop
  532. }
  533. elseif(stristr($thisContent['filename'],'REF'))
  534. {
  535. $package_type='scorm_plantyn2';
  536. break;//exit the foreach loop
  537. }
  538. elseif(stristr($thisContent['filename'],'SCO'))
  539. {
  540. $package_type='scorm_plantyn3';
  541. break;//exit the foreach loop
  542. }
  543. elseif(stristr($thisContent['filename'],'AICC'))
  544. {
  545. $package_type='aicc';
  546. break;//exit the foreach loop
  547. }*/
  548. else
  549. {
  550. $package_type = '';
  551. }
  552. $realFileSize += $thisContent['size'];
  553. }
  554. if($this->debug>1){error_log('New LP - Package type is now '.$package_type,0);}
  555. if($package_type== '')
  556. // && defined('CHECK_FOR_SCORM') && CHECK_FOR_SCORM)
  557. {
  558. return api_failure::set_failure('not_scorm_content');
  559. }
  560. if (! enough_size($realFileSize, $course_sys_dir, $maxFilledSpace) )
  561. {
  562. return api_failure::set_failure('not_enough_space');
  563. }
  564. // it happens on Linux that $new_dir sometimes doesn't start with '/'
  565. if($new_dir[0] != '/')
  566. {
  567. $new_dir='/'.$new_dir;
  568. }
  569. if($new_dir[strlen($new_dir)-1] == '/')
  570. {
  571. $new_dir=substr($new_dir,0,-1);
  572. }
  573. /*
  574. --------------------------------------
  575. Uncompressing phase
  576. --------------------------------------
  577. */
  578. /*
  579. The first version, using OS unzip, is not used anymore
  580. because it does not return enough information.
  581. We need to process each individual file in the zip archive to
  582. - add it to the database
  583. - parse & change relative html links
  584. */
  585. if (PHP_OS == 'Linux' && ! get_cfg_var('safe_mode') && false) // *** UGent, changed by OC ***
  586. {
  587. // Shell Method - if this is possible, it gains some speed
  588. //check availability of 'unzip' first!
  589. exec("unzip -d \"".$course_sys_dir."".$new_dir." ".$zip_file_path);
  590. if($this->debug>=1){error_log('New LP - found Linux system, using unzip',0);}
  591. }
  592. elseif(is_dir($course_sys_dir.$new_dir) OR @mkdir($course_sys_dir.$new_dir))
  593. {
  594. // PHP method - slower...
  595. if($this->debug>=1){error_log('New LP - Changing dir to '.$course_sys_dir.$new_dir,0);}
  596. $saved_dir = getcwd();
  597. chdir($course_sys_dir.$new_dir);
  598. $unzippingState = $zipFile->extract();
  599. for($j=0;$j<count($unzippingState);$j++)
  600. {
  601. $state=$unzippingState[$j];
  602. //TODO fix relative links in html files (?)
  603. $extension = strrchr($state["stored_filename"], ".");
  604. if($this->debug>=1){error_log('New LP - found extension '.$extension.' in '.$state['stored_filename'],0);}
  605. }
  606. if(!empty($new_dir))
  607. {
  608. $new_dir = $new_dir.'/';
  609. }
  610. //rename files, for example with \\ in it
  611. if($dir=@opendir($course_sys_dir.$new_dir))
  612. {
  613. if($this->debug==1){error_log('New LP - Opened dir '.$course_sys_dir.$new_dir,0);}
  614. while($file=readdir($dir))
  615. {
  616. if($file != '.' && $file != '..')
  617. {
  618. $filetype="file";
  619. if(is_dir($course_sys_dir.$new_dir.$file)) $filetype="folder";
  620. //TODO RENAMING FILES CAN BE VERY DANGEROUS SCORM-WISE, avoid that as much as possible!
  621. //$safe_file=replace_dangerous_char($file,'strict');
  622. $safe_file = str_replace('\\','/',$file);
  623. if($safe_file != $file){
  624. //@rename($course_sys_dir.$new_dir,$course_sys_dir.'/'.$safe_file);
  625. $mydir = dirname($course_sys_dir.$new_dir.$safe_file);
  626. if(!is_dir($mydir)){
  627. $mysubdirs = split('/',$mydir);
  628. $mybasedir = '/';
  629. foreach($mysubdirs as $mysubdir){
  630. if(!empty($mysubdir)){
  631. $mybasedir = $mybasedir.$mysubdir.'/';
  632. if(!is_dir($mybasedir)){
  633. @mkdir($mybasedir);
  634. if($this->debug==1){error_log('New LP - Dir '.$mybasedir.' doesnt exist. Creating.',0);}
  635. }
  636. }
  637. }
  638. }
  639. @rename($course_sys_dir.$new_dir.$file,$course_sys_dir.$new_dir.$safe_file);
  640. if($this->debug==1){error_log('New LP - Renaming '.$course_sys_dir.$new_dir.$file.' to '.$course_sys_dir.$new_dir.$safe_file,0);}
  641. }
  642. //set_default_settings($course_sys_dir,$safe_file,$filetype);
  643. }
  644. }
  645. closedir($dir);
  646. chdir($saved_dir);
  647. }
  648. }else{
  649. return '';
  650. }
  651. return $course_sys_dir.$new_dir.$manifest;
  652. }
  653. /**
  654. * Sets the proximity setting in the database
  655. * @param string Proximity setting
  656. */
  657. function set_proximity($proxy=''){
  658. if($this->debug>0){error_log('In scorm::set_proximity('.$proxy.') method',0);}
  659. $lp = $this->get_id();
  660. if($lp!=0){
  661. $tbl_lp = Database::get_course_table('lp');
  662. $sql = "UPDATE $tbl_lp SET content_local = '$proxy' WHERE id = ".$lp;
  663. $res = api_sql_query($sql);
  664. return $res;
  665. }else{
  666. return false;
  667. }
  668. }
  669. /**
  670. * Sets the content maker setting in the database
  671. * @param string Proximity setting
  672. */
  673. function set_maker($maker=''){
  674. if($this->debug>0){error_log('In scorm::set_maker method('.$maker.')',0);}
  675. $lp = $this->get_id();
  676. if($lp!=0){
  677. $tbl_lp = Database::get_course_table('lp');
  678. $sql = "UPDATE $tbl_lp SET content_maker = '$maker' WHERE id = ".$lp;
  679. $res = api_sql_query($sql);
  680. return $res;
  681. }else{
  682. return false;
  683. }
  684. }
  685. /**
  686. * Exports the current SCORM object's files as a zip. Excerpts taken from learnpath_functions.inc.php::exportpath()
  687. * @param integer Learnpath ID (optional, taken from object context if not defined)
  688. */
  689. function export_zip($lp_id=null){
  690. if($this->debug>0){error_log('In scorm::export_zip method('.$lp_id.')',0);}
  691. if(empty($lp_id)){
  692. if(!is_object($this))
  693. {
  694. return false;
  695. }
  696. else{
  697. $id = $this->get_id();
  698. if(empty($id)){
  699. return false;
  700. }
  701. else{
  702. $lp_id = $this->get_id();
  703. }
  704. }
  705. }
  706. //error_log('New LP - in export_zip()',0);
  707. //zip everything that is in the corresponding scorm dir
  708. //write the zip file somewhere (might be too big to return)
  709. require_once (api_get_path(LIBRARY_PATH)."fileUpload.lib.php");
  710. require_once (api_get_path(LIBRARY_PATH)."fileManage.lib.php");
  711. require_once (api_get_path(LIBRARY_PATH)."document.lib.php");
  712. require_once (api_get_path(LIBRARY_PATH)."pclzip/pclzip.lib.php");
  713. require_once ("learnpath_functions.inc.php");
  714. $tbl_lp = Database::get_course_table('lp');
  715. $_course = Database::get_course_info(api_get_course_id());
  716. $sql = "SELECT * FROM $tbl_lp WHERE id=".$lp_id;
  717. $result = api_sql_query($sql, __FILE__, __LINE__);
  718. $row = mysql_fetch_array($result);
  719. $LPname = $row['path'];
  720. $list = split('/',$LPname);
  721. $LPnamesafe = $list[0];
  722. //$zipfoldername = '/tmp';
  723. //$zipfoldername = '../../courses/'.$_course['directory']."/temp/".$LPnamesafe;
  724. $zipfoldername = api_get_path('SYS_COURSE_PATH').$_course['directory']."/temp/".$LPnamesafe;
  725. $scormfoldername = api_get_path('SYS_COURSE_PATH').$_course['directory']."/scorm/".$LPnamesafe;
  726. $zipfilename = $zipfoldername."/".$LPnamesafe.".zip";
  727. //Get a temporary dir for creating the zip file
  728. //error_log('New LP - cleaning dir '.$zipfoldername,0);
  729. deldir($zipfoldername); //make sure the temp dir is cleared
  730. $res = mkdir($zipfoldername);
  731. //error_log('New LP - made dir '.$zipfoldername,0);
  732. //create zipfile of given directory
  733. $zip_folder = new PclZip($zipfilename);
  734. $zip_folder->create($scormfoldername.'/', PCLZIP_OPT_REMOVE_PATH, $scormfoldername.'/');
  735. //$zipfilename = '/var/www/dokeos-comp/courses/TEST2/scorm/example_document.html';
  736. //this file sending implies removing the default mime-type from php.ini
  737. //DocumentManager :: file_send_for_download($zipfilename, true, $LPnamesafe.".zip");
  738. DocumentManager :: file_send_for_download($zipfilename, true);
  739. // Delete the temporary zip file and directory in fileManage.lib.php
  740. my_delete($zipfilename);
  741. my_delete($zipfoldername);
  742. return true;
  743. }
  744. /**
  745. * Gets a resource's path if available, otherwise return empty string
  746. * @param string Resource ID as used in resource array
  747. * @return string The resource's path as declared in imsmanifest.xml
  748. */
  749. function get_res_path($id){
  750. if($this->debug>0){error_log('In scorm::get_res_path('.$id.') method',0);}
  751. $path = '';
  752. if(isset($this->resources[$id])){
  753. $oRes =& $this->resources[$id];
  754. $path = @$oRes->get_path();
  755. }
  756. return $path;
  757. }
  758. /**
  759. * Gets a resource's type if available, otherwise return empty string
  760. * @param string Resource ID as used in resource array
  761. * @return string The resource's type as declared in imsmanifest.xml
  762. */
  763. function get_res_type($id){
  764. if($this->debug>0){error_log('In scorm::get_res_type('.$id.') method',0);}
  765. $type = '';
  766. if(isset($this->resources[$id])){
  767. $oRes =& $this->resources[$id];
  768. $temptype = $oRes->get_scorm_type();
  769. if(!empty($temptype)){
  770. $type = $temptype;
  771. }
  772. }
  773. return $type;
  774. }
  775. /**
  776. * Gets the default organisation's title
  777. * @return string The organization's title
  778. */
  779. function get_title(){
  780. if($this->debug>0){error_log('In scorm::get_title() method',0);}
  781. $title = '';
  782. if(isset($this->manifest['organizations']['default'])){
  783. $title = $this->organizations[$this->manifest['organizations']['default']]->get_name();
  784. }elseif(count($this->organizations)==1){
  785. //this will only get one title but so we don't need to know the index
  786. foreach($this->organizations as $id => $value){
  787. $title = $this->organizations[$id]->get_name();
  788. break;
  789. }
  790. }
  791. return $title;
  792. }
  793. /**
  794. * //TODO @TODO implement this function to restore items data from an imsmanifest,
  795. * updating the existing table... This will prove very useful in case initial data
  796. * from imsmanifest were not imported well enough
  797. * @param string course Code
  798. * @param string LP ID (in database)
  799. * @param string Manifest file path (optional if lp_id defined)
  800. * @return integer New LP ID or false on failure
  801. * TODO @TODO Implement imsmanifest_path parameter
  802. */
  803. function reimport_manifest($course,$lp_id=null,$imsmanifest_path=''){
  804. if($this->debug>0){error_log('In scorm::reimport_manifest() method',0);}
  805. global $_course;
  806. //RECOVERING PATH FROM DB
  807. $main_table = Database::get_main_table(MAIN_COURSE_TABLE);
  808. //$course = Database::escape_string($course);
  809. $course = $this->escape_string($course);
  810. $sql = "SELECT * FROM $main_table WHERE code = '$course'";
  811. if($this->debug>2){error_log('New LP - scorm::reimport_manifest() '.__LINE__.' - Querying course: '.$sql,0);}
  812. //$res = Database::query($sql);
  813. $res = api_sql_query($sql);
  814. if(Database::num_rows($res)>0)
  815. {
  816. $this->cc = $course;
  817. }
  818. else
  819. {
  820. $this->error = 'Course code does not exist in database ('.$sql.')';
  821. return false;
  822. }
  823. //TODO make it flexible to use any course_code (still using env course code here)
  824. //$lp_table = Database::get_course_table(LEARNPATH_TABLE);
  825. $lp_db = Database::get_current_course_database();
  826. $lp_pref = Database::get_course_table_prefix();
  827. $lp_table = $lp_db.'.'.$lp_pref.'lp';
  828. //$id = Database::escape_integer($id);
  829. $lp_id = $this->escape_string($lp_id);
  830. $sql = "SELECT * FROM $lp_table WHERE id = '$lp_id'";
  831. if($this->debug>2){error_log('New LP - scorm::reimport_manifest() '.__LINE__.' - Querying lp: '.$sql,0);}
  832. //$res = Database::query($sql);
  833. $res = api_sql_query($sql);
  834. if(Database::num_rows($res)>0)
  835. {
  836. $this->lp_id = $lp_id;
  837. $row = Database::fetch_array($res);
  838. $this->type = $row['lp_type'];
  839. $this->name = stripslashes($row['name']);
  840. $this->encoding = $row['default_encoding'];
  841. $this->proximity = $row['content_local'];
  842. $this->maker = $row['content_maker'];
  843. $this->prevent_reinit = $row['prevent_reinit'];
  844. $this->license = $row['content_license'];
  845. $this->scorm_debug = $row['debug'];
  846. $this->js_lib = $row['js_lib'];
  847. $this->path = $row['path'];
  848. if($this->type == 2){
  849. if($row['force_commit'] == 1){
  850. $this->force_commit = true;
  851. }
  852. }
  853. $this->mode = $row['default_view_mod'];
  854. $this->subdir = $row['path'];
  855. }
  856. //parse the manifest (it is already in this lp's details)
  857. $manifest_file = api_get_path('SYS_COURSE_PATH').$_course['directory'].'/scorm/'.$this->subdir.'/imsmanifest.xml';
  858. if($this->subdir == ''){
  859. $manifest_file = api_get_path('SYS_COURSE_PATH').$_course['directory'].'/scorm/imsmanifest.xml';
  860. }
  861. echo $manifest_file;
  862. if(is_file($manifest_file) && is_readable($manifest_file)){
  863. //re-parse the manifest file
  864. if($this->debug>1){error_log('New LP - In scorm::reimport_manifest() - Parsing manifest '.$manifest_file,0);}
  865. $manifest = $this->parse_manifest($manifest_file);
  866. //import new LP in DB (ignore the current one)
  867. if($this->debug>1){error_log('New LP - In scorm::reimport_manifest() - Importing manifest '.$manifest_file,0);}
  868. $this->import_manifest(api_get_course_id());
  869. }else{
  870. if($this->debug>0){error_log('New LP - In scorm::reimport_manifest() - Could not find manifest file at '.$manifest_file,0);}
  871. }
  872. return false;
  873. }
  874. }
  875. ?>