aicc.class.php 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. /**
  4. * Class aicc
  5. * Defines the AICC class, which is meant to contain the aicc items (nuclear elements).
  6. *
  7. * @package chamilo.learnpath
  8. *
  9. * @author Yannick Warnier <ywarnier@beeznest.org>
  10. * @license GNU/GPL
  11. *
  12. * @package chamilo.learnpath
  13. */
  14. class aicc extends learnpath
  15. {
  16. public $config = [];
  17. // The configuration files might be multiple and might have
  18. // funny names. We need to keep the name of that file while we
  19. // install the content.
  20. public $config_basename = '';
  21. public $config_files = [];
  22. public $config_exts = [
  23. 'crs' => 0, // Course description file (mandatory)
  24. 'au' => 0, // Assignable Unit file (mandatory)
  25. 'des' => 0, // Descriptor file (mandatory)
  26. 'cst' => 0, // Course structure file (mandatory)
  27. 'ore' => 0, // Objectives relationshops file (optional)
  28. 'pre' => 0, // Prerequisites file (optional)
  29. 'cmp' => 0, // Completion Requirements file (optional)
  30. ];
  31. public $aulist = [];
  32. public $au_order_list = [];
  33. public $au_order_list_new_id = [];
  34. public $deslist = [];
  35. public $cstlist = [];
  36. public $orelist = [];
  37. // Path between the scorm/ directory and the config files
  38. // e.g. maritime_nav/maritime_nav.
  39. // This is the path that will be used in the lp_path when importing a package.
  40. public $subdir = '';
  41. // Keeps the zipfile safe for the object's life
  42. // so that we can use it if there is no title available.
  43. public $zipname = '';
  44. // Keeps an index of the number of uses of the zipname so far.
  45. public $lastzipnameindex = 0;
  46. public $config_encoding = 'ISO-8859-1';
  47. public $debug = 0;
  48. /**
  49. * Class constructor. Based on the parent constructor.
  50. *
  51. * @param string $course_code
  52. * @param int $resource_id Learnpath ID in DB
  53. * @param int $user_id
  54. */
  55. public function __construct($course_code = null, $resource_id = null, $user_id = null)
  56. {
  57. if ($this->debug > 0) {
  58. error_log('In aicc::aicc()');
  59. }
  60. if (!empty($course_code) && !empty($resource_id) && !empty($user_id)) {
  61. parent::__construct($course_code, $resource_id, $user_id);
  62. }
  63. }
  64. /**
  65. * Opens a resource.
  66. *
  67. * @param int Database ID of the resource
  68. */
  69. public function open($id)
  70. {
  71. }
  72. /**
  73. * Parses a set of AICC config files and puts everything into the $config array.
  74. *
  75. * @param string Path to the config files dir on the system.
  76. * If not defined, uses the base path of the course's scorm dir
  77. *
  78. * @return array Structured array representing the config files' contents
  79. */
  80. public function parse_config_files($dir = '')
  81. {
  82. if ($this->debug > 0) {
  83. error_log('New LP - In aicc::parse_config_files('.$dir.')', 0);
  84. }
  85. if (empty($dir)) {
  86. // Get the path of the AICC config files dir.
  87. $dir = $this->subdir;
  88. }
  89. if (is_dir($dir) && is_readable($dir)) {
  90. // Now go through all the config files one by one and parse everything into AICC objects.
  91. // The basename for the config files is stored in $this->config_basename.
  92. // Parse the Course Description File (.crs) - ini-type.
  93. $crs_file = $dir.'/'.$this->config_files['crs'];
  94. $crs_params = $this->parse_ini_file_quotes_safe($crs_file);
  95. if ($this->debug > 1) {
  96. error_log('New LP - In aicc::parse_config_files() - '.$crs_file.' has been parsed');
  97. }
  98. // CRS distribute crs params into the aicc object.
  99. if (!empty($crs_params['course']['course_creator'])) {
  100. $this->course_creator = Database::escape_string($crs_params['course']['course_creator']);
  101. }
  102. if (!empty($crs_params['course']['course_id'])) {
  103. $this->course_id = Database::escape_string($crs_params['course']['course_id']);
  104. }
  105. if (!empty($crs_params['course']['course_system'])) {
  106. $this->course_system = $crs_params['course']['course_system'];
  107. }
  108. if (!empty($crs_params['course']['course_title'])) {
  109. $this->course_title = Database::escape_string($crs_params['course']['course_title']);
  110. }
  111. if (!empty($crs_params['course']['course_level'])) {
  112. $this->course_level = $crs_params['course']['course_level'];
  113. }
  114. if (!empty($crs_params['course']['max_fields_cst'])) {
  115. $this->course_max_fields_cst = $crs_params['course']['max_fields_cst'];
  116. }
  117. if (!empty($crs_params['course']['max_fields_ort'])) {
  118. $this->course_max_fields_ort = $crs_params['course']['max_fields_ort'];
  119. }
  120. if (!empty($crs_params['course']['total_aus'])) {
  121. $this->course_total_aus = $crs_params['course']['total_aus'];
  122. }
  123. if (!empty($crs_params['course']['total_blocks'])) {
  124. $this->course_total_blocks = $crs_params['course']['total_blocks'];
  125. }
  126. if (!empty($crs_params['course']['total_objectives'])) {
  127. $this->course_total_objectives = $crs_params['course']['total_objectives'];
  128. }
  129. if (!empty($crs_params['course']['total_complex_objectives'])) {
  130. $this->course_total_complex_objectives = $crs_params['course']['total_complex_objectives'];
  131. }
  132. if (!empty($crs_params['course']['version'])) {
  133. $this->course_version = $crs_params['course']['version'];
  134. }
  135. if (!empty($crs_params['course_description'])) {
  136. $this->course_description = Database::escape_string($crs_params['course_description']);
  137. }
  138. // Parse the Descriptor File (.des) - csv-type.
  139. $des_file = $dir.'/'.$this->config_files['des'];
  140. $des_params = $this->parse_csv_file($des_file);
  141. if ($this->debug > 1) {
  142. error_log('New LP - In aicc::parse_config_files() - '.$des_file.' has been parsed', 0);
  143. }
  144. // Distribute des params into the aicc object.
  145. foreach ($des_params as $des) {
  146. // One AU in AICC is equivalent to one SCO in SCORM (scormItem class).
  147. $oDes = new aiccResource('config', $des);
  148. $this->deslist[$oDes->identifier] = $oDes;
  149. }
  150. // Parse the Assignable Unit File (.au) - csv-type.
  151. $au_file = $dir.'/'.$this->config_files['au'];
  152. $au_params = $this->parse_csv_file($au_file);
  153. if ($this->debug > 1) {
  154. error_log('New LP - In aicc::parse_config_files() - '.$au_file.' has been parsed', 0);
  155. }
  156. // Distribute au params into the aicc object.
  157. foreach ($au_params as $au) {
  158. $oAu = new aiccItem('config', $au);
  159. $this->aulist[$oAu->identifier] = $oAu;
  160. $this->au_order_list[] = $oAu->identifier;
  161. }
  162. // Parse the Course Structure File (.cst) - csv-type.
  163. $cst_file = $dir.'/'.$this->config_files['cst'];
  164. $cst_params = $this->parse_csv_file($cst_file, ',', '"', true);
  165. if ($this->debug > 1) {
  166. error_log('New LP - In aicc::parse_config_files() - '.$cst_file.' has been parsed', 0);
  167. }
  168. // Distribute cst params into the aicc object.
  169. foreach ($cst_params as $cst) {
  170. $oCst = new aiccBlock('config', $cst);
  171. $this->cstlist[$oCst->identifier] = $oCst;
  172. }
  173. // Parse the Objectives Relationships File (.ore) - csv-type - if exists.
  174. // TODO: Implement these objectives. For now they're just parsed.
  175. if (!empty($this->config_files['ore'])) {
  176. $ore_file = $dir.'/'.$this->config_files['ore'];
  177. $ore_params = $this->parse_csv_file($ore_file, ',', '"', true);
  178. if ($this->debug > 1) {
  179. error_log('New LP - In aicc::parse_config_files() - '.$ore_file.' has been parsed', 0);
  180. }
  181. // Distribute ore params into the aicc object.
  182. foreach ($ore_params as $ore) {
  183. $oOre = new aiccObjective('config', $ore);
  184. $this->orelist[$oOre->identifier] = $oOre;
  185. }
  186. }
  187. // Parse the Prerequisites File (.pre) - csv-type - if exists.
  188. if (!empty($this->config_files['pre'])) {
  189. $pre_file = $dir.'/'.$this->config_files['pre'];
  190. $pre_params = $this->parse_csv_file($pre_file);
  191. if ($this->debug > 1) {
  192. error_log('New LP - In aicc::parse_config_files() - '.$pre_file.' has been parsed', 0);
  193. }
  194. // Distribute pre params into the aicc object.
  195. foreach ($pre_params as $pre) {
  196. // Place a constraint on the corresponding block or AU.
  197. if (in_array(api_strtolower($pre['structure_element']), array_keys($this->cstlist))) {
  198. // If this references a block element:
  199. $this->cstlist[api_strtolower($pre['structure_element'])]->prereq_string = api_strtolower($pre['prerequisite']);
  200. }
  201. if (in_array(api_strtolower($pre['structure_element']), array_keys($this->aulist))) {
  202. // If this references a block element:
  203. $this->aulist[api_strtolower($pre['structure_element'])]->prereq_string = api_strtolower($pre['prerequisite']);
  204. }
  205. }
  206. }
  207. // Parse the Completion Requirements File (.cmp) - csv-type - if exists.
  208. // TODO: Implement this set of requirements (needs database changes).
  209. if (!empty($this->config_files['cmp'])) {
  210. $cmp_file = $dir.'/'.$this->config_files['cmp'];
  211. $cmp_params = $this->parse_csv_file($cmp_file);
  212. if ($this->debug > 1) {
  213. error_log('New LP - In aicc::parse_config_files() - '.$cmp_file.' has been parsed', 0);
  214. }
  215. // Distribute cmp params into the aicc object.
  216. foreach ($cmp_params as $cmp) {
  217. //$oCmp = new aiccCompletionRequirements('config', $cmp);
  218. //$this->cmplist[$oCmp->identifier] =& $oCmp;
  219. }
  220. }
  221. }
  222. return $this->config;
  223. }
  224. /**
  225. * Import the aicc object (as a result from the parse_config_files function) into the database structure.
  226. *
  227. * @param string $course_code
  228. *
  229. * @return bool Returns -1 on error
  230. */
  231. public function import_aicc($course_code)
  232. {
  233. $courseInfo = api_get_course_info($course_code);
  234. $course_id = $courseInfo['real_id'];
  235. if (empty($course_id)) {
  236. return false;
  237. }
  238. if ($this->debug > 0) {
  239. error_log('New LP - In aicc::import_aicc('.$course_code.')', 0);
  240. }
  241. $new_lp = Database::get_course_table(TABLE_LP_MAIN);
  242. $new_lp_item = Database::get_course_table(TABLE_LP_ITEM);
  243. $get_max = "SELECT MAX(display_order) FROM $new_lp WHERE c_id = $course_id";
  244. $res_max = Database::query($get_max);
  245. if (Database::num_rows($res_max) < 1) {
  246. $dsp = 1;
  247. } else {
  248. $row = Database::fetch_array($res_max);
  249. $dsp = $row[0] + 1;
  250. }
  251. $this->config_encoding = "ISO-8859-1"; // TODO: We may apply detection for this value, see the function api_detect_encoding().
  252. $sql = "INSERT INTO $new_lp (c_id, lp_type, name, ref, description, path, force_commit, default_view_mod, default_encoding, js_lib, content_maker,display_order)".
  253. "VALUES ".
  254. "($course_id, 3, '".$this->course_title."', '".$this->course_id."','".$this->course_description."',".
  255. "'".$this->subdir."', 0, 'embedded', '".$this->config_encoding."',".
  256. "'aicc_api.php','".$this->course_creator."',$dsp)";
  257. if ($this->debug > 2) {
  258. error_log('New LP - In import_aicc(), inserting path: '.$sql, 0);
  259. }
  260. Database::query($sql);
  261. $lp_id = Database::insert_id();
  262. if ($lp_id) {
  263. $sql = "UPDATE $new_lp SET id = iid WHERE iid = $lp_id";
  264. Database::query($sql);
  265. $this->lp_id = $lp_id;
  266. api_item_property_update(
  267. $courseInfo,
  268. TOOL_LEARNPATH,
  269. $this->lp_id,
  270. 'LearnpathAdded',
  271. api_get_user_id()
  272. );
  273. api_item_property_update(
  274. $courseInfo,
  275. TOOL_LEARNPATH,
  276. $this->lp_id,
  277. 'visible',
  278. api_get_user_id()
  279. );
  280. }
  281. $previous = 0;
  282. foreach ($this->aulist as $identifier => $dummy) {
  283. $oAu = &$this->aulist[$identifier];
  284. //echo "Item ".$oAu->identifier;
  285. $field_add = '';
  286. $value_add = '';
  287. if (!empty($oAu->masteryscore)) {
  288. $field_add = 'mastery_score, ';
  289. $value_add = $oAu->masteryscore.',';
  290. }
  291. $title = $oAu->identifier;
  292. if (is_object($this->deslist[$identifier])) {
  293. $title = $this->deslist[$identifier]->title;
  294. }
  295. $path = $oAu->path;
  296. //$max_score = $oAu->max_score // TODO: Check if special constraint exists for this item.
  297. //$min_score = $oAu->min_score // TODO: Check if special constraint exists for this item.
  298. $parent = 0; // TODO: Deal with the parent.
  299. $previous = 0;
  300. $prereq = $oAu->prereq_string;
  301. $sql_item = "INSERT INTO $new_lp_item (c_id, lp_id,item_type,ref,title, path,min_score,max_score, $field_add parent_item_id,previous_item_id,next_item_id, prerequisite,display_order,parameters) ".
  302. "VALUES ".
  303. "($course_id, $lp_id, 'au','".$oAu->identifier."','".$title."',".
  304. "'$path',0,100, $value_add".
  305. "$parent, $previous, 0, ".
  306. "'$prereq', 0,'".(!empty($oAu->parameters) ? Database::escape_string($oAu->parameters) : '')."'".
  307. ")";
  308. Database::query($sql_item);
  309. if ($this->debug > 1) {
  310. error_log('New LP - In aicc::import_aicc() - inserting item : '.$sql_item.' : ', 0);
  311. }
  312. $item_id = Database::insert_id();
  313. if ($item_id) {
  314. $sql = "UPDATE $new_lp_item SET id = iid WHERE iid = $lp_id";
  315. Database::query($sql);
  316. }
  317. // Now update previous item to change next_item_id.
  318. if ($previous != 0) {
  319. $upd = "UPDATE $new_lp_item SET next_item_id = $item_id WHERE c_id = $course_id AND id = $previous";
  320. Database::query($upd);
  321. // Update the previous item id.
  322. }
  323. $previous = $item_id;
  324. }
  325. }
  326. /**
  327. * Intermediate to import_package only to allow import from local zip files.
  328. *
  329. * @param string Path to the zip file, from the dokeos sys root
  330. * @param string Current path (optional)
  331. *
  332. * @return string Absolute path to the AICC description files or empty string on error
  333. */
  334. public function import_local_package($file_path, $current_dir = '')
  335. {
  336. // TODO: Prepare info as given by the $_FILES[''] vector.
  337. $file_info = [];
  338. $file_info['tmp_name'] = $file_path;
  339. $file_info['name'] = basename($file_path);
  340. // Call the normal import_package function.
  341. return $this->import_package($file_info, $current_dir);
  342. }
  343. /**
  344. * Imports a zip file (presumably AICC) into the Chamilo structure.
  345. *
  346. * @param string Zip file info as given by $_FILES['userFile']
  347. *
  348. * @return string Absolute path to the AICC config files directory or empty string on error
  349. */
  350. public function import_package($zip_file_info, $current_dir = '')
  351. {
  352. if ($this->debug > 0) {
  353. error_log('In aicc::import_package('.print_r($zip_file_info, true).',"'.$current_dir.'") method', 0);
  354. }
  355. //ini_set('error_log', 'E_ALL');
  356. $maxFilledSpace = 1000000000;
  357. $zip_file_path = $zip_file_info['tmp_name'];
  358. $zip_file_name = $zip_file_info['name'];
  359. if ($this->debug > 0) {
  360. error_log(
  361. 'New LP - aicc::import_package() - Zip file path = '.$zip_file_path.', zip file name = '.$zip_file_name,
  362. 0
  363. );
  364. }
  365. $course_rel_dir = api_get_course_path().'/scorm'; // Scorm dir web path starting from /courses
  366. $course_sys_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir; // The absolute system path of this course.
  367. $current_dir = api_replace_dangerous_char(trim($current_dir)); // Current dir we are in, inside scorm/
  368. if ($this->debug > 0) {
  369. error_log('New LP - aicc::import_package() - Current_dir = '.$current_dir, 0);
  370. }
  371. //$uploaded_filename = $_FILES['userFile']['name'];
  372. // Get the name of the zip file without the extension.
  373. if ($this->debug > 0) {
  374. error_log('New LP - aicc::import_package() - Received zip file name: '.$zip_file_path, 0);
  375. }
  376. $file_info = pathinfo($zip_file_name);
  377. $filename = $file_info['basename'];
  378. $extension = $file_info['extension'];
  379. $file_base_name = str_replace('.'.$extension, '', $filename); // Filename without its extension.
  380. $this->zipname = $file_base_name; // Save for later in case we don't have a title.
  381. if ($this->debug > 0) {
  382. error_log('New LP - aicc::import_package() - Base file name is : '.$file_base_name, 0);
  383. }
  384. $new_dir = api_replace_dangerous_char(trim($file_base_name));
  385. $this->subdir = $new_dir;
  386. if ($this->debug > 0) {
  387. error_log('New LP - aicc::import_package() - Subdir is first set to : '.$this->subdir, 0);
  388. }
  389. /*
  390. if (check_name_exist($course_sys_dir.$current_dir.'/'.$new_dir)) {
  391. $dialogBox = get_lang('FileExists');
  392. $stopping_error = true;
  393. }
  394. */
  395. $zipFile = new PclZip($zip_file_path);
  396. // Check the zip content (real size and file extension).
  397. $zipContentArray = $zipFile->listContent();
  398. $package_type = ''; // The type of the package. Should be 'aicc' after the next few lines.
  399. $package = ''; // The basename of the config files (if 'courses.crs' => 'courses').
  400. $at_root = false; // Check if the config files are at zip root.
  401. $config_dir = ''; // The directory in which the config files are. May remain empty.
  402. $files_found = [];
  403. $subdir_isset = false;
  404. // The following loop should be stopped as soon as we found the right config files (.crs, .au, .des and .cst).
  405. $realFileSize = 0;
  406. foreach ($zipContentArray as $thisContent) {
  407. if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
  408. // If a php file is found, do not authorize (security risk).
  409. if ($this->debug > 1) {
  410. error_log(
  411. 'New LP - aicc::import_package() - Found unauthorized file: '.$thisContent['filename']
  412. );
  413. }
  414. Display::addFlash(
  415. Display::return_message(get_lang('ZipNoPhp'))
  416. );
  417. return false;
  418. } elseif (preg_match('?.*/aicc/$?', $thisContent['filename'])) {
  419. // If a directory named 'aicc' is found, package type = aicc, but continue,
  420. // because we need to find the right AICC files;
  421. if ($this->debug > 1) {
  422. error_log('New LP - aicc::import_package() - Found aicc directory: '.$thisContent['filename']);
  423. }
  424. $package_type = 'aicc';
  425. } else {
  426. // else, look for one of the files we're searching for (something.crs case insensitive).
  427. $res = [];
  428. if (preg_match('?^(.*)\.(crs|au|des|cst|ore|pre|cmp)$?i', $thisContent['filename'], $res)) {
  429. if ($this->debug > 1) {
  430. error_log(
  431. 'New LP - aicc::import_package() - Found AICC config file: '.$thisContent['filename'].'. Now splitting: '.$res[1].' and '.$res[2]
  432. );
  433. }
  434. if ($thisContent['filename'] == basename($thisContent['filename'])) {
  435. if ($this->debug > 2) {
  436. error_log('New LP - aicc::import_package() - '.$thisContent['filename'].' is at root level', 0);
  437. }
  438. $at_root = true;
  439. if (!is_array($files_found[$res[1]])) {
  440. $files_found[$res[1]] = $this->config_exts; // Initialise list of expected extensions (defined in class definition).
  441. }
  442. $files_found[$res[1]][api_strtolower($res[2])] = $thisContent['filename'];
  443. $subdir_isset = true;
  444. } else {
  445. if (!$subdir_isset) {
  446. if (preg_match('?^.*/aicc$?i', dirname($thisContent['filename']))) {
  447. //echo "Cutting subdir<br/>";
  448. $this->subdir .= '/'.substr(dirname($thisContent['filename']), 0, -5);
  449. } else {
  450. //echo "Not cutting subdir<br/>";
  451. $this->subdir .= '/'.dirname($thisContent['filename']);
  452. }
  453. $subdir_isset = true;
  454. }
  455. if ($this->debug > 2) {
  456. error_log('New LP - aicc::import_package() - '.$thisContent['filename'].' is not at root level - recording subdir '.$this->subdir, 0);
  457. }
  458. $config_dir = dirname($thisContent['filename']); // Just the relative directory inside scorm/
  459. if (!is_array($files_found[basename($res[1])])) {
  460. $files_found[basename($res[1])] = $this->config_exts;
  461. }
  462. $files_found[basename($res[1])][api_strtolower($res[2])] = basename($thisContent['filename']);
  463. }
  464. $package_type = 'aicc';
  465. } else {
  466. if ($this->debug > 3) {
  467. error_log('New LP - aicc::import_package() - File '.$thisContent['filename'].' didnt match any check', 0);
  468. }
  469. }
  470. }
  471. $realFileSize += $thisContent['size'];
  472. }
  473. if ($this->debug > 2) {
  474. error_log('New LP - aicc::import_package() - $files_found: '.print_r($files_found, true), 0);
  475. }
  476. if ($this->debug > 1) {
  477. error_log('New LP - aicc::import_package() - Package type is now '.$package_type, 0);
  478. }
  479. $mandatory = false;
  480. foreach ($files_found as $file_name => $file_exts) {
  481. $temp = (
  482. !empty($files_found[$file_name]['crs'])
  483. && !empty($files_found[$file_name]['au'])
  484. && !empty($files_found[$file_name]['des'])
  485. && !empty($files_found[$file_name]['cst'])
  486. );
  487. if ($temp) {
  488. if ($this->debug > 1) {
  489. error_log('New LP - aicc::import_package() - Found all config files for '.$file_name, 0);
  490. }
  491. $mandatory = true;
  492. $package = $file_name;
  493. // Store base config file name for reuse in parse_config_files().
  494. $this->config_basename = $file_name;
  495. // Store filenames for reuse in parse_config_files().
  496. $this->config_files = $files_found[$file_name];
  497. // Get out, we only want one config files set.
  498. break;
  499. }
  500. }
  501. if ($package_type == '' || !$mandatory) {
  502. Display::addFlash(
  503. Display::return_message(get_lang('FileError'))
  504. );
  505. return false;
  506. }
  507. if (!enough_size($realFileSize, $course_sys_dir, $maxFilledSpace)) {
  508. Display::addFlash(
  509. Display::return_message(get_lang('NoSpace'))
  510. );
  511. return false;
  512. }
  513. // It happens on Linux that $new_dir sometimes doesn't start with '/'
  514. if ($new_dir[0] != '/') {
  515. $new_dir = '/'.$new_dir;
  516. }
  517. // Cut trailing slash.
  518. if ($new_dir[strlen($new_dir) - 1] == '/') {
  519. $new_dir = substr($new_dir, 0, -1);
  520. }
  521. /* Uncompressing phase */
  522. /*
  523. We need to process each individual file in the zip archive to
  524. - add it to the database
  525. - parse & change relative html links
  526. - make sure the filenames are secure (filter funny characters or php extensions)
  527. */
  528. if (is_dir($course_sys_dir.$new_dir) ||
  529. @mkdir($course_sys_dir.$new_dir, api_get_permissions_for_new_directories())
  530. ) {
  531. // PHP method - slower...
  532. if ($this->debug >= 1) {
  533. error_log('New LP - Changing dir to '.$course_sys_dir.$new_dir, 0);
  534. }
  535. chdir($course_sys_dir.$new_dir);
  536. $zipFile->extract(
  537. PCLZIP_CB_PRE_EXTRACT,
  538. 'clean_up_files_in_zip'
  539. );
  540. } else {
  541. return '';
  542. }
  543. return $course_sys_dir.$new_dir.$config_dir;
  544. }
  545. /**
  546. * Sets the proximity setting in the database.
  547. *
  548. * @param string $proxy Proximity setting
  549. *
  550. * @return bool
  551. */
  552. public function set_proximity($proxy = '')
  553. {
  554. $course_id = api_get_course_int_id();
  555. if ($this->debug > 0) {
  556. error_log('In aicc::set_proximity('.$proxy.') method', 0);
  557. }
  558. $lp = $this->get_id();
  559. if ($lp != 0) {
  560. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  561. $sql = "UPDATE $tbl_lp SET content_local = '$proxy' WHERE c_id = ".$course_id." id = ".$lp;
  562. Database::query($sql);
  563. return true;
  564. } else {
  565. return false;
  566. }
  567. }
  568. /**
  569. * Sets the theme setting in the database.
  570. *
  571. * @param string Theme setting
  572. *
  573. * @return bool
  574. */
  575. public function set_theme($theme = '')
  576. {
  577. $course_id = api_get_course_int_id();
  578. if ($this->debug > 0) {
  579. error_log('In aicc::set_theme('.$theme.') method', 0);
  580. }
  581. $lp = $this->get_id();
  582. if ($lp != 0) {
  583. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  584. $sql = "UPDATE $tbl_lp SET theme = '$theme' WHERE c_id = ".$course_id." id = ".$lp;
  585. $res = Database::query($sql);
  586. return true;
  587. } else {
  588. return false;
  589. }
  590. }
  591. /**
  592. * Sets the image LP in the database.
  593. *
  594. * @param string $preview_image Theme setting
  595. *
  596. * @return bool
  597. */
  598. public function set_preview_image($preview_image = '')
  599. {
  600. $course_id = api_get_course_int_id();
  601. if ($this->debug > 0) {
  602. error_log('In aicc::set_preview_image('.$preview_image.') method', 0);
  603. }
  604. $lp = $this->get_id();
  605. if ($lp != 0) {
  606. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  607. $sql = "UPDATE $tbl_lp SET preview_image = '$preview_image'
  608. WHERE c_id = ".$course_id." id = ".$lp;
  609. Database::query($sql);
  610. return true;
  611. } else {
  612. return false;
  613. }
  614. }
  615. /**
  616. * Sets the Author LP in the database.
  617. *
  618. * @param string $author
  619. *
  620. * @return true
  621. */
  622. public function set_author($author = '')
  623. {
  624. $course_id = api_get_course_int_id();
  625. $lp = $this->get_id();
  626. if ($lp != 0) {
  627. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  628. $sql = "UPDATE $tbl_lp SET author = '$author'
  629. WHERE c_id = ".$course_id." id = ".$lp;
  630. Database::query($sql);
  631. return true;
  632. }
  633. return false;
  634. }
  635. /**
  636. * Sets the content maker setting in the database.
  637. *
  638. * @param string $maker
  639. *
  640. * @return bool
  641. */
  642. public function set_maker($maker = '')
  643. {
  644. $course_id = api_get_course_int_id();
  645. if ($this->debug > 0) {
  646. error_log('In aicc::set_maker method('.$maker.')', 0);
  647. }
  648. $lp = $this->get_id();
  649. if ($lp != 0) {
  650. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  651. $sql = "UPDATE $tbl_lp SET content_maker = '$maker'
  652. WHERE c_id = ".$course_id." id = ".$lp;
  653. Database::query($sql);
  654. return true;
  655. } else {
  656. return false;
  657. }
  658. }
  659. /**
  660. * Exports the current AICC object's files as a zip. Excerpts taken from learnpath_functions.inc.php::exportpath().
  661. *
  662. * @param int Learnpath ID (optional, taken from object context if not defined)
  663. *
  664. * @return bool
  665. */
  666. public function export_zip($lp_id = null)
  667. {
  668. if ($this->debug > 0) {
  669. error_log('In aicc::export_zip method('.$lp_id.')', 0);
  670. }
  671. if (empty($lp_id)) {
  672. if (!is_object($this)) {
  673. return false;
  674. } else {
  675. $id = $this->get_id();
  676. if (empty($id)) {
  677. return false;
  678. } else {
  679. $lp_id = $this->get_id();
  680. }
  681. }
  682. }
  683. // Zip everything that is in the corresponding scorm dir.
  684. // Write the zip file somewhere (might be too big to return).
  685. $course_id = api_get_course_int_id();
  686. $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
  687. $_course = api_get_course_info(api_get_course_id());
  688. $sql = "SELECT * FROM $tbl_lp WHERE iid= $lp_id";
  689. $result = Database::query($sql);
  690. $row = Database::fetch_array($result);
  691. $LPname = $row['path'];
  692. $list = explode('/', $LPname);
  693. $LPnamesafe = $list[0];
  694. //$zipfoldername = '/tmp';
  695. //$zipfoldername = '../../courses/'.$_course['directory'].'/temp/'.$LPnamesafe;
  696. $zipfoldername = api_get_path(SYS_COURSE_PATH).$_course['directory'].'/temp/'.$LPnamesafe;
  697. $scormfoldername = api_get_path(SYS_COURSE_PATH).$_course['directory'].'/scorm/'.$LPnamesafe;
  698. $zipfilename = $zipfoldername.'/'.$LPnamesafe.'.zip';
  699. // Get a temporary dir for creating the zip file.
  700. removeDir($zipfoldername); //make sure the temp dir is cleared
  701. mkdir($zipfoldername, api_get_permissions_for_new_directories());
  702. // Create zipfile of given directory.
  703. $zip_folder = new PclZip($zipfilename);
  704. $zip_folder->create($scormfoldername.'/', PCLZIP_OPT_REMOVE_PATH, $scormfoldername.'/');
  705. //this file sending implies removing the default mime-type from php.ini
  706. DocumentManager::file_send_for_download($zipfilename, true);
  707. // Delete the temporary zip file and directory in fileManage.lib.php
  708. my_delete($zipfilename);
  709. my_delete($zipfoldername);
  710. return true;
  711. }
  712. /**
  713. * Gets a resource's path if available, otherwise return empty string.
  714. *
  715. * @param string Resource ID as used in resource array
  716. *
  717. * @return string The resource's path as declared in config file course.crs
  718. */
  719. public function get_res_path($id)
  720. {
  721. if ($this->debug > 0) {
  722. error_log('In aicc::get_res_path('.$id.') method', 0);
  723. }
  724. $path = '';
  725. if (isset($this->resources[$id])) {
  726. $oRes = &$this->resources[$id];
  727. $path = @$oRes->get_path();
  728. }
  729. return $path;
  730. }
  731. /**
  732. * Gets a resource's type if available, otherwise return empty string.
  733. *
  734. * @param string Resource ID as used in resource array
  735. *
  736. * @return string The resource's type as declared in the assignable unit (.au) file
  737. */
  738. public function get_res_type($id)
  739. {
  740. if ($this->debug > 0) {
  741. error_log('In aicc::get_res_type('.$id.') method', 0);
  742. }
  743. $type = '';
  744. if (isset($this->resources[$id])) {
  745. $oRes = &$this->resources[$id];
  746. $temptype = $oRes->get_scorm_type();
  747. if (!empty($temptype)) {
  748. $type = $temptype;
  749. }
  750. }
  751. return $type;
  752. }
  753. /**
  754. * Gets the default organisation's title.
  755. *
  756. * @return string The organization's title
  757. */
  758. public function get_title()
  759. {
  760. if ($this->debug > 0) {
  761. error_log('In aicc::get_title() method', 0);
  762. }
  763. $title = '';
  764. if (isset($this->config['organizations']['default'])) {
  765. $title = $this->organizations[$this->config['organizations']['default']]->get_name();
  766. } elseif (count($this->organizations) == 1) {
  767. // This will only get one title but so we don't need to know the index.
  768. foreach ($this->organizations as $id => $value) {
  769. $title = $this->organizations[$id]->get_name();
  770. break;
  771. }
  772. }
  773. return $title;
  774. }
  775. /**
  776. * TODO: Implement this function to restore items data from a set of AICC config files,
  777. * updating the existing table... This will prove very useful in case initial data
  778. * from config files were not imported well enough.
  779. */
  780. public function reimport_aicc()
  781. {
  782. if ($this->debug > 0) {
  783. error_log('In aicc::reimport_aicc() method', 0);
  784. }
  785. //query current items list
  786. //get the identifiers
  787. //parse the config files
  788. //match both
  789. //update DB accordingly
  790. return true;
  791. }
  792. /**
  793. * Static function to parse AICC ini files.
  794. * Based on work by sinedeo at gmail dot com published on php.net (parse_ini_file()).
  795. *
  796. * @param string File path
  797. *
  798. * @return array Structured array
  799. */
  800. public function parse_ini_file_quotes_safe($f)
  801. {
  802. $null = '';
  803. $r = $null;
  804. $sec = $null;
  805. $f = @file_get_contents($f);
  806. $f = api_convert_encoding($f, api_get_system_encoding(), $this->config_encoding);
  807. $f = preg_split('/\r?\n/', $f);
  808. for ($i = 0; $i < @count($f); $i++) {
  809. $newsec = 0;
  810. $w = @trim($f[$i]);
  811. if (substr($w, 0, 1) == ';') {
  812. // Ignore comment lines
  813. continue;
  814. }
  815. if ($w) {
  816. if ((!$r) or ($sec)) {
  817. if ((@substr($w, 0, 1) == '[') and (@substr($w, -1, 1)) == ']') {
  818. $sec = @substr($w, 1, @strlen($w) - 2);
  819. $newsec = 1;
  820. }
  821. }
  822. if (!$newsec) {
  823. $w = @explode('=', $w);
  824. $k = @trim($w[0]);
  825. unset($w[0]);
  826. $v = @trim(@implode('=', $w));
  827. if ((@substr($v, 0, 1) == "\"") and (@substr($v, -1, 1) == "\"")) {
  828. $v = @substr($v, 1, @strlen($v) - 2);
  829. }
  830. if ($sec) {
  831. if (api_strtolower($sec) == 'course_description') { // A special case.
  832. $r[api_strtolower($sec)] = $k;
  833. } else {
  834. $r[api_strtolower($sec)][api_strtolower($k)] = $v;
  835. }
  836. } else {
  837. $r[api_strtolower($k)] = $v;
  838. }
  839. }
  840. }
  841. }
  842. return $r;
  843. }
  844. /**
  845. * Static function to parse AICC ini strings.
  846. * Based on work by sinedeo at gmail dot com published on php.net (parse_ini_file()).
  847. *
  848. * @param string INI File string
  849. * @param array List of names of sections that should be considered
  850. * as containing only hard string data (no variables), provided in lower case
  851. *
  852. * @return array Structured array
  853. */
  854. public function parse_ini_string_quotes_safe($s, $pure_strings = [])
  855. {
  856. $null = '';
  857. $r = $null;
  858. $sec = $null;
  859. $s = api_convert_encoding($s, api_get_system_encoding(), $this->config_encoding);
  860. //$f = split("\r\n", $s);
  861. $f = preg_split('/\r?\n/', $s);
  862. for ($i = 0; $i < @count($f); $i++) {
  863. $newsec = 0;
  864. $w = @trim($f[$i]);
  865. if (substr($w, 0, 1) == ';') {
  866. // Ignore comment lines
  867. continue;
  868. }
  869. if ($w) {
  870. if ((!$r) or ($sec)) {
  871. if ((@substr($w, 0, 1) == '[') and (@substr($w, -1, 1)) == ']') {
  872. $sec = @substr($w, 1, @strlen($w) - 2);
  873. $pure_data = 0;
  874. if (in_array(api_strtolower($sec), $pure_strings)) {
  875. // This section can only be considered as pure string data (until the next section).
  876. $pure_data = 1;
  877. $r[api_strtolower($sec)] = '';
  878. }
  879. $newsec = 1;
  880. }
  881. }
  882. if (!$newsec) {
  883. $w = @explode('=', $w);
  884. $k = @trim($w[0]);
  885. unset($w[0]);
  886. $v = @trim(@implode('=', $w));
  887. if ((@substr($v, 0, 1) == "\"") and (@substr($v, -1, 1) == "\"")) {
  888. $v = @substr($v, 1, @strlen($v) - 2);
  889. }
  890. if ($sec) {
  891. if ($pure_data) {
  892. $r[api_strtolower($sec)] .= $f[$i];
  893. } else {
  894. if (api_strtolower($sec) == 'course_description') { // A special case.
  895. $r[api_strtolower($sec)] = $k;
  896. } else {
  897. $r[api_strtolower($sec)][api_strtolower($k)] = $v;
  898. }
  899. }
  900. } else {
  901. $r[api_strtolower($k)] = $v;
  902. }
  903. }
  904. }
  905. }
  906. return $r;
  907. }
  908. /**
  909. * Static function that parses CSV files into simple arrays, based on a function
  910. * by spam at cyber-space dot nl published on php.net (fgetcsv()).
  911. *
  912. * @param string Filepath
  913. * @param string CSV delimiter
  914. * @param string CSV enclosure
  915. * @param bool Might one field name happen more than once on the same line? (then split by comma in the values)
  916. *
  917. * @return array Simple structured array
  918. */
  919. public function parse_csv_file($f, $delim = ',', $enclosure = '"', $multiples = false)
  920. {
  921. $data = @file_get_contents($f);
  922. $data = api_convert_encoding($data, api_get_system_encoding(), $this->config_encoding);
  923. $enclosed = false;
  924. $fldcount = 0;
  925. $linecount = 0;
  926. $fldval = '';
  927. for ($i = 0; $i < strlen($data); $i++) {
  928. $chr = $data[$i];
  929. switch ($chr) {
  930. case $enclosure:
  931. if ($enclosed && $data[$i + 1] == $enclosure) {
  932. $fldval .= $chr;
  933. $i++; // Skip the next character.
  934. } else {
  935. $enclosed = !$enclosed;
  936. }
  937. break;
  938. case $delim:
  939. if (!$enclosed) {
  940. $ret_array[$linecount][$fldcount++] = $fldval;
  941. $fldval = '';
  942. } else {
  943. $fldval .= $chr;
  944. }
  945. break;
  946. case "\r":
  947. if (!$enclosed && $data[$i + 1] == "\n") {
  948. continue;
  949. }
  950. // no break
  951. case "\n":
  952. if (!$enclosed) {
  953. $ret_array[$linecount++][$fldcount] = $fldval;
  954. $fldcount = 0;
  955. $fldval = '';
  956. } else {
  957. $fldval .= $chr;
  958. }
  959. break;
  960. case "\\r":
  961. if (!$enclosed && $data[$i + 1] == "\\n") {
  962. continue;
  963. }
  964. // no break
  965. case "\\n":
  966. if (!$enclosed) {
  967. $ret_array[$linecount++][$fldcount] = $fldval;
  968. $fldcount = 0;
  969. $fldval = '';
  970. } else {
  971. $fldval .= $chr;
  972. }
  973. break;
  974. default:
  975. $fldval .= $chr;
  976. }
  977. }
  978. if ($fldval) {
  979. $ret_array[$linecount][$fldcount] = $fldval;
  980. }
  981. // Transform the array to use the first line as titles.
  982. $titles = [];
  983. $ret_ret_array = [];
  984. foreach ($ret_array as $line_idx => $line) {
  985. if ($line_idx == 0) {
  986. $titles = $line;
  987. } else {
  988. $ret_ret_array[$line_idx] = [];
  989. foreach ($line as $idx => $val) {
  990. if ($multiples && !empty($ret_ret_array[$line_idx][api_strtolower($titles[$idx])])) {
  991. $ret_ret_array[$line_idx][api_strtolower($titles[$idx])] .= ','.$val;
  992. } else {
  993. $ret_ret_array[$line_idx][api_strtolower($titles[$idx])] = $val;
  994. }
  995. }
  996. }
  997. }
  998. return $ret_ret_array;
  999. }
  1000. }