aicc.class.php 42 KB

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