scorm.class.php 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  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->set_error_msg("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->set_error_msg("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->set_error_msg("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(TABLE_MAIN_COURSE)." 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'];
  363. $new_lp = $dbname.'.lp';
  364. $new_lp_item = $dbname.'.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."', 0, '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,launch_data," .
  435. "parameters) " .
  436. "VALUES " .
  437. "($lp_id, '$type','".$identifier."','".$title."'," .
  438. "'$path',0,100, $value_add" .
  439. "$parent, $previous, 0, " .
  440. "'$prereq', ".$item['rel_order'] .", '".$item['datafromlms']."'," .
  441. "'".$item['parameters']."'" .
  442. ")";
  443. $res_item = api_sql_query($sql_item);
  444. if($this->debug>1){error_log('New LP - In import_manifest(), inserting item : '.$sql_item.' : '.mysql_error(),0);}
  445. $item_id = Database::get_last_insert_id();
  446. //now update previous item to change next_item_id
  447. $upd = "UPDATE $new_lp_item SET next_item_id = $item_id WHERE id = $previous";
  448. $upd_res = api_sql_query($upd);
  449. //update previous item id
  450. $previous = $item_id;
  451. }
  452. }
  453. }
  454. /**
  455. * Intermediate to import_package only to allow import from local zip files
  456. * @param string Path to the zip file, from the dokeos sys root
  457. * @param string Current path (optional)
  458. * @return string Absolute path to the imsmanifest.xml file or empty string on error
  459. */
  460. function import_local_package($file_path,$current_dir='')
  461. {
  462. //todo prepare info as given by the $_FILES[''] vector
  463. $file_info = array();
  464. $file_info['tmp_name'] = $file_path;
  465. $file_info['name'] = basename($file_path);
  466. //call the normal import_package function
  467. return $this->import_package($file_info,$current_dir);
  468. }
  469. /**
  470. * Imports a zip file into the Dokeos structure
  471. * @param string Zip file info as given by $_FILES['userFile']
  472. * @return string Absolute path to the imsmanifest.xml file or empty string on error
  473. */
  474. function import_package($zip_file_info,$current_dir = '')
  475. {
  476. if($this->debug>0){error_log('In scorm::import_package('.print_r($zip_file_info,true).',"'.$current_dir.'") method',0);}
  477. $maxFilledSpace = 1000000000;
  478. $zip_file_path = $zip_file_info['tmp_name'];
  479. $zip_file_name = $zip_file_info['name'];
  480. if($this->debug>1){error_log('New LP - import_package() - zip file path = '.$zip_file_path.', zip file name = '.$zip_file_name,0);}
  481. $course_rel_dir = api_get_course_path().'/scorm'; //scorm dir web path starting from /courses
  482. $course_sys_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir; //absolute system path for this course
  483. $current_dir = replace_dangerous_char(trim($current_dir),'strict'); //current dir we are in, inside scorm/
  484. if($this->debug>1){error_log('New LP - import_package() - current_dir = '.$current_dir,0);}
  485. //$uploaded_filename = $_FILES['userFile']['name'];
  486. //get name of the zip file without the extension
  487. if($this->debug>1){error_log('New LP - Received zip file name: '.$zip_file_path,0);}
  488. $file_info = pathinfo($zip_file_name);
  489. $filename = $file_info['basename'];
  490. $extension = $file_info['extension'];
  491. $file_base_name = str_replace('.'.$extension,'',$filename); //filename without its extension
  492. $this->zipname = $file_base_name; //save for later in case we don't have a title
  493. if($this->debug>1){error_log("New LP - base file name is : ".$file_base_name,0);}
  494. $new_dir = replace_dangerous_char(trim($file_base_name),'strict');
  495. $this->subdir = $new_dir;
  496. if($this->debug>1){error_log("New LP - subdir is first set to : ".$this->subdir,0);}
  497. $zipFile = new pclZip($zip_file_path);
  498. // Check the zip content (real size and file extension)
  499. $zipContentArray = $zipFile->listContent();
  500. $package_type='';
  501. $at_root = false;
  502. $manifest = '';
  503. $manifest_list = array();
  504. //the following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?)
  505. foreach($zipContentArray as $thisContent)
  506. {
  507. //error_log('Looking at '.$thisContent['filename'],0);
  508. if ( preg_match('~.(php.*|phtml)$~i', $thisContent['filename']) )
  509. {
  510. $this->set_error_msg("File $file contains a PHP script");
  511. //return api_failure::set_failure('php_file_in_zip_file');
  512. }
  513. elseif(stristr($thisContent['filename'],'imsmanifest.xml'))
  514. {
  515. //error_log('Found imsmanifest at '.$thisContent['filename'],0);
  516. if($thisContent['filename'] == basename($thisContent['filename'])){
  517. $at_root = true;
  518. }else{
  519. //$this->subdir .= '/'.dirname($thisContent['filename']);
  520. if($this->debug>2){error_log("New LP - subdir is now ".$this->subdir,0);}
  521. }
  522. $package_type = 'scorm';
  523. $manifest_list[] = $thisContent['filename'];
  524. $manifest = $thisContent['filename']; //just the relative directory inside scorm/
  525. }
  526. else
  527. {
  528. //do nothing, if it has not been set as scorm somewhere else, it stays as '' default
  529. }
  530. $realFileSize += $thisContent['size'];
  531. }
  532. //now get the shortest path (basically, the imsmanifest that is the closest to the root)
  533. $shortest_path = $manifest_list[0];
  534. $slash_count = substr_count($shortest_path,'/');
  535. foreach($manifest_list as $manifest_path){
  536. $tmp_slash_count = substr_count($manifest_path,'/');
  537. if($tmp_slash_count<$slash_count){
  538. $shortest_path = $manifest_path;
  539. $slash_count = $tmp_slash_count;
  540. }
  541. }
  542. $this->subdir .= '/'.dirname($shortest_path); //do not concatenate because already done above
  543. $manifest = $shortest_path;
  544. if($this->debug>1){error_log('New LP - Package type is now '.$package_type,0);}
  545. if($package_type== '')
  546. // && defined('CHECK_FOR_SCORM') && CHECK_FOR_SCORM)
  547. {
  548. return api_failure::set_failure('not_scorm_content');
  549. }
  550. if (! enough_size($realFileSize, $course_sys_dir, $maxFilledSpace) )
  551. {
  552. return api_failure::set_failure('not_enough_space');
  553. }
  554. // it happens on Linux that $new_dir sometimes doesn't start with '/'
  555. if($new_dir[0] != '/')
  556. {
  557. $new_dir='/'.$new_dir;
  558. }
  559. if($new_dir[strlen($new_dir)-1] == '/')
  560. {
  561. $new_dir=substr($new_dir,0,-1);
  562. }
  563. /*
  564. --------------------------------------
  565. Uncompressing phase
  566. --------------------------------------
  567. */
  568. /*
  569. The first version, using OS unzip, is not used anymore
  570. because it does not return enough information.
  571. We need to process each individual file in the zip archive to
  572. - add it to the database
  573. - parse & change relative html links
  574. */
  575. if (PHP_OS == 'Linux' && ! get_cfg_var('safe_mode') && false) // *** UGent, changed by OC ***
  576. {
  577. // Shell Method - if this is possible, it gains some speed
  578. //check availability of 'unzip' first!
  579. exec("unzip -d \"".$course_sys_dir."".$new_dir." ".$zip_file_path);
  580. if($this->debug>=1){error_log('New LP - found Linux system, using unzip',0);}
  581. }
  582. elseif(is_dir($course_sys_dir.$new_dir) OR @mkdir($course_sys_dir.$new_dir))
  583. {
  584. // PHP method - slower...
  585. if($this->debug>=1){error_log('New LP - Changing dir to '.$course_sys_dir.$new_dir,0);}
  586. $saved_dir = getcwd();
  587. chdir($course_sys_dir.$new_dir);
  588. $unzippingState = $zipFile->extract();
  589. for($j=0;$j<count($unzippingState);$j++)
  590. {
  591. $state=$unzippingState[$j];
  592. //TODO fix relative links in html files (?)
  593. $extension = strrchr($state["stored_filename"], ".");
  594. if($this->debug>=1){error_log('New LP - found extension '.$extension.' in '.$state['stored_filename'],0);}
  595. }
  596. if(!empty($new_dir))
  597. {
  598. $new_dir = $new_dir.'/';
  599. }
  600. //rename files, for example with \\ in it
  601. if($dir=@opendir($course_sys_dir.$new_dir))
  602. {
  603. if($this->debug==1){error_log('New LP - Opened dir '.$course_sys_dir.$new_dir,0);}
  604. while($file=readdir($dir))
  605. {
  606. if($file != '.' && $file != '..')
  607. {
  608. $filetype="file";
  609. if(is_dir($course_sys_dir.$new_dir.$file)) $filetype="folder";
  610. //TODO RENAMING FILES CAN BE VERY DANGEROUS SCORM-WISE, avoid that as much as possible!
  611. //$safe_file=replace_dangerous_char($file,'strict');
  612. $safe_file = str_replace('\\','/',$file);
  613. if($safe_file != $file){
  614. //@rename($course_sys_dir.$new_dir,$course_sys_dir.'/'.$safe_file);
  615. $mydir = dirname($course_sys_dir.$new_dir.$safe_file);
  616. if(!is_dir($mydir)){
  617. $mysubdirs = split('/',$mydir);
  618. $mybasedir = '/';
  619. foreach($mysubdirs as $mysubdir){
  620. if(!empty($mysubdir)){
  621. $mybasedir = $mybasedir.$mysubdir.'/';
  622. if(!is_dir($mybasedir)){
  623. @mkdir($mybasedir);
  624. if($this->debug==1){error_log('New LP - Dir '.$mybasedir.' doesnt exist. Creating.',0);}
  625. }
  626. }
  627. }
  628. }
  629. @rename($course_sys_dir.$new_dir.$file,$course_sys_dir.$new_dir.$safe_file);
  630. if($this->debug==1){error_log('New LP - Renaming '.$course_sys_dir.$new_dir.$file.' to '.$course_sys_dir.$new_dir.$safe_file,0);}
  631. }
  632. //set_default_settings($course_sys_dir,$safe_file,$filetype);
  633. }
  634. }
  635. closedir($dir);
  636. chdir($saved_dir);
  637. }
  638. }else{
  639. return '';
  640. }
  641. return $course_sys_dir.$new_dir.$manifest;
  642. }
  643. /**
  644. * Sets the proximity setting in the database
  645. * @param string Proximity setting
  646. */
  647. function set_proximity($proxy=''){
  648. if($this->debug>0){error_log('In scorm::set_proximity('.$proxy.') method',0);}
  649. $lp = $this->get_id();
  650. if($lp!=0){
  651. $tbl_lp = Database::get_course_table('lp');
  652. $sql = "UPDATE $tbl_lp SET content_local = '$proxy' WHERE id = ".$lp;
  653. $res = api_sql_query($sql);
  654. return $res;
  655. }else{
  656. return false;
  657. }
  658. }
  659. /**
  660. * Sets the content maker setting in the database
  661. * @param string Proximity setting
  662. */
  663. function set_maker($maker=''){
  664. if($this->debug>0){error_log('In scorm::set_maker method('.$maker.')',0);}
  665. $lp = $this->get_id();
  666. if($lp!=0){
  667. $tbl_lp = Database::get_course_table('lp');
  668. $sql = "UPDATE $tbl_lp SET content_maker = '$maker' WHERE id = ".$lp;
  669. $res = api_sql_query($sql);
  670. return $res;
  671. }else{
  672. return false;
  673. }
  674. }
  675. /**
  676. * Exports the current SCORM object's files as a zip. Excerpts taken from learnpath_functions.inc.php::exportpath()
  677. * @param integer Learnpath ID (optional, taken from object context if not defined)
  678. */
  679. function export_zip($lp_id=null){
  680. if($this->debug>0){error_log('In scorm::export_zip method('.$lp_id.')',0);}
  681. if(empty($lp_id)){
  682. if(!is_object($this))
  683. {
  684. return false;
  685. }
  686. else{
  687. $id = $this->get_id();
  688. if(empty($id)){
  689. return false;
  690. }
  691. else{
  692. $lp_id = $this->get_id();
  693. }
  694. }
  695. }
  696. //error_log('New LP - in export_zip()',0);
  697. //zip everything that is in the corresponding scorm dir
  698. //write the zip file somewhere (might be too big to return)
  699. require_once (api_get_path(LIBRARY_PATH)."fileUpload.lib.php");
  700. require_once (api_get_path(LIBRARY_PATH)."fileManage.lib.php");
  701. require_once (api_get_path(LIBRARY_PATH)."document.lib.php");
  702. require_once (api_get_path(LIBRARY_PATH)."pclzip/pclzip.lib.php");
  703. require_once ("learnpath_functions.inc.php");
  704. $tbl_lp = Database::get_course_table('lp');
  705. $_course = Database::get_course_info(api_get_course_id());
  706. $sql = "SELECT * FROM $tbl_lp WHERE id=".$lp_id;
  707. $result = api_sql_query($sql, __FILE__, __LINE__);
  708. $row = mysql_fetch_array($result);
  709. $LPname = $row['path'];
  710. $list = split('/',$LPname);
  711. $LPnamesafe = $list[0];
  712. //$zipfoldername = '/tmp';
  713. //$zipfoldername = '../../courses/'.$_course['directory']."/temp/".$LPnamesafe;
  714. $zipfoldername = api_get_path('SYS_COURSE_PATH').$_course['directory']."/temp/".$LPnamesafe;
  715. $scormfoldername = api_get_path('SYS_COURSE_PATH').$_course['directory']."/scorm/".$LPnamesafe;
  716. $zipfilename = $zipfoldername."/".$LPnamesafe.".zip";
  717. //Get a temporary dir for creating the zip file
  718. //error_log('New LP - cleaning dir '.$zipfoldername,0);
  719. deldir($zipfoldername); //make sure the temp dir is cleared
  720. $res = mkdir($zipfoldername);
  721. //error_log('New LP - made dir '.$zipfoldername,0);
  722. //create zipfile of given directory
  723. $zip_folder = new PclZip($zipfilename);
  724. $zip_folder->create($scormfoldername.'/', PCLZIP_OPT_REMOVE_PATH, $scormfoldername.'/');
  725. //$zipfilename = '/var/www/dokeos-comp/courses/TEST2/scorm/example_document.html';
  726. //this file sending implies removing the default mime-type from php.ini
  727. //DocumentManager :: file_send_for_download($zipfilename, true, $LPnamesafe.".zip");
  728. DocumentManager :: file_send_for_download($zipfilename, true);
  729. // Delete the temporary zip file and directory in fileManage.lib.php
  730. my_delete($zipfilename);
  731. my_delete($zipfoldername);
  732. return true;
  733. }
  734. /**
  735. * Gets a resource's path if available, otherwise return empty string
  736. * @param string Resource ID as used in resource array
  737. * @return string The resource's path as declared in imsmanifest.xml
  738. */
  739. function get_res_path($id){
  740. if($this->debug>0){error_log('In scorm::get_res_path('.$id.') method',0);}
  741. $path = '';
  742. if(isset($this->resources[$id])){
  743. $oRes =& $this->resources[$id];
  744. $path = @$oRes->get_path();
  745. }
  746. return $path;
  747. }
  748. /**
  749. * Gets a resource's type if available, otherwise return empty string
  750. * @param string Resource ID as used in resource array
  751. * @return string The resource's type as declared in imsmanifest.xml
  752. */
  753. function get_res_type($id){
  754. if($this->debug>0){error_log('In scorm::get_res_type('.$id.') method',0);}
  755. $type = '';
  756. if(isset($this->resources[$id])){
  757. $oRes =& $this->resources[$id];
  758. $temptype = $oRes->get_scorm_type();
  759. if(!empty($temptype)){
  760. $type = $temptype;
  761. }
  762. }
  763. return $type;
  764. }
  765. /**
  766. * Gets the default organisation's title
  767. * @return string The organization's title
  768. */
  769. function get_title(){
  770. if($this->debug>0){error_log('In scorm::get_title() method',0);}
  771. $title = '';
  772. if(isset($this->manifest['organizations']['default'])){
  773. $title = $this->organizations[$this->manifest['organizations']['default']]->get_name();
  774. }elseif(count($this->organizations)==1){
  775. //this will only get one title but so we don't need to know the index
  776. foreach($this->organizations as $id => $value){
  777. $title = $this->organizations[$id]->get_name();
  778. break;
  779. }
  780. }
  781. return $title;
  782. }
  783. /**
  784. * //TODO @TODO implement this function to restore items data from an imsmanifest,
  785. * updating the existing table... This will prove very useful in case initial data
  786. * from imsmanifest were not imported well enough
  787. * @param string course Code
  788. * @param string LP ID (in database)
  789. * @param string Manifest file path (optional if lp_id defined)
  790. * @return integer New LP ID or false on failure
  791. * TODO @TODO Implement imsmanifest_path parameter
  792. */
  793. function reimport_manifest($course,$lp_id=null,$imsmanifest_path=''){
  794. if($this->debug>0){error_log('In scorm::reimport_manifest() method',0);}
  795. global $_course;
  796. //RECOVERING PATH FROM DB
  797. $main_table = Database::get_main_table(TABLE_MAIN_COURSE);
  798. //$course = Database::escape_string($course);
  799. $course = $this->escape_string($course);
  800. $sql = "SELECT * FROM $main_table WHERE code = '$course'";
  801. if($this->debug>2){error_log('New LP - scorm::reimport_manifest() '.__LINE__.' - Querying course: '.$sql,0);}
  802. //$res = Database::query($sql);
  803. $res = api_sql_query($sql);
  804. if(Database::num_rows($res)>0)
  805. {
  806. $this->cc = $course;
  807. }
  808. else
  809. {
  810. $this->error = 'Course code does not exist in database ('.$sql.')';
  811. return false;
  812. }
  813. //TODO make it flexible to use any course_code (still using env course code here)
  814. //$lp_table = Database::get_course_table(LEARNPATH_TABLE);
  815. $lp_db = Database::get_current_course_database();
  816. $lp_pref = Database::get_course_table_prefix();
  817. $lp_table = $lp_db.'.'.$lp_pref.'lp';
  818. //$id = Database::escape_integer($id);
  819. $lp_id = $this->escape_string($lp_id);
  820. $sql = "SELECT * FROM $lp_table WHERE id = '$lp_id'";
  821. if($this->debug>2){error_log('New LP - scorm::reimport_manifest() '.__LINE__.' - Querying lp: '.$sql,0);}
  822. //$res = Database::query($sql);
  823. $res = api_sql_query($sql);
  824. if(Database::num_rows($res)>0)
  825. {
  826. $this->lp_id = $lp_id;
  827. $row = Database::fetch_array($res);
  828. $this->type = $row['lp_type'];
  829. $this->name = stripslashes($row['name']);
  830. $this->encoding = $row['default_encoding'];
  831. $this->proximity = $row['content_local'];
  832. $this->maker = $row['content_maker'];
  833. $this->prevent_reinit = $row['prevent_reinit'];
  834. $this->license = $row['content_license'];
  835. $this->scorm_debug = $row['debug'];
  836. $this->js_lib = $row['js_lib'];
  837. $this->path = $row['path'];
  838. if($this->type == 2){
  839. if($row['force_commit'] == 1){
  840. $this->force_commit = true;
  841. }
  842. }
  843. $this->mode = $row['default_view_mod'];
  844. $this->subdir = $row['path'];
  845. }
  846. //parse the manifest (it is already in this lp's details)
  847. $manifest_file = api_get_path('SYS_COURSE_PATH').$_course['directory'].'/scorm/'.$this->subdir.'/imsmanifest.xml';
  848. if($this->subdir == ''){
  849. $manifest_file = api_get_path('SYS_COURSE_PATH').$_course['directory'].'/scorm/imsmanifest.xml';
  850. }
  851. echo $manifest_file;
  852. if(is_file($manifest_file) && is_readable($manifest_file)){
  853. //re-parse the manifest file
  854. if($this->debug>1){error_log('New LP - In scorm::reimport_manifest() - Parsing manifest '.$manifest_file,0);}
  855. $manifest = $this->parse_manifest($manifest_file);
  856. //import new LP in DB (ignore the current one)
  857. if($this->debug>1){error_log('New LP - In scorm::reimport_manifest() - Importing manifest '.$manifest_file,0);}
  858. $this->import_manifest(api_get_course_id());
  859. }else{
  860. if($this->debug>0){error_log('New LP - In scorm::reimport_manifest() - Could not find manifest file at '.$manifest_file,0);}
  861. }
  862. return false;
  863. }
  864. }
  865. ?>