* @license GNU/GPL */ /** * Code */ require_once 'aiccItem.class.php'; //require_once 'aiccMetadata.class.php'; //require_once 'aiccOrganization.class.php'; require_once 'aiccResource.class.php'; require_once 'aiccBlock.class.php'; /** * Class * @package chamilo.learnpath */ class aicc extends learnpath { public $config = array(); public $config_basename = ''; // The configuration files might be multiple and might have // funny names. We need to keep the name of that file while we // install the content. public $config_files = array(); public $config_exts = array( 'crs' => 0, // Course description file (mandatory) 'au' => 0, // Assignable Unit file (mandatory) 'des' => 0, // Descriptor file (mandatory) 'cst' => 0, // Course structure file (mandatory) 'ore' => 0, // Objectives relationshops file (optional) 'pre' => 0, // Prerequisites file (optional) 'cmp' => 0 // Completion Requirements file (optional) ); public $aulist = array(); public $au_order_list = array(); public $au_order_list_new_id = array(); public $deslist = array(); public $cstlist = array(); public $orelist = array(); public $subdir = ''; // Path between the scorm/ directory and the config files e.g. maritime_nav/maritime_nav. This is the path that will be used in the lp_path when importing a package. public $zipname = ''; // Keeps the zipfile safe for the object's life so that we can use it if there is no title available. public $lastzipnameindex = 0; // Keeps an index of the number of uses of the zipname so far. public $config_encoding = 'ISO-8859-1'; public $debug = 0; /** * Class constructor. Based on the parent constructor. * @param string Course code * @param integer Learnpath ID in DB * @param integer User ID */ public function __construct($course_code = null, $resource_id = null, $user_id = null) { if ($this->debug > 0) { error_log('In aicc::aicc()', 0); } if (!empty($course_code) && !empty($resource_id) && !empty($user_id)) { parent::__construct($course_code, $resource_id, $user_id); } else { //do nothing but still build the aicc object } } /** * Opens a resource * @param integer Database ID of the resource */ public function open($id) { if ($this->debug > 0) { error_log('In aicc::open()', 0); } // Redefine parent method. } /** * Parses a set of AICC config files and puts everything into the $config array * @param string Path to the config files dir on the system. If not defined, uses the base path of the course's scorm dir * @return array Structured array representing the config files' contents */ function parse_config_files($dir = '') { if ($this->debug > 0) { error_log('New LP - In aicc::parse_config_files('.$dir.')', 0); } if (empty($dir)) { // Get the path of the AICC config files dir. $dir = $this->subdir; } if (is_dir($dir) && is_readable($dir)) { // Now go through all the config files one by one and parse everything into AICC objects. // The basename for the config files is stored in $this->config_basename. // Parse the Course Description File (.crs) - ini-type. $crs_file = $dir.'/'.$this->config_files['crs']; $crs_params = $this->parse_ini_file_quotes_safe($crs_file); //echo '
crs:'.print_r($crs_params, true).''; if ($this->debug > 1) { error_log('New LP - In aicc::parse_config_files() - '.$crs_file.' has been parsed', 0); } // CRS distribute crs params into the aicc object. if (!empty($crs_params['course']['course_creator'])) { $this->course_creator = Database::escape_string($crs_params['course']['course_creator']); } if (!empty($crs_params['course']['course_id'])) { $this->course_id = Database::escape_string($crs_params['course']['course_id']); } if (!empty($crs_params['course']['course_system'])) { $this->course_system = $crs_params['course']['course_system']; } if (!empty($crs_params['course']['course_title'])) { $this->course_title = Database::escape_string($crs_params['course']['course_title']); } if (!empty($crs_params['course']['course_level'])) { $this->course_level = $crs_params['course']['course_level']; } if (!empty($crs_params['course']['max_fields_cst'])) { $this->course_max_fields_cst = $crs_params['course']['max_fields_cst']; } if (!empty($crs_params['course']['max_fields_ort'])) { $this->course_max_fields_ort = $crs_params['course']['max_fields_ort']; } if (!empty($crs_params['course']['total_aus'])) { $this->course_total_aus = $crs_params['course']['total_aus']; } if (!empty($crs_params['course']['total_blocks'])) { $this->course_total_blocks = $crs_params['course']['total_blocks']; } if (!empty($crs_params['course']['total_objectives'])) { $this->course_total_objectives = $crs_params['course']['total_objectives']; } if (!empty($crs_params['course']['total_complex_objectives'])) { $this->course_total_complex_objectives = $crs_params['course']['total_complex_objectives']; } if (!empty($crs_params['course']['version'])) { $this->course_version = $crs_params['course']['version']; } if (!empty($crs_params['course_description'])) { $this->course_description = Database::escape_string($crs_params['course_description']); } // Parse the Descriptor File (.des) - csv-type. $des_file = $dir.'/'.$this->config_files['des']; $des_params = $this->parse_csv_file($des_file); //echo '
des:'.print_r($des_params, true).''; if ($this->debug > 1) { error_log('New LP - In aicc::parse_config_files() - '.$des_file.' has been parsed', 0); } // Distribute des params into the aicc object. foreach ($des_params as $des) { // One AU in AICC is equivalent to one SCO in SCORM (scormItem class). $oDes = new aiccResource('config', $des); $this->deslist[$oDes->identifier] = $oDes; } // Parse the Assignable Unit File (.au) - csv-type. $au_file = $dir.'/'.$this->config_files['au']; $au_params = $this->parse_csv_file($au_file); //echo '
au:'.print_r($au_params, true).''; if ($this->debug > 1) { error_log('New LP - In aicc::parse_config_files() - '.$au_file.' has been parsed', 0); } // Distribute au params into the aicc object. foreach ($au_params as $au) { $oAu = new aiccItem('config', $au); $this->aulist[$oAu->identifier] = $oAu; $this->au_order_list[] = $oAu->identifier; } // Parse the Course Structure File (.cst) - csv-type. $cst_file = $dir.'/'.$this->config_files['cst']; $cst_params = $this->parse_csv_file($cst_file, ',', '"', true); //echo '
cst:'.print_r($cst_params, true).''; if ($this->debug > 1) { error_log('New LP - In aicc::parse_config_files() - '.$cst_file.' has been parsed', 0); } // Distribute cst params into the aicc object. foreach ($cst_params as $cst) { $oCst = new aiccBlock('config', $cst); $this->cstlist[$oCst->identifier] = $oCst; } // Parse the Objectives Relationships File (.ore) - csv-type - if exists. // TODO: Implement these objectives. For now they're just parsed. if (!empty($this->config_files['ore'])) { $ore_file = $dir.'/'.$this->config_files['ore']; $ore_params = $this->parse_csv_file($ore_file, ',', '"', true); //echo '
ore:'.print_r($ore_params,true).''; if ($this->debug > 1) { error_log('New LP - In aicc::parse_config_files() - '.$ore_file.' has been parsed', 0); } // Distribute ore params into the aicc object. foreach ($ore_params as $ore) { $oOre = new aiccObjective('config', $ore); $this->orelist[$oOre->identifier] = $oOre; } } // Parse the Prerequisites File (.pre) - csv-type - if exists. if (!empty($this->config_files['pre'])) { $pre_file = $dir.'/'.$this->config_files['pre']; $pre_params = $this->parse_csv_file($pre_file); //echo '
pre:'.print_r($pre_params, true).''; if ($this->debug > 1) { error_log('New LP - In aicc::parse_config_files() - '.$pre_file.' has been parsed', 0); } // Distribute pre params into the aicc object. foreach ($pre_params as $pre) { // Place a constraint on the corresponding block or AU. if (in_array(api_strtolower($pre['structure_element']), array_keys($this->cstlist))) { // If this references a block element: $this->cstlist[api_strtolower($pre['structure_element'])]->prereq_string = api_strtolower( $pre['prerequisite'] ); } if (in_array(api_strtolower($pre['structure_element']), array_keys($this->aulist))) { // If this references a block element: $this->aulist[api_strtolower($pre['structure_element'])]->prereq_string = api_strtolower( $pre['prerequisite'] ); } } } // Parse the Completion Requirements File (.cmp) - csv-type - if exists. // TODO: Implement this set of requirements (needs database changes). if (!empty($this->config_files['cmp'])) { $cmp_file = $dir.'/'.$this->config_files['cmp']; $cmp_params = $this->parse_csv_file($cmp_file); //echo '
cmp:'.print_r($cmp_params, true).''; if ($this->debug > 1) { error_log('New LP - In aicc::parse_config_files() - '.$cmp_file.' has been parsed', 0); } // Distribute cmp params into the aicc object. foreach ($cmp_params as $cmp) { //$oCmp = new aiccCompletionRequirements('config', $cmp); //$this->cmplist[$oCmp->identifier] =& $oCmp; } } } return $this->config; } /** * Import the aicc object (as a result from the parse_config_files function) into the database structure * @param string Unique course code * @return bool Returns -1 on error */ function import_aicc($course_code) { $course_id = api_get_course_int_id(); if ($this->debug > 0) { error_log('New LP - In aicc::import_aicc('.$course_code.')', 0); } // Get table names. $new_lp = 'lp'; $new_lp_item = 'lp_item'; // The previous method wasn't safe to get the database name, so do it manually with the course_code. $sql = "SELECT * FROM ".Database::get_main_table(TABLE_MAIN_COURSE)." WHERE code='$course_code'"; $res = Database::query($sql); if (Database::num_rows($res) < 1) { error_log('New LP - Database for '.$course_code.' not found '.__FILE__.' '.__LINE__, 0); return -1; } $row = Database::fetch_array($res); $new_lp = Database::get_course_table(TABLE_LP_MAIN); $new_lp_item = Database::get_course_table(TABLE_LP_ITEM); $get_max = "SELECT MAX(display_order) FROM $new_lp WHERE c_id = $course_id"; $res_max = Database::query($get_max); if (Database::num_rows($res_max) < 1) { $dsp = 1; } else { $row = Database::fetch_array($res_max); $dsp = $row[0] + 1; } $this->config_encoding = "ISO-8859-1"; // TODO: We may apply detection for this value, see the function api_detect_encoding(). $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)". "VALUES ". "($course_id, 3, '".$this->course_title."', '".$this->course_id."','".$this->course_description."',". "'".$this->subdir."', 0, 'embedded', '".$this->config_encoding."',". "'aicc_api.php','".$this->course_creator."',$dsp)"; if ($this->debug > 2) { error_log('New LP - In import_aicc(), inserting path: '.$sql, 0); } $res = Database::query($sql); $lp_id = Database::insert_id(); $this->lp_id = $lp_id; api_item_property_update( api_get_course_info($course_code), TOOL_LEARNPATH, $this->lp_id, 'LearnpathAdded', api_get_user_id() ); api_item_property_update( api_get_course_info($course_code), TOOL_LEARNPATH, $this->lp_id, 'visible', api_get_user_id() ); $previous = 0; foreach ($this->aulist as $identifier => $dummy) { $oAu =& $this->aulist[$identifier]; //echo "Item ".$oAu->identifier; $field_add = ''; $value_add = ''; if (!empty($oAu->masteryscore)) { $field_add = 'mastery_score, '; $value_add = $oAu->masteryscore.','; } $title = $oAu->identifier; if (is_object($this->deslist[$identifier])) { $title = $this->deslist[$identifier]->title; } $path = $oAu->path; //$max_score = $oAu->max_score // TODO: Check if special constraint exists for this item. //$min_score = $oAu->min_score // TODO: Check if special constraint exists for this item. $parent = 0; // TODO: Deal with the parent. $previous = 0; $prereq = $oAu->prereq_string; //$previous = (!empty($this->au_order_list_new_id[x]) ? $this->au_order_list_new_id[x] : 0); // TODO: Deal with the previous. $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) ". "VALUES ". "($course_id, $lp_id, 'au','".$oAu->identifier."','".$title."',". "'$path',0,100, $value_add". "$parent, $previous, 0, ". "'$prereq', 0". ")"; $res_item = Database::query($sql_item); if ($this->debug > 1) { error_log('New LP - In aicc::import_aicc() - inserting item : '.$sql_item.' : '.Database::error(), 0); } $item_id = Database::insert_id(); // Now update previous item to change next_item_id. if ($previous != 0) { $upd = "UPDATE $new_lp_item SET next_item_id = $item_id WHERE c_id = $course_id AND id = $previous"; $upd_res = Database::query($upd); // Update the previous item id. } $previous = $item_id; } } /** * Intermediate to import_package only to allow import from local zip files * @param string Path to the zip file, from the dokeos sys root * @param string Current path (optional) * @return string Absolute path to the AICC description files or empty string on error */ function import_local_package($file_path, $current_dir = '') { // TODO: Prepare info as given by the $_FILES[''] vector. $file_info = array(); $file_info['tmp_name'] = $file_path; $file_info['name'] = basename($file_path); // Call the normal import_package function. return $this->import_package($file_info, $current_dir); } /** * Imports a zip file (presumably AICC) into the Chamilo structure * @param string Zip file info as given by $_FILES['userFile'] * @return string Absolute path to the AICC config files directory or empty string on error */ function import_package($zip_file_info, $current_dir = '') { if ($this->debug > 0) { error_log('In aicc::import_package('.print_r($zip_file_info, true).',"'.$current_dir.'") method', 0); } //ini_set('error_log', 'E_ALL'); $maxFilledSpace = 1000000000; $zip_file_path = $zip_file_info['tmp_name']; $zip_file_name = $zip_file_info['name']; if ($this->debug > 0) { error_log( 'New LP - aicc::import_package() - Zip file path = '.$zip_file_path.', zip file name = '.$zip_file_name, 0 ); } $course_rel_dir = api_get_course_path().'/scorm'; // Scorm dir web path starting from /courses $course_sys_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir; // The absolute system path of this course. $current_dir = api_replace_dangerous_char(trim($current_dir), 'strict'); // Current dir we are in, inside scorm/ if ($this->debug > 0) { error_log('New LP - aicc::import_package() - Current_dir = '.$current_dir, 0); } //$uploaded_filename = $_FILES['userFile']['name']; // Get the name of the zip file without the extension. if ($this->debug > 0) { error_log('New LP - aicc::import_package() - Received zip file name: '.$zip_file_path, 0); } $file_info = pathinfo($zip_file_name); $filename = $file_info['basename']; $extension = $file_info['extension']; $file_base_name = str_replace('.'.$extension, '', $filename); // Filename without its extension. $this->zipname = $file_base_name; // Save for later in case we don't have a title. if ($this->debug > 0) { error_log('New LP - aicc::import_package() - Base file name is : '.$file_base_name, 0); } $new_dir = api_replace_dangerous_char(trim($file_base_name), 'strict'); $this->subdir = $new_dir; if ($this->debug > 0) { error_log('New LP - aicc::import_package() - Subdir is first set to : '.$this->subdir, 0); } /* if (check_name_exist($course_sys_dir.$current_dir.'/'.$new_dir)) { $dialogBox = get_lang('FileExists'); $stopping_error = true; } */ $zipFile = new PclZip($zip_file_path); // Check the zip content (real size and file extension). $zipContentArray = $zipFile->listContent(); $package_type = ''; // The type of the package. Should be 'aicc' after the next few lines. $package = ''; // The basename of the config files (if 'courses.crs' => 'courses'). $at_root = false; // Check if the config files are at zip root. $config_dir = ''; // The directory in which the config files are. May remain empty. $files_found = array(); $subdir_isset = false; // The following loop should be stopped as soon as we found the right config files (.crs, .au, .des and .cst). foreach ($zipContentArray as $thisContent) { if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) { // If a php file is found, do not authorize (security risk). if ($this->debug > 1) { error_log( 'New LP - aicc::import_package() - Found unauthorized file: '.$thisContent['filename'], 0 ); } return api_failure::set_failure('php_file_in_zip_file'); } elseif (preg_match('?.*/aicc/$?', $thisContent['filename'])) { // If a directory named 'aicc' is found, package type = aicc, but continue, // because we need to find the right AICC files; if ($this->debug > 1) { error_log('New LP - aicc::import_package() - Found aicc directory: '.$thisContent['filename'], 0); } $package_type = 'aicc'; } else { // else, look for one of the files we're searching for (something.crs case insensitive). $res = array(); if (preg_match('?^(.*)\.(crs|au|des|cst|ore|pre|cmp)$?i', $thisContent['filename'], $res)) { if ($this->debug > 1) { error_log( 'New LP - aicc::import_package() - Found AICC config file: '.$thisContent['filename'].'. Now splitting: '.$res[1].' and '.$res[2], 0 ); } if ($thisContent['filename'] == basename($thisContent['filename'])) { if ($this->debug > 2) { error_log( 'New LP - aicc::import_package() - '.$thisContent['filename'].' is at root level', 0 ); } $at_root = true; if (!is_array($files_found[$res[1]])) { $files_found[$res[1]] = $this->config_exts; // Initialise list of expected extensions (defined in class definition). } $files_found[$res[1]][api_strtolower($res[2])] = $thisContent['filename']; $subdir_isset = true; } else { if (!$subdir_isset) { if (preg_match('?^.*/aicc$?i', dirname($thisContent['filename']))) { //echo "Cutting subdir