Skip to content

Instantly share code, notes, and snippets.

@MathieuDomingo
Last active July 24, 2018 13:42
Show Gist options
  • Save MathieuDomingo/28467fd619c7f2893cc1eefeefdb45da to your computer and use it in GitHub Desktop.
Save MathieuDomingo/28467fd619c7f2893cc1eefeefdb45da to your computer and use it in GitHub Desktop.
Script pour migrer des cours de chamilo vers moodle
<?php
/*
* Import des cours de Chamilo (1.9.6) vers Moodle (3.3)
*
* Auteur : Mathieu Domingo
* Coté moodle :
* Il faut la version modifiée de /mod/scorm/lib.php que j'ai faite pour pouvoir utiliser du createfilefrompathname
* Il faut la version modifiée de /mod/scorm/datamodels/scormlib.php pour throw une error au lieu du die dans la fonction parse(
* Il faut la version modifiée de /course/modlib.php pour catch l'exception et faire le rollback si il y a un echec lors de l'import d'un scorm
* Il faut crée une nouvelle catégorie caché qui va contenir tous les cours de Chamilo, recuperer l'id obtenu pour cette catégorie puis remplir la variable $categoryid avec l'id
*
* Coté Chamilo :
*
* Il faut le fichier /main/newscorm/lp_controller_unsecure.php (obtenu suite à une modification du fichier /main/newscorm/lp_controller.php)
* Il faut la version modifiée du fichier /main/newscorm/learnpath.class.php (ajout d'une fonction qui est appelée dans lp_controller_unsecure.php
*/
//pour envoyer le fichier en local vers le serveur puis l'executer
// clear; scp /home/mathieu/Bureaux/Bureau_Moodle/moodle-git-test/import_Chamilo_to_moodle.php root@moodle-test:/opt/tools/ ; md_moodle-test 'sudo -u www-data /usr/bin/php /opt/tools/import_Chamilo_to_moodle.php'
// copie des fichiers modifiés mis dans un script :
// ./copie_fichiers_modif_vers_moodle_test.sh ; md_moodle-test 'sudo -u www-data /usr/bin/php /opt/tools/import_Chamilo_to_moodle.php'
define('CLI_SCRIPT', true);
require_once ('/opt/moodle/config.php');
require_once ($CFG->libdir . '/clilib.php');
require_once ($CFG->dirroot . '/course/lib.php');
require_once($CFG->dirroot . '/course/modlib.php');
require_once($CFG->dirroot . '/lib/filestorage/file_storage.php');
//Variable globale : Informations de connexion a Chamilo
//!! Il faut que le serveur de moodle soit autoriser a se connecter a la bdd Chamilo !! (via grant access)
$Chamilo_db['serveur']="nom_serveur_chamilo";
$Chamilo_db['user']="login_chamilo";
$Chamilo_db['pass']="password_chamilo";
$Chamilo_db['base']="nom_base_chamilo";
$Chamilo_dbo = new mysqli($Chamilo_db['serveur'] , $Chamilo_db['user'] , $Chamilo_db['pass'] , $Chamilo_db['base']);
if ($Chamilo_dbo->connect_error) {
die('Erreur de connexion : ' . $Chamilo_dbo->connect_error."\n");
}
//Debut des fonctions qu'il faudrait tres serieusement ameliorer/recoder
// TODO : faire une vraie fonction qui permet de generer les zips des scorms de chamilo. En l'état, assez honteux comme solution de contournement..
// Dans /var/www/chamilo/main/newscorm/ j'ai modifié le fichier learnpath.class.php pour crée les zip des scorms dans le dossier scorm.
//j'ai également crée un fichier lp_controller_unsecure.php à partir du fichier lp_controller.php pour ne pas avoir besoin d'etre connecté pour executer une fonction perso qui appelle le fichier learnpath.class.php
//Appeller directement le lp_controller_unsecure.php n'est pas suffisant... cela crée bien des zips, mais il n'y a que les fichiers manifest, pas le contenu .. il faut obligatoirement etre connecté pour que cela fonctionne
//Solution alternative pour ne pas perdre de temps à faire croire que le script est connecté : se connecté dans firefox a chamilo, puis faire le "telechargement" du zip à la main
//cette fonction va generer les commandes linux qui vont bien pour ouvrir tous les onglets dans firefox. Il faut stocker le resultat de sortie de cette fonction dans un fichier script et lancé le script (en local ca ouvre firefox)
function creer_zips()
{
global $Chamilo_dbo;
//Requete pour obtenir tous les code cours et les lp_id (des scorms de type Dokeos)
//$query='SELECT c.code, clp.id as lp_id FROM c_lp clp, course c WHERE content_maker="Dokeos" and clp.c_id=c.id and c.code="C2IFAD"';
//$query='SELECT c.code, clp.id as lp_id FROM c_lp clp, course c WHERE content_maker="Dokeos" and clp.c_id=c.id and c.code="CL69cc"';
$query='SELECT c.code, clp.id as lp_id FROM c_lp clp, course c WHERE content_maker="Dokeos" and clp.c_id=c.id';
if($result=$Chamilo_dbo->query($query))
{
//echo "Nombre de liens : ".$result->num_rows."\n";
//Solution pourrie mais rapide : Rajout pour faire un script
echo "#!/bin/bash \n";
//tant qu'il y a des lignes on crée un objet que l'on push dans le tableau que l'on retourne
while($row=$result->fetch_object())
{
//print_r($row);
$adresse='https://webcampus.univ-pau.fr/main/newscorm/lp_controller_unsecure.php?cidReq='.$row->code.'&id_session=0&gidReq=0&action=export_perso&lp_id='.$row->lp_id;
//echo "Adresse : ".$adresse."\n";
//echo file_get_contents($adresse);
//readfile($adresse);
//sleep(10);
//Solution pourrie mais rapide : Rajout pour faire un script
echo 'firefox "view-source:'.$adresse.'"'."\n";
echo "sleep 2 \n";
}
//on "ferme" la requete
$result->close();
}
}
//Fin des fonctions qu'il faudrait tres serieusement ameliorer/recoder
//Debut des fonctions trouvées sur internet
//pour le "copyright" , fonction d'origine trouvée sur internet : http://php.net/manual/fr/function.scandir.php#107117
/*
function find_all_files($dir)
{
$root = scandir($dir);
foreach($root as $value)
{
if($value === '.' || $value === '..') {continue;}
if(is_file("$dir/$value")) {$result[]="$dir/$value";continue;}
foreach(find_all_files("$dir/$value") as $value)
{
$result[]=$value;
}
}
return $result;
}
*/
//modification pour ne pas avoir l'arborescence complete et pour separer le sous path et le nom du fichier
function find_all_files($dir,$subdir='/')
{
$result=array(); //init array
$root = scandir($dir.$subdir);
foreach($root as $value)
{
//print_r($dir.$subdir.$value."\n"); //pour voir toute l'arborescence
if($value === '.' || $value === '..') {continue;}
//Il y a des "fichiers par défaut" dans certains cours -> ajout de ces fichiers a exclure des imports
//Je ne sais pas vraiment si c'est le cas pour tous les Chamilos, mais bon dans le doute j'ajoute la modification..
if(
$value === 'ListeningComprehension.mp3' ||
$value === 'ArtefactsInRMI.swf' ||
$value === 'PonderationOfMrSignal.swf' ||
$value === 'SpinEchoSequence.swf' ||
$value === 'example.flv'
) {continue;}
if(is_file($dir.$subdir.$value)) {array_push($result, array("Path"=>$subdir,"File"=>$value));continue;}
foreach(find_all_files($dir, $subdir.$value."/") as $value)
{
$result[]=$value;
}
}
return $result;
}
//pour le "copyright" , fonction trouvée sur internet : php.net/manual/fr/function.is-dir.php#85961
function isEmptyDir($dir){
return (($files = @scandir($dir)) && count($files) <= 2);
}
//pour le "copyright", fonction d'origine trouvée sur internet : https://stackoverflow.com/questions/33052644/save-zip-file-in-a-different-directory-than-the-level-at-which-code-file-is-pres#question
//utilisée pour crée un Zip pour le dossier scorm des cours de Chamilo
function Zip($source, $destination, $include_dir = false)
{
if (!extension_loaded('zip') || !file_exists($source)) {
return false;
}
if (file_exists($destination)) {
unlink ($destination);
}
$zip = new ZipArchive();
if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
return false;
}
$source = str_replace('\\', '/', realpath($source));
if (is_dir($source) === true)
{
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
if ($include_dir) {
$arr = explode("/",$source);
$maindir = $arr[count($arr)- 1];
$source = "";
for ($i=0; $i < count($arr) - 1; $i++) {
$source .= '/' . $arr[$i];
}
$source = substr($source, 1);
$zip->addEmptyDir($maindir);
}
foreach ($files as $file)
{
$file = str_replace('\\', '/', $file);
// Ignore "." and ".." folders
if( in_array(substr($file, strrpos($file, '/')+1), array('.', '..')) )
continue;
$file = realpath($file);
if (is_dir($file) === true)
{
$zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
}
else if (is_file($file) === true)
{
$zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
}
}
}
else if (is_file($source) === true)
{
$zip->addFromString(basename($source), file_get_contents($source));
}
return $zip->close();
}
//Fin des fonctions trouvées sur internet
//Debut des fonctions globalement OK
/**
* Fonction qui crée un cours dans une categorie et y ajoute un enseignant
* @param int categoryid correspond à l'id de la catégorie (1290 par exemple)
* @param stdobject $cours est un objet qui contient les informations du cours : il faut fournir fullname shortname et idnumber (numero d'identification, pas necessairement un vrai numero)
* A ce stade $cours doit avoir pour attribut code, directory et title et teachers_id
**/
function creer_cours($categoryid,$cours)
{
global $DB;
$catcontext = context_coursecat::instance($categoryid);
$editoroptions['context'] = $catcontext;
$editoroptions['subdirs'] = 0;
//On crée l'objet requis par la fonction create_course
$data = new stdClass();
$data->fullname=$cours->title;
$data->shortname=$cours->code;
$data->idnumber=$cours->code;
$data->category="".$categoryid."";
$data->summary_editor=[];
$data->summary_editor["text"]="";//"Résumé du cours";
$data->summary_editor["format"]="1";
$data->format="topics";
$data->numsections="10";
//Creation du cours et "enrolement" de l'enseignement
echo "Creation du cours ".$data->shortname."\n"; // echo pour permettre de voir dans les logs où l'on est (les logs sont assez pauvres, il faudra ameliorer ce point si j'ai le temps :s)
$course = create_course($data, $editoroptions);
//A priori Il n'y a besoin que de l'id du coup je le stock dans l'objet cours pour les prochaines fonctions.
$cours->id=$course->id;
//On fixe le nom des 3 premieres sections du cours pour que cela soit cohérents avec ce que l'on va mettre dedans
//(Il est par contre possible que cela soit des sections vides, cela pourrait peut etre etre fait dans chaque fonction)
$DB->execute("UPDATE {course_sections} SET name = 'Documents' WHERE course = ? AND section = 1",array($cours->id));
$DB->execute("UPDATE {course_sections} SET name = 'Liens' WHERE course = ? AND section = 2",array($cours->id));
$DB->execute("UPDATE {course_sections} SET name = 'Scorm' WHERE course = ? AND section = 3",array($cours->id));
//on met a jour le cache du cours pour voir la modification
rebuild_course_cache($cours->id, true);
//Maintenant que le cours est crée il faut inscrire l'enseignant au cours.
//En créeant le cours on a obtenu l'id du cours, mais on a besoin du enrolid (id du cours - 1 et fois 4 mais on fait une requete pour l'obtenir proprement).
$instance = $DB->get_record('enrol', array('courseid'=>$cours->id, 'enrol'=>'manual'), '*', MUST_EXIST);
$roleid=3; //Je met le role 3 qui correspond a editing teacher, il faudra verifier en prod que c'est bien le meme numero, et si c'est bien ce role que je dois attribuer.
if (!$enrol_manual = enrol_get_plugin('manual')) {
throw new coding_exception('Can not instantiate enrol_manual');
}
foreach ($cours->teachers_id as $userid) {
$enrol_manual->enrol_user($instance, $userid, $roleid);//, $timestart, $timeend); pas besoin de preciser de date de debut et de fin des droits..
}
return $course;
}
/**
* Fonction qui recupere les liens de chamilo et les retourne dans un tableau
*/
function get_links($cours){
global $Chamilo_dbo, $DB;
//Etape 1 : obtenir les liens et les titles a partir du cours via une requete sur la table c_link de Chamilo
$query="select l.url, l.title from c_link l , course c where c.id=l.c_id and code='".$cours->code."'";
//on execute la requete et on stock les resultats dans results
if($result=$Chamilo_dbo->query($query))
{
echo "Nombre de liens : ".$result->num_rows."\n"; //pour les logs
//tant qu'il y a des lignes on crée un objet que l'on push dans le tableau que l'on retourne
while($row=$result->fetch_object())
{
//bien que la base de données soit indiqué avec un encodage en utf8, cela n'a pas l'air d'etre le cas des données qui sont à l'interieur ...
//du coup on boucle pour convertir chaque string en ut8 avant de les mettre dans le tableau retourné
foreach ($row as $key => $case) {
$row->$key=utf8_encode($case);
}
$links[]= $row;
}
//on "ferme" la requete
$result->close();
}
$cours->links=$links;
}
/**
* Fonction qui recupere les identifiants (moodle) des enseignants d'un cours.
* Premier traitement recupere les username du coté Chamilo puis les utilises pour obtenir les ids du coté Moodle
* A la fin de la fonction la variable $cours contient un attribut teachers_id ($cours->teachers_id) qui contient un tableau des ids.
*/
function get_teachers_id($cours)
{
global $Chamilo_dbo, $DB;
//Etape 1 : il va falloir trouver l'id moodle des enseignants du cours pour pouvoir les enroler
//Etape1_1 : Obtenir l'username des enseignants du coté Chamilo
$query='SELECT username '
. 'FROM course_rel_user cu, user u '
. 'WHERE u.user_id=cu.user_id and cu.status=1 and cu.course_code="'.$cours->code.'"';
//on execute la requete et on voit si on a des resultats
if($result=$Chamilo_dbo->query($query))
{
echo "Nombre d'enseignant dans le cours : ".$result->num_rows."\n"; //Pour les logs
if($result->num_rows==0)
{
throw new exception("Il n'y a pas d'enseignant dans le cours");
}
//tant qu'il y a des lignes on crée un objet que l'on push dans le tableau que l'on retourne
/*$count=0;
$select='';
while($row=$result->fetch_row())
{
if($count==0)
{
$select='username="'.$row[0].'"';
$count=$count+1;
}
else
{
$select=$select.' OR username="'.$row[0].'"';
}
}*/
//On initialise plutot à 0 et on evite de gerer l'apparition du OR qui devient systematique
$select='0';
while($row=$result->fetch_row())
{
$select=$select.' OR username="'.$row[0].'"';
}
//Etape1_2 : Obtenir l'id du coté moodle grace à l'username
$userids = $DB->get_records_sql('SELECT id FROM {user} where '.$select);
$teachersid=[];
foreach ($userids as $userid) {
$teachersid[]=$userid->id;
}
//On stock le resultat
$cours->teachers_id=$teachersid;
//on "ferme" la requete
$result->close();
}
}
/**
* Fonction qui retourne code directory et title pour l'ensemble des cours de Chamilo. Il suffit de modifier la requete pour traiter seulement une partie des cours.
*
*/
function get_Chamilo_cours()
{
global $Chamilo_dbo;
$return=[];
//requete pour obtenir des informations de base sur un cours
$query="Select code, directory, title from course where code='C2IFAD' "; //limite à un cours pour les tests
//$query="Select code, directory, title from course where YEAR( last_visit ) >2014 order by code";
//on execute la requete et on stock les resultats dans results
if($result=$Chamilo_dbo->query($query))
{
echo "Nombre de cours à transferer : ".$result->num_rows."\n"; //Pour les logs
//tant qu'il y a des lignes on crée un objet que l'on push dans le tableau que l'on retourne
while($row=$result->fetch_object())
{
//bien que la base de données soit indiqué avec un encodage en utf8, cela n'a pas l'air d'etre le cas des données qui sont à l'interieur ...
//du coup on boucle pour convertir chaque string en ut8 avant de les mettre dans le tableau retourné
foreach ($row as $key => $case) {
$row->$key=utf8_encode($case);
}
$return[]= $row;
}
//on "ferme" la requete
$result->close();
}
return $return;
}
/**
* Fonction qui importe des documents dans moodle
* @param string folderpath correspond au chemin du repertoire qui contient les differents fichiers et dossier à importer (le repertoire en question n'est pas importé, seulement son contenu)
* @param stdobject course correspond à l'objet retournée apres la creation du cours ou s'obtient grace à l'id du cours avec $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
* @param stdobject data est un objet qui contient les informations du cours : il faut fournir fullname shortname et idnumber (numero d'identification, pas necessairement un vrai numero)
* a voir pour $data si je trouve un "résumé" du cours dans Chamilo ou non.
* @return stdobject course est un objet que l'on retourne qui permet d'avoir l'id du cours que l'on vient de créer
**/
function import_documents($folderpath,$course,$userid){
global $DB;
$section=1; //organisation choisi empiriquement à la creation du cours: section 1 pour les documents section 2 pour les liens sections 3 pour les scorms
//On verifie que le dossiers documents existe bien
if (!(file_exists($folderpath) && is_readable($folderpath))) { //documents n'existe pas
echo "le fichier ".$folderpath." n'existe pas ou n'est pas lisible\n"; //pour les logs
}
else //documents existe
{
echo "On importe les documents inclus dans ".$folderpath."\n"; //pour les logs
//DONE : Amelioration : au lieu de faire un seul dossier documents qui contient tous les fichiers et documents, on regarde ce qu'il y a dans le dossier documents et on differencie dossier et fichier seul :
//Si on a un fichier on ajoute le fichier seul
//Si on a un dossier, on l'ajoute (avec tous ses fichiers et sous dossiers à l'interieur)
$documents= scandir($folderpath);
foreach ($documents as $document) {
//on ne traite pas . et ..
if($document === '.' || $document === '..'){continue;}
if(containDeleted($document)){continue;}//{echo $document." contient deleted \n";continue;}
//Si l'on a un fichier on ajoute un module de type fichier et le fichier
if(is_file($folderpath."/".$document))
{
//TODO eventuellement : créer une fonction add_resource et add_folder qui n'aura que les parametres requis en parametre (courseid, section, nom du fichier, chemin du fichier, userid)
$modulename="resource";//Pour ajouter un fichier
$moduleinfo = new stdClass();
$moduleinfo->modulename = $modulename;
$module= $DB->get_record('modules', array('name'=>$modulename), '*', MUST_EXIST);
$moduleinfo->module=$module->id;
$moduleinfo->section = $section; // This is the section number in the course. Not the section id in the database.
$moduleinfo->course = $course->id;
$moduleinfo->visible = true;
$moduleinfo->name=$document;
$moduleinfo = add_moduleinfo($moduleinfo, $course, null);
//Le module est crée, il peut contenir le fichier que l'on ajoute donc maintenant
$context = context_module::instance($moduleinfo->coursemodule);
$filerecord->contextid=$context->id;
$filerecord->component="mod_".$modulename;
$filerecord->filearea="content";
$filerecord->itemid=0;
$filerecord->sortorder=0;
$filerecord->filepath="/"; //"/";
$filerecord->filename=$document;
$filerecord->userid=$userid;
//on verifie que le fichier n'existe pas avant de l'enregistrer
$fs = get_file_storage();
if($fs->file_exists($filerecord->contextid, $filerecord->component, $filerecord->filearea,$filerecord->itemid, $filerecord->filepath, $filerecord->filename))
{
echo "le fichier ".$folderpath."/".$document." existe deja\n"; //pour les logs (peu probable mais bon..)
}
else
{
//echo "On enregistre le fichier : ".$folderpath."/".$document." existe deja\n"; //pour les logs (flood)
$stored_file=$fs->create_file_from_pathname($filerecord,$folderpath."/".$document);
//var_dump($stored_file);
}
}
//Si on a un repertoire et qu'il n'est pas vide, on importe le repetoire (avec fichiers et sous dossier)
if(is_dir($folderpath."/".$document) and !isEmptyDir($folderpath."/".$document))
{
//on evite d'importer les conversations ...
if($document==="chat_files")
{
echo "On n'importe pas les conversations \n"; //pour les logs
continue;
}
//On verifie ici que le dossier contient bien des documents, si il n'en contient pas on evite de crée un dossier vide :)
$files=find_all_files($folderpath."/".$document, "/");
if(empty($files))
{
echo "Le dossier ".$folderpath."/".$document." ne contient pas de fichiers \n"; //pour les logs
continue; //on ne crée pas le dossier et on passe au dossier suivant
}
//echo $folderpath."/".$document." est un dossier, on ajoute un module dossier et on l'y met dedans avec tout son contenu\n";
//Une fois que l'on sait que le fichier existe bien et est lisible, on commence par crée le "module" (dossier cette fois ci) qui va contenir le fichier dans le cours
$modulename="folder";//folder pour ajouter un dossier.
$moduleinfo = new stdClass();
$moduleinfo->modulename = $modulename;
$module= $DB->get_record('modules', array('name'=>$modulename), '*', MUST_EXIST);
$moduleinfo->module=$module->id;
$moduleinfo->section = $section; // This is the section number in the course. Not the section id in the database.
$moduleinfo->course = $course->id;
$moduleinfo->visible = true;
$moduleinfo->name=$document; // Ce name correspond au nom du dossier qui va contenir les fichier que l'on met en dedans.
$moduleinfo->display=1; //Il y a une option dans la table folder pour savoir si l'on affiche le dossier dans le cours ou sur une nouvelle page. 1 pour afficher dans le cours, 0 pour afficher dans une nouvelle page
$moduleinfo->showexpanded=0;//Empeche d'afficher le contenu des sous dossiers
$moduleinfo->showdownloadfolder=0; //Option qui permet d'afficher un bouton pour telecharger le dossier(1) ou non (0)
$moduleinfo = add_moduleinfo($moduleinfo, $course, null);
$context = context_module::instance($moduleinfo->coursemodule);
foreach ($files as $file) {
$pathfile=$file["Path"];
$filename=$file["File"];
$pathname=$folderpath."/".$document.$pathfile.$filename;
if(containDeleted($pathname)){continue;}
$filerecord->contextid=$context->id;
$filerecord->component="mod_".$modulename;
$filerecord->filearea="content";
$filerecord->itemid=0;
$filerecord->sortorder=0;
$filerecord->filepath=$pathfile; //"/";
$filerecord->filename=$filename;
$filerecord->userid=$userid;
//on verifie que le fichier n'existe pas avant de l'enregistrer
$fs = get_file_storage();
if($fs->file_exists($filerecord->contextid, $filerecord->component, $filerecord->filearea,$filerecord->itemid, $filerecord->filepath, $filerecord->filename))
{
echo "le fichier ".$pathname." existe deja \n"; //pour les logs (mais peu probable)
}
else
{
//echo "On enregistre le fichier : ".$pathname."\n"; //pour les logs (flood)
$stored_file=$fs->create_file_from_pathname($filerecord,$pathname);
}
}
}
}
}
}
//fonction qui crée un lien dans un cours
function import_liens($course,$url,$urlname){
global $DB;
//On commence par crée le "module" (fichier pour commencer, dossier un peu plus loin) qui va contenir le fichier dans le cours
$modulename="url";//Pour ajouter un lien.
$section=2; //organisation choisi empiriquement à la creation du cours: section 1 pour les documents section 2 pour les liens sections 3 pour les scorms
$moduleinfo = new stdClass();
$moduleinfo->modulename = $modulename;
$module= $DB->get_record('modules', array('name'=>$modulename), '*', MUST_EXIST);
$moduleinfo->module=$module->id;
$moduleinfo->section = $section; // This is the section number in the course. Not the section id in the database.
$moduleinfo->course = $course->id;
$moduleinfo->visible = true;
$moduleinfo->display=5;//type d'affichage : automatique, nouvelle fenetre, inclus dans moodle, dans notre cas : open pour ouvrir le lien lors du clique : RESOURCELIB_DISPLAY_OPEN
$moduleinfo->printintro=0;// on n'affiche pas l'intro dans
$moduleinfo->externalurl=$url;
$moduleinfo->name=$urlname;
$moduleinfo = add_moduleinfo($moduleinfo, $course, null);
}
//fonction qui ajoute des scorms dans un cours
function import_scorms($scormpath,$course,$userid){
global $DB,$nb_erreur;
$section=3 ; //organisation choisi empiriquement à la creation du cours: section 1 pour les documents section 2 pour les liens sections 3 pour les scorms
if(!is_dir($scormpath))
{
echo "Le dossier scorm n'existe pas dans ce cours\n";
}
else
{
echo "On importe les scorms inclus dans".$scormpath." \n";
//echo "le dossier scorm existe dans ce cours\n";
//le dossier existe on verifie si il contient des trucs
$scormfiles=scandir($scormpath);
foreach ($scormfiles as $scormfile) {
//on ne traite pas
if($scormfile=="." or $scormfile==".."){continue;}
$scormdirectory=$scormpath."/".$scormfile;
//si c'est un fichier, on verifie que c'est un zip (crée préalablement par la fonction moche "creer_zips" pour gerer les Scorms de type Dokeos qui ne sont pas dans le dossier scorm, contrairement a ceux de type Scorm et Chamilo qui sont deja présent(peu clair, voir "content_maker" de la table "c_lp" dans chamilo pour voir les differents type de scorms).
if(!is_dir($scormdirectory))
{
$info= new SplFileInfo($scormdirectory);
if($info->getExtension()!="zip")
{
continue;
}
else
{
$zipreturn=true; //on a bien un zip
$scormfile=$info->getBasename('.zip'); //pour avoir le nom du fichier sans l'extension .zip
$pathzip=$scormpath."/";
}
}
else //si on a un dossier, on en crée un zip à coté du script
{
//echo $scormdirectory." est un dossier\n";
//$destinationlocalzip="./".$scormfile.".zip";//on zip les dossiers dans le dossier du script
$destinationlocalzip=__DIR__."/".$scormfile.".zip";//on zip les dossiers dans le dossier du script
$zipreturn=Zip($scormdirectory."/", $destinationlocalzip);
$pathzip=__DIR__."/";
}
if($zipreturn)
{
//echo "Zip existant : ".$scormfile." , on ajoute maintenant le scorm dans le cours\n"; //pour les logs (flood/doublon)
//Module SCORM :
//Une fois que l'on sait que le fichier existe bien et est lisible, on commence par crée le "module" (dossier cette fois ci) qui va contenir le scorm dans le cours
$modulename="scorm";
$moduleinfo = new stdClass();
$moduleinfo->modulename = $modulename;
$module= $DB->get_record('modules', array('name'=>$modulename), '*', MUST_EXIST);
$moduleinfo->module=$module->id;
$moduleinfo->section = $section;
$moduleinfo->course = $course->id;
$moduleinfo->visible = true;
$moduleinfo->name=$scormfile; //nom du scorm qui apparait dans le cours
$moduleinfo->scormtype="local";
$moduleinfo->maxattempt=0; //nombre max de tentative : infini
$moduleinfo->intro=""; //Il n'y a pas l'air d'y avoir de texte d'intro du coté Chamilo à mettre ici, mais champs obligatoire
$moduleinfo->width = 100;
$moduleinfo->height = 500;
$moduleinfo->packagename=$scormfile.".zip";
$moduleinfo->packagepath=$pathzip;//la fonction zip enregistre les fichiers a coté du script, on les recupere donc directement ici
$moduleinfo->userid=$userid;
try{
$moduleinfo = add_moduleinfo($moduleinfo, $course, null); //va dans /mod/scorm/lib.php que j'ai modifié (et qui fait un echo pour les logs) (doublon avec le echo au dessus)
}catch(Exception $ex)
{
echo "Erreur : Echec lors de l'import de ".$scormfile."\n\n"; //pour les logs
$nb_erreur+=1;
}
unlink($destinationlocalzip); // on supprime le fichier si l'on vient de le crée à coté du zip
}
}
}
}
//dans chamilo les documents ne sont pas reelement supprimé, ils sont renommé en suffixant _DELETED suivi d'un numero
function containDeleted($file)
{
$pattern="#_DELETED_[0-9]+#";
/*
if(preg_match($pattern, $file))
{
echo $file." contient deleted\n"; //si on veut aussi log ca...
}
else
{
echo $file."ne contient pas deleted\n";
}
*/
return preg_match($pattern, $file);
}
function main(){
$scripttimestart = microtime(true); //pour les logs
echo "[INFO] Debut du script import_Chamilo_to_moodle \nDate du jour : ".date(DATE_RFC850)."\n"; //pour les logs
// fake-plugin
$string['pluginname'] = "Import Chamilo To Moodle";
global $Chamilo_dbo, $DB, $nb_erreur;
$nb_erreur=0;
//Informations necessaires pour la creation du cours
//Dans moodle, j'ai crée une categorie Chamilo expres pour contenir tous les cours de Chamilo
//!!!IL faudra refaire la meme operation sur le serveur de prod et mettre le bon numero de categorie ici :
$categoryid=1290;
//On recupere l'ensemble des cours : le code, le directory (correspond au dossier qui contient les données) et le title
$tab_cours=get_Chamilo_cours(); //TODO : rajouter un parametre optionnelle qui rajoute une condition au select si le parametre est non vide.
//On parcours tous les cours (si il y a besoin de faire un traitement seulement sur certains cours il faudra reduire la selection des cours dans get_Chamilo_cours())
foreach ($tab_cours as $cours) {
//$cours->code $cours->directory et $cours->title
//echo "\nTraitement du cours ".$cours->code."\n";
try{
get_teachers_id($cours); //Doit remplir la variable teachers_id du cours (et doit etre un array)
}
catch(Exception $e)
{
$nb_erreur+=1;
echo "Erreur : ".$cours->code." ".$e->getMessage()."\n\n"; //Pour les logs
continue;//on passe au cours suivant
}
if(count($cours->teachers_id)==0)
{
//echo $cours->code."\n"; $nb_erreur+=1;
}
try{
$course=creer_cours($categoryid,$cours); // Doit remplir la variable id du cours (un int)
}
catch(Exception $e)
{
$nb_erreur+=1;
echo "Erreur : ".$e->getMessage()."\n\n"; //pour les logs
continue;//on passe au cours suivant
}
echo "Le nouveau cours à pour id :".$cours->id."\n"; //Pour les logs
//maintenant que l'on a crée le cours il faut recuperer le dossier du cours Chamilo pour avoir les fichiers et les mettre dans le cours de moodle
$coursepath="/opt/fake_chamilo_mount/".$cours->directory."/"; //pour s'approcher de la structure que l'on aura : un chemin jusqu'au dossier du cours
$folderpath=$coursepath."document"; //les documents sont contenu dans le dossier documents du cours
import_documents($folderpath,$course,$userid[0]); //On attribut tous les uploads au 1er des enseignants ..
//on recupere les lien du cours
get_links($cours); //rempli l'attribut $cours->links
//Une fois que l'on a l'ensemble des liens, on les parcours et on appelle la fonction qui importe les liens (
foreach ($cours->links as $link) {
import_liens($course, $link->url, $link->title);
}
//Fonction (sale) creer_zip doit etre executer a part (une fois) avant sur le Chamilo qui a crée tuos les zip dans les dossiers scorm des cours de chamilo
$scormpath=$coursepath."scorm";
import_scorms($scormpath, $course, $userid[0]);
}
echo "Nombre d'erreur : ".$nb_erreur."\n\n"; //pour les logs
$scripttimeend = microtime(true);
$scripttime = $scripttimeend - $scripttimestart;
$page_load_time = number_format($scripttime, 3);
echo "\n[INFO] Fin du script à : ".date("H:i:s", $scripttimeend)."\n"; //pour les logs
echo "[INFO] Temps d'exécution : ".$page_load_time."s"."\n"; //pour les logs
}
// options CLI
list($options, $unrecognized) = cli_get_params(array('help'=>false), array('h'=>'help'));
if ($unrecognized) {
$unrecognized = implode("\n ", $unrecognized);
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
}
if ($options['help']) {
$help =
"Import des cours de Chamilo a moodle
Options:
-h, --help Affiche cette aide
Example:,
\$ sudo -u www-data /usr/bin/php import_Chamilo_to_moodle.php
";
echo $help;
die;
}
main();
//Fonction à ajouter dans le fichier /main/newscorm/learnpath.class.php sur le serveur chamilo, obtenu après quelques legeres modifications de la fonction scorm_export
public function scorm_export_md() {
global $_course;
//print_r($_course);
$course_id = api_get_course_int_id();
// Remove memory and time limits as much as possible as this might be a long process...
if (function_exists('ini_set')) {
api_set_memory_limit('128M');
ini_set('max_execution_time', 600);
}
// Create the zip handler (this will remain available throughout the method).
$archive_path = api_get_path(SYS_ARCHIVE_PATH);
// echo("\n archive path :");
// print_r($archive_path);
$sys_course_path = api_get_path(SYS_COURSE_PATH);
// echo("\n sys_course_path :");
// print_r($sys_course_path);
$current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
//echo("\n current_course_path :");
//print_r($current_course_path);
$temp_dir_short = uniqid();
// echo("\n temp_dir_short :");
// print_r($temp_dir_short);
//$temp_zip_dir = $archive_path.'/'.$temp_dir_short;
//echo("\n temp_zip_dir au depart :");
//print_r($temp_zip_dir);
$temp_zip_dir = $current_course_path.'/scorm'; //au depart : /var/www/chamilo/archive//5a3d1aef82713
//echo("\n temp_zip_dir apres modif :");
//print_r($temp_zip_dir);
//$temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
//echo("\n temp_zip_file au depart :");
//print_r($temp_zip_file);
$temp_zip_file=$temp_zip_dir.'/'.replace_dangerous_char($this->get_name()).'.zip'; //au depart : /var/www/chamilo/archive//5a3d1aef82713/b4039a108f40273bd302112835c4d55a.zip
//echo("\n temp_zip_file apres modif :");
//print_r($temp_zip_file);
//avant de crée le nouveau zip, on l'efface si il existe deja :
if(file_exists($temp_zip_file))
{
// echo "Le fichier ".$temp_zip_file." existe deja";
unlink($temp_zip_file);
}
$zip_folder = new PclZip($temp_zip_file);
//echo("\n zip_folder :");
//print_r($zip_folder);
$root_path = $main_path = api_get_path(SYS_PATH);
//echo("\n root_path :");
//print_r($root_path);
//echo "\n this get name : ";
//print_r(replace_dangerous_char($this->get_name()).'.zip');
$files_cleanup = array();
// Place to temporarily stash the zipfiles.
// create the temp dir if it doesn't exist
// or do a cleanup befor creating the zipfile.
if (!is_dir($temp_zip_dir)) {
mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
} else {
/* //on enleve pour ne pas supprimer les zips que je crée
// Cleanup: Check the temp dir for old files and delete them.
$handle = opendir($temp_zip_dir);
while (false !== ($file = readdir($handle))) {
if ($file != '.' && $file != '..') {
unlink("$temp_zip_dir/$file");
}
}
closedir($handle);
*/
}
$zip_files = $zip_files_abs = $zip_files_dist = array();
if (is_dir($current_course_path.'/scorm/'.$this->path) && is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')) {
// Remove the possible . at the end of the path.
$dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
$dest_path_to_scorm_folder = str_replace('//','/',$temp_zip_dir.'/scorm/'.$dest_path_to_lp);
mkdir($dest_path_to_scorm_folder, api_get_permissions_for_new_directories(), true);
$zip_files_dist = copyr($current_course_path.'/scorm/'.$this->path, $dest_path_to_scorm_folder, array('imsmanifest'), $zip_files);
}
// Build a dummy imsmanifest structure. Do not add to the zip yet (we still need it).
// This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
// Aggregation Model official document, secion "2.3 Content Packaging".
$xmldoc = new DOMDocument('1.0'); // We are going to build a UTF-8 encoded manifest. Later we will recode it to the desired (and supported) encoding.
$root = $xmldoc->createElement('manifest');
$root->setAttribute('identifier', 'SingleCourseManifest');
$root->setAttribute('version', '1.1');
$root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
$root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
$root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
$root->setAttribute('xsi:schemaLocation', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd');
// Build mandatory sub-root container elements.
$metadata = $xmldoc->createElement('metadata');
$md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
$metadata->appendChild($md_schema);
$md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
$metadata->appendChild($md_schemaversion);
$root->appendChild($metadata);
$organizations = $xmldoc->createElement('organizations');
$resources = $xmldoc->createElement('resources');
// Build the only organization we will use in building our learnpaths.
$organizations->setAttribute('default', 'chamilo_scorm_export');
$organization = $xmldoc->createElement('organization');
$organization->setAttribute('identifier', 'chamilo_scorm_export');
// To set the title of the SCORM entity (=organization), we take the name given
// in Chamilo and convert it to HTML entities using the Chamilo charset (not the
// learning path charset) as it is the encoding that defines how it is stored
// in the database. Then we convert it to HTML entities again as the "&" character
// alone is not authorized in XML (must be &amp;).
// The title is then decoded twice when extracting (see scorm::parse_manifest).
$org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
$organization->appendChild($org_title);
$folder_name = 'document';
// Removes the learning_path/scorm_folder path when exporting see #4841
$path_to_remove = null;
$result = $this->generate_lp_folder($_course);
if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
$path_to_remove = 'document'.$result['dir'];
$path_to_replace = $folder_name.'/';
}
//Fixes chamilo scorm exports
if ($this->ref == 'chamilo_scorm_export') {
$path_to_remove = 'scorm/'.$this->path.'/document/';
}
// For each element, add it to the imsmanifest structure, then add it to the zip.
// Always call the learnpathItem->scorm_export() method to change it to the SCORM format.
$link_updates = array();
foreach ($this->items as $index => $item) {
if (!in_array($item->type, array(TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION))) {
// Get included documents from this item.
if ($item->type == 'sco')
$inc_docs = $item->get_resources_from_source(null, api_get_path(SYS_COURSE_PATH).api_get_course_path().'/'.'scorm/'.$this->path.'/'.$item->get_path());
else
$inc_docs = $item->get_resources_from_source();
// Give a child element <item> to the <organization> element.
$my_item_id = $item->get_id();
$my_item = $xmldoc->createElement('item');
$my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
$my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
$my_item->setAttribute('isvisible', 'true');
// Give a child element <title> to the <item> element.
$my_title = $xmldoc->createElement('title', htmlspecialchars(api_utf8_encode($item->get_title()), ENT_QUOTES, 'UTF-8'));
$my_item->appendChild($my_title);
// Give a child element <adlcp:prerequisites> to the <item> element.
$my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $this->get_scorm_prereq_string($my_item_id));
$my_prereqs->setAttribute('type', 'aicc_script');
$my_item->appendChild($my_prereqs);
// Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
//$xmldoc->createElement('adlcp:maxtimeallowed','');
// Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
//$xmldoc->createElement('adlcp:timelimitaction','');
// Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
//$xmldoc->createElement('adlcp:datafromlms','');
// Give a child element <adlcp:masteryscore> to the <item> element.
$my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
$my_item->appendChild($my_masteryscore);
// Attach this item to the organization element or hits parent if there is one.
if (!empty($item->parent) && $item->parent != 0) {
$children = $organization->childNodes;
$possible_parent = &$this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
if (is_object($possible_parent)) {
$possible_parent->appendChild($my_item);
} else {
if ($this->debug > 0) { error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found'); }
}
} else {
if ($this->debug > 0) { error_log('No parent'); }
$organization->appendChild($my_item);
}
// Get the path of the file(s) from the course directory root.
$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
if (!empty($path_to_remove)) {
//From docs
$my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
//From quiz
if ($this->ref == 'chamilo_scorm_export') {
$path_to_remove = 'scorm/'.$this->path.'/';
$my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
}
} else {
$my_xml_file_path = $my_file_path;
}
$my_sub_dir = dirname($my_file_path);
$my_sub_dir = str_replace('\\', '/', $my_sub_dir);
//$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_QUOTES, 'UTF-8');
$my_xml_sub_dir = $my_sub_dir;
// Give a <resource> child to the <resources> element
$my_resource = $xmldoc->createElement('resource');
$my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
$my_resource->setAttribute('type', 'webcontent');
$my_resource->setAttribute('href', $my_xml_file_path);
// adlcp:scormtype can be either 'sco' or 'asset'.
if ($item->type == 'sco') {
$my_resource->setAttribute('adlcp:scormtype', 'sco');
} else {
$my_resource->setAttribute('adlcp:scormtype', 'asset');
}
// xml:base is the base directory to find the files declared in this resource.
$my_resource->setAttribute('xml:base', '');
// Give a <file> child to the <resource> element.
$my_file = $xmldoc->createElement('file');
$my_file->setAttribute('href', $my_xml_file_path);
$my_resource->appendChild($my_file);
// Dependency to other files - not yet supported.
$i = 1;
foreach ($inc_docs as $doc_info) {
if (count($doc_info) < 1 || empty($doc_info[0])) { continue; }
$my_dep = $xmldoc->createElement('resource');
$res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
$my_dep->setAttribute('identifier', $res_id);
$my_dep->setAttribute('type', 'webcontent');
$my_dep->setAttribute('adlcp:scormtype', 'asset');
$my_dep_file = $xmldoc->createElement('file');
// Check type of URL.
//error_log(__LINE__.'Now dealing with '.$doc_info[0].' of type '.$doc_info[1].'-'.$doc_info[2], 0);
if ($doc_info[1] == 'remote') {
// Remote file. Save url as is.
$my_dep_file->setAttribute('href', $doc_info[0]);
$my_dep->setAttribute('xml:base', '');
} elseif ($doc_info[1] == 'local') {
switch ($doc_info[2]) {
case 'url': // Local URL - save path as url for now, don't zip file.
$abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
$current_dir = dirname($abs_path);
$current_dir = str_replace('\\', '/', $current_dir);
$file_path = realpath($abs_path);
$file_path = str_replace('\\', '/', $file_path);
$my_dep_file->setAttribute('href', $file_path);
$my_dep->setAttribute('xml:base', '');
if (strstr($file_path, $main_path) !== false) {
// The calculated real path is really inside Chamilo's root path.
// Reduce file path to what's under the DocumentRoot.
$file_path = substr($file_path, strlen($root_path) - 1);
//echo $file_path;echo '<br /><br />';
//error_log(__LINE__.'Reduced url path: '.$file_path, 0);
$zip_files_abs[] = $file_path;
$link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path);
$my_dep_file->setAttribute('href', $file_path);
$my_dep->setAttribute('xml:base', '');
} elseif (empty($file_path)) {
/*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH)));
if (strpos($document_root, -1) == '/') {
$document_root = substr(0, -1, $document_root);
}*/
$file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
$file_path = str_replace('//', '/', $file_path);
if (file_exists($file_path)) {
$file_path = substr($file_path, strlen($current_dir)); // We get the relative path.
$zip_files[] = $my_sub_dir.'/'.$file_path;
$link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path);
$my_dep_file->setAttribute('href', $file_path);
$my_dep->setAttribute('xml:base', '');
}
}
break;
case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
$my_dep_file->setAttribute('href', $doc_info[0]);
$my_dep->setAttribute('xml:base', '');
//$current_dir = str_replace('\\', '/', dirname($current_course_path.'/'.$item->get_file_path())).'/';
// The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
// an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
$abs_img_path_without_subdir = $doc_info[0];
$relp = api_get_path(REL_PATH); // The url-append config param.
$pos = strpos($abs_img_path_without_subdir, $relp);
if ($pos === 0) {
$abs_img_path_without_subdir = '/'.substr($abs_img_path_without_subdir, strlen($relp));
}
//$file_path = realpath(api_get_path(SYS_PATH).$doc_info[0]);
$file_path = realpath(api_get_path(SYS_PATH).$abs_img_path_without_subdir);
$file_path = str_replace('\\', '/', $file_path);
$file_path = str_replace('//', '/', $file_path);
//error_log(__LINE__.'Abs path: '.$file_path, 0);
// Prepare the current directory path (until just under 'document') with a trailing slash.
$cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
// Check if the current document is in that path.
if (strstr($file_path, $cur_path) !== false) {
// The document is in that path, now get the relative path
// to the containing document.
$orig_file_path = dirname($cur_path.$my_file_path).'/';
$orig_file_path = str_replace('\\', '/', $orig_file_path);
$relative_path = '';
if (strstr($file_path, $cur_path) !== false) {
$relative_path = substr($file_path, strlen($orig_file_path));
$file_path = substr($file_path, strlen($cur_path));
} else {
// This case is still a problem as it's difficult to calculate a relative path easily
// might still generate wrong links.
//$file_path = substr($file_path,strlen($cur_path));
// Calculate the directory path to the current file (without trailing slash).
$my_relative_path = dirname($file_path);
$my_relative_path = str_replace('\\', '/', $my_relative_path);
$my_relative_file = basename($file_path);
// Calculate the directory path to the containing file (without trailing slash).
$my_orig_file_path = substr($orig_file_path, 0, -1);
$dotdot = '';
$subdir = '';
while (strstr($my_relative_path, $my_orig_file_path) === false && (strlen($my_orig_file_path) > 1) && (strlen($my_relative_path) > 1)) {
$my_relative_path2 = dirname($my_relative_path);
$my_relative_path2 = str_replace('\\', '/', $my_relative_path2);
$my_orig_file_path = dirname($my_orig_file_path);
$my_orig_file_path = str_replace('\\', '/', $my_orig_file_path);
$subdir = substr($my_relative_path, strlen($my_relative_path2) + 1).'/'.$subdir;
$dotdot += '../';
$my_relative_path = $my_relative_path2;
}
$relative_path = $dotdot.$subdir.$my_relative_file;
}
// Put the current document in the zip (this array is the array
// that will manage documents already in the course folder - relative).
$zip_files[] = $file_path;
// Update the links to the current document in the containing document (make them relative).
$link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $relative_path);
$my_dep_file->setAttribute('href', $file_path);
$my_dep->setAttribute('xml:base', '');
} elseif (strstr($file_path, $main_path) !== false) {
// The calculated real path is really inside Chamilo's root path.
// Reduce file path to what's under the DocumentRoot.
$file_path = substr($file_path, strlen($root_path));
//echo $file_path;echo '<br /><br />';
//error_log('Reduced path: '.$file_path, 0);
$zip_files_abs[] = $file_path;
$link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path);
$my_dep_file->setAttribute('href', 'document/'.$file_path);
$my_dep->setAttribute('xml:base', '');
} elseif (empty($file_path)) {
/*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH)));
if(strpos($document_root,-1) == '/') {
$document_root = substr(0, -1, $document_root);
}*/
$file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0];
$file_path = str_replace('//', '/', $file_path);
if (file_exists($file_path)) {
$file_path = substr($file_path,strlen($current_dir)); // We get the relative path.
$zip_files[] = $my_sub_dir.'/'.$file_path;
$link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path);
$my_dep_file->setAttribute('href','document/'.$file_path);
$my_dep->setAttribute('xml:base', '');
}
}
break;
case 'rel': // Path relative to the current document. Save xml:base as current document's directory and save file in zip as subdir.file_path
if (substr($doc_info[0], 0, 2) == '..') {
// Relative path going up.
$current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
$current_dir = str_replace('\\', '/', $current_dir);
$file_path = realpath($current_dir.$doc_info[0]);
$file_path = str_replace('\\', '/', $file_path);
//error_log($file_path.' <-> '.$main_path,0);
if (strstr($file_path, $main_path) !== false) {
// The calculated real path is really inside Chamilo's root path.
// Reduce file path to what's under the DocumentRoot.
$file_path = substr($file_path, strlen($root_path));
//error_log('Reduced path: '.$file_path, 0);
$zip_files_abs[] = $file_path;
$link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path);
$my_dep_file->setAttribute('href', 'document/'.$file_path);
$my_dep->setAttribute('xml:base', '');
}
} else {
$zip_files[] = $my_sub_dir.'/'.$doc_info[0];
$my_dep_file->setAttribute('href', $doc_info[0]);
$my_dep->setAttribute('xml:base', $my_xml_sub_dir);
}
break;
default:
$my_dep_file->setAttribute('href', $doc_info[0]);
$my_dep->setAttribute('xml:base', '');
break;
}
}
$my_dep->appendChild($my_dep_file);
$resources->appendChild($my_dep);
$dependency = $xmldoc->createElement('dependency');
$dependency->setAttribute('identifierref', $res_id);
$my_resource->appendChild($dependency);
$i++;
}
//$my_dependency = $xmldoc->createElement('dependency');
//$my_dependency->setAttribute('identifierref', '');
$resources->appendChild($my_resource);
$zip_files[] = $my_file_path;
//error_log('File '.$my_file_path. ' added to $zip_files', 0);
} else {
// If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
switch ($item->type) {
case TOOL_LINK:
$my_item = $xmldoc->createElement('item');
$my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
$my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
$my_item->setAttribute('isvisible', 'true');
// Give a child element <title> to the <item> element.
$my_title = $xmldoc->createElement('title', htmlspecialchars(api_utf8_encode($item->get_title()), ENT_QUOTES, 'UTF-8'));
$my_item->appendChild($my_title);
// Give a child element <adlcp:prerequisites> to the <item> element.
$my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
$my_prereqs->setAttribute('type', 'aicc_script');
$my_item->appendChild($my_prereqs);
// Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
//$xmldoc->createElement('adlcp:maxtimeallowed', '');
// Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
//$xmldoc->createElement('adlcp:timelimitaction', '');
// Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
//$xmldoc->createElement('adlcp:datafromlms', '');
// Give a child element <adlcp:masteryscore> to the <item> element.
$my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
$my_item->appendChild($my_masteryscore);
// Attach this item to the organization element or its parent if there is one.
if (!empty($item->parent) && $item->parent != 0) {
$children = $organization->childNodes;
for ($i = 0; $i < $children->length; $i++) {
$item_temp = $children->item($i);
if ($item_temp -> nodeName == 'item') {
if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
$item_temp -> appendChild($my_item);
}
}
}
} else {
$organization->appendChild($my_item);
}
$my_file_path = 'link_'.$item->get_id().'.html';
$sql = 'SELECT url, title FROM '.Database :: get_course_table(TABLE_LINK).' WHERE c_id = '.$course_id.' AND id='.$item->path;
$rs = Database::query($sql);
if ($link = Database :: fetch_array($rs)) {
$url = $link['url'];
$title = stripslashes($link['title']);
$links_to_create[$my_file_path] = array('title' => $title, 'url' => $url);
//$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_QUOTES, 'UTF-8');
$my_xml_file_path = $my_file_path;
$my_sub_dir = dirname($my_file_path);
$my_sub_dir = str_replace('\\', '/', $my_sub_dir);
//$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_QUOTES, 'UTF-8');
$my_xml_sub_dir = $my_sub_dir;
// Give a <resource> child to the <resources> element.
$my_resource = $xmldoc->createElement('resource');
$my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
$my_resource->setAttribute('type', 'webcontent');
$my_resource->setAttribute('href', $my_xml_file_path);
// adlcp:scormtype can be either 'sco' or 'asset'.
$my_resource->setAttribute('adlcp:scormtype', 'asset');
// xml:base is the base directory to find the files declared in this resource.
$my_resource->setAttribute('xml:base', '');
// give a <file> child to the <resource> element.
$my_file = $xmldoc->createElement('file');
$my_file->setAttribute('href', $my_xml_file_path);
$my_resource->appendChild($my_file);
$resources->appendChild($my_resource);
}
break;
case TOOL_QUIZ:
require_once api_get_path(SYS_CODE_PATH).'exercice/exercise.class.php';
$exe_id = $item->path; // Should be using ref when everything will be cleaned up in this regard.
$exe = new Exercise();
$exe->read($exe_id);
$my_item = $xmldoc->createElement('item');
$my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
$my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
$my_item->setAttribute('isvisible', 'true');
// Give a child element <title> to the <item> element.
$my_title = $xmldoc->createElement('title', htmlspecialchars(api_utf8_encode($item->get_title()), ENT_QUOTES, 'UTF-8'));
$my_item->appendChild($my_title);
$my_max_score = $xmldoc->createElement('max_score', $item->get_max());
//$my_item->appendChild($my_max_score);
// Give a child element <adlcp:prerequisites> to the <item> element.
$my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
$my_prereqs->setAttribute('type','aicc_script');
$my_item->appendChild($my_prereqs);
// Give a child element <adlcp:masteryscore> to the <item> element.
$my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
$my_item->appendChild($my_masteryscore);
// Attach this item to the organization element or hits parent if there is one.
if (!empty($item->parent) && $item->parent != 0) {
$children = $organization->childNodes;
for ($i = 0; $i < $children->length; $i++) {
$item_temp = $children->item($i);
if ($item_temp -> nodeName == 'item') {
if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
$item_temp -> appendChild($my_item);
}
}
}
} else {
$organization->appendChild($my_item);
}
// Include export scripts.
require_once api_get_path(SYS_CODE_PATH).'exercice/export/scorm/scorm_export.php';
// Get the path of the file(s) from the course directory root
//$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
$my_file_path = 'quiz_'.$item->get_id().'.html';
// Write the contents of the exported exercise into a (big) html file
// to later pack it into the exported SCORM. The file will be removed afterwards.
$contents = export_exercise($exe_id, true);
$tmp_file_path = $archive_path.$temp_dir_short.'/'.$my_file_path;
$res = file_put_contents($tmp_file_path, $contents);
if ($res === false) { error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0); }
$files_cleanup[] = $tmp_file_path;
//error_log($tmp_path); die();
//$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_QUOTES, 'UTF-8');
$my_xml_file_path = $my_file_path;
$my_sub_dir = dirname($my_file_path);
$my_sub_dir = str_replace('\\', '/', $my_sub_dir);
//$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_QUOTES, 'UTF-8');
$my_xml_sub_dir = $my_sub_dir;
// Give a <resource> child to the <resources> element.
$my_resource = $xmldoc->createElement('resource');
$my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
$my_resource->setAttribute('type', 'webcontent');
$my_resource->setAttribute('href', $my_xml_file_path);
// adlcp:scormtype can be either 'sco' or 'asset'.
$my_resource->setAttribute('adlcp:scormtype', 'sco');
// xml:base is the base directory to find the files declared in this resource.
$my_resource->setAttribute('xml:base', '');
// Give a <file> child to the <resource> element.
$my_file = $xmldoc->createElement('file');
$my_file->setAttribute('href', $my_xml_file_path);
$my_resource->appendChild($my_file);
// Get included docs.
$inc_docs = $item->get_resources_from_source(null,$tmp_file_path);
// Dependency to other files - not yet supported.
$i = 1;
foreach ($inc_docs as $doc_info) {
if (count($doc_info) < 1 || empty($doc_info[0])) { continue; }
$my_dep = $xmldoc->createElement('resource');
$res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
$my_dep->setAttribute('identifier', $res_id);
$my_dep->setAttribute('type', 'webcontent');
$my_dep->setAttribute('adlcp:scormtype', 'asset');
$my_dep_file = $xmldoc->createElement('file');
// Check type of URL.
//error_log(__LINE__.'Now dealing with '.$doc_info[0].' of type '.$doc_info[1].'-'.$doc_info[2], 0);
if ($doc_info[1] == 'remote') {
// Remote file. Save url as is.
$my_dep_file->setAttribute('href', $doc_info[0]);
$my_dep->setAttribute('xml:base', '');
} elseif ($doc_info[1] == 'local') {
switch ($doc_info[2]) {
case 'url': // Local URL - save path as url for now, don't zip file.
// Save file but as local file (retrieve from URL).
$abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
$current_dir = dirname($abs_path);
$current_dir = str_replace('\\', '/', $current_dir);
$file_path = realpath($abs_path);
$file_path = str_replace('\\', '/', $file_path);
$my_dep_file->setAttribute('href', 'document/'.$file_path);
$my_dep->setAttribute('xml:base', '');
if (strstr($file_path, $main_path) !== false) {
// The calculated real path is really inside the chamilo root path.
// Reduce file path to what's under the DocumentRoot.
$file_path = substr($file_path, strlen($root_path));
//echo $file_path;echo '<br /><br />';
//error_log('Reduced path: '.$file_path, 0);
$zip_files_abs[] = $file_path;
$link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => 'document/'.$file_path);
$my_dep_file->setAttribute('href', 'document/'.$file_path);
$my_dep->setAttribute('xml:base', '');
} elseif (empty($file_path)) {
/*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH),api_get_path(REL_PATH)));
if (strpos($document_root,-1) == '/') {
$document_root = substr(0, -1, $document_root);
}*/
$file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
$file_path = str_replace('//', '/', $file_path);
if (file_exists($file_path)) {
$file_path = substr($file_path, strlen($current_dir)); // We get the relative path.
$zip_files[] = $my_sub_dir.'/'.$file_path;
$link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => 'document/'.$file_path);
$my_dep_file->setAttribute('href', 'document/'.$file_path);
$my_dep->setAttribute('xml:base', '');
}
}
break;
case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
$current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
$current_dir = str_replace('\\', '/', $current_dir);
$file_path = realpath($doc_info[0]);
$file_path = str_replace('\\', '/', $file_path);
$my_dep_file->setAttribute('href', $file_path);
$my_dep->setAttribute('xml:base', '');
if (strstr($file_path,$main_path) !== false) {
// The calculated real path is really inside the chamilo root path.
// Reduce file path to what's under the DocumentRoot.
$file_path = substr($file_path, strlen($root_path));
//echo $file_path;echo '<br /><br />';
//error_log('Reduced path: '.$file_path, 0);
$zip_files_abs[] = $file_path;
$link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path);
$my_dep_file->setAttribute('href', 'document/'.$file_path);
$my_dep->setAttribute('xml:base', '');
} elseif (empty($file_path)) {
/*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH)));
if (strpos($document_root,-1) == '/') {
$document_root = substr(0, -1, $document_root);
}*/
$file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0];
$file_path = str_replace('//', '/', $file_path);
if (file_exists($file_path)) {
$file_path = substr($file_path,strlen($current_dir)); // We get the relative path.
$zip_files[] = $my_sub_dir.'/'.$file_path;
$link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path);
$my_dep_file->setAttribute('href', 'document/'.$file_path);
$my_dep->setAttribute('xml:base', '');
}
}
break;
case 'rel': // Path relative to the current document. Save xml:base as current document's directory and save file in zip as subdir.file_path
if (substr($doc_info[0], 0, 2) == '..') {
// Relative path going up.
$current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
$current_dir = str_replace('\\', '/', $current_dir);
$file_path = realpath($current_dir.$doc_info[0]);
$file_path = str_replace('\\', '/', $file_path);
//error_log($file_path.' <-> '.$main_path, 0);
if (strstr($file_path, $main_path) !== false) {
// The calculated real path is really inside Chamilo's root path.
// Reduce file path to what's under the DocumentRoot.
$file_path = substr($file_path, strlen($root_path));
$file_path_dest = $file_path;
// File path is courses/CHAMILO/document/....
$info_file_path = explode('/', $file_path);
if ($info_file_path[0] == 'courses') { // Add character "/" in file path.
$file_path_dest = 'document/'.$file_path;
}
//error_log('Reduced path: '.$file_path, 0);
$zip_files_abs[] = $file_path;
$link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path_dest);
$my_dep_file->setAttribute('href', 'document/'.$file_path);
$my_dep->setAttribute('xml:base', '');
}
} else {
$zip_files[] = $my_sub_dir.'/'.$doc_info[0];
$my_dep_file->setAttribute('href', $doc_info[0]);
$my_dep->setAttribute('xml:base', $my_xml_sub_dir);
}
break;
default:
$my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
$my_dep->setAttribute('xml:base', '');
break;
}
}
$my_dep->appendChild($my_dep_file);
$resources->appendChild($my_dep);
$dependency = $xmldoc->createElement('dependency');
$dependency->setAttribute('identifierref', $res_id);
$my_resource->appendChild($dependency);
$i++;
}
$resources->appendChild($my_resource);
$zip_files[] = $my_file_path;
break;
default:
// Get the path of the file(s) from the course directory root
$my_file_path = 'non_exportable.html';
//$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
$my_xml_file_path = $my_file_path;
$my_sub_dir = dirname($my_file_path);
$my_sub_dir = str_replace('\\', '/', $my_sub_dir);
//$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
$my_xml_sub_dir = $my_sub_dir;
// Give a <resource> child to the <resources> element.
$my_resource = $xmldoc->createElement('resource');
$my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
$my_resource->setAttribute('type', 'webcontent');
$my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
// adlcp:scormtype can be either 'sco' or 'asset'.
$my_resource->setAttribute('adlcp:scormtype', 'asset');
// xml:base is the base directory to find the files declared in this resource.
$my_resource->setAttribute('xml:base', '');
// Give a <file> child to the <resource> element.
$my_file = $xmldoc->createElement('file');
$my_file->setAttribute('href', 'document/'.$my_xml_file_path);
$my_resource->appendChild($my_file);
$resources->appendChild($my_resource);
break;
}
}
}
$organizations->appendChild($organization);
$root->appendChild($organizations);
$root->appendChild($resources);
$xmldoc->appendChild($root);
// TODO: Add a readme file here, with a short description and a link to the Reload player
// then add the file to the zip, then destroy the file (this is done automatically).
// http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
//error_log(print_r($zip_files,true), 0);
foreach ($zip_files as $file_path) {
if (empty($file_path)) { continue; }
//error_log(__LINE__.'getting document from '.$sys_course_path.$_course['path'].'/'.$file_path.' removing '.$sys_course_path.$_course['path'].'/',0);
$dest_file = $archive_path.$temp_dir_short.'/'.$file_path;
$this->create_path($dest_file);
//error_log('copy '.api_get_path(SYS_COURSE_PATH).$_course['path'].'/'.$file_path.' to '.api_get_path(SYS_ARCHIVE_PATH).$temp_dir_short.'/'.$file_path,0);
//echo $main_path.$file_path.'<br />';
@copy($sys_course_path.$_course['path'].'/'.$file_path, $dest_file);
// Check if the file needs a link update.
if (in_array($file_path, array_keys($link_updates))) {
$string = file_get_contents($dest_file);
unlink($dest_file);
foreach ($link_updates[$file_path] as $old_new) {
//error_log('Replacing '.$old_new['orig'].' by '.$old_new['dest'].' in '.$file_path, 0);
// This is an ugly hack that allows .flv files to be found by the flv player that
// will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
// to find the flv to play in document/main/, so we replace main/ in the flv path by
// ../../.. to return from inc/lib/flv_player to the document/main path.
if (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 5) == 'main/') {
$old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
} elseif (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 6) == 'video/') {
$old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
}
//Fix to avoid problems with default_course_document
if (strpos("main/default_course_document", $old_new['dest'] === false)) {
$new_dest = str_replace('document/', $mult.'document/', $old_new['dest']);
} else {
//$new_dest = str_replace('main/default_course_document', $mult.'document/main/default_course_document', $old_new['dest']);
$new_dest = $old_new['dest'];
}
//$string = str_replace($old_new['orig'], $old_new['dest'], $string);
$string = str_replace($old_new['orig'], $new_dest, $string);
//Add files inside the HTMLs
$new_path = str_replace('/courses/', '', $old_new['orig']);
//var_dump($sys_course_path.$new_path); var_dump($archive_path.$temp_dir_short.'/'.$old_new['dest']); echo '---';
if (file_exists($sys_course_path.$new_path)) {
copy($sys_course_path.$new_path, $archive_path.$temp_dir_short.'/'.$old_new['dest']);
}
}
file_put_contents($dest_file, $string);
}
}
foreach ($zip_files_abs as $file_path) {
if (empty($file_path)) { continue; }
//error_log(__LINE__.'checking existence of '.$main_path.$file_path.'', 0);
if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) { continue; }
//error_log(__LINE__.'getting document from '.$main_path.$file_path.' removing '.api_get_path(SYS_COURSE_PATH).$_course['path'].'/', 0);
$dest_file = $archive_path.$temp_dir_short.'/document/'.$file_path;
$this->create_path($dest_file);
//error_log('Created path '.api_get_path(SYS_ARCHIVE_PATH).$temp_dir_short.'/document/'.$file_path, 0);
//error_log('copy '.api_get_path(SYS_COURSE_PATH).$_course['path'].'/'.$file_path.' to '.api_get_path(SYS_ARCHIVE_PATH).$temp_dir_short.'/'.$file_path, 0);
//echo $main_path.$file_path.' - '.$dest_file.'<br />';
copy($main_path.$file_path, $dest_file);
// Check if the file needs a link update.
if (in_array($file_path, array_keys($link_updates))) {
$string = file_get_contents($dest_file);
unlink($dest_file);
foreach ($link_updates[$file_path] as $old_new) {
//error_log('Replacing '.$old_new['orig'].' by '.$old_new['dest'].' in '.$file_path, 0);
// This is an ugly hack that allows .flv files to be found by the flv player that
// will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
// to find the flv to play in document/main/, so we replace main/ in the flv path by
// ../../.. to return from inc/lib/flv_player to the document/main path.
if (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 5) == 'main/') {
$old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
}
$string = str_replace($old_new['orig'], $old_new['dest'], $string);
}
file_put_contents($dest_file, $string);
}
}
if (is_array($links_to_create)) {
foreach ($links_to_create as $file => $link) {
$file_content = '<!DOCTYPE html>
<head>
<meta charset="'.api_get_language_isocode().'" />
<title>'.$link['title'].'</title>
</head>
<body dir="'.api_get_text_direction().'">
<div style="text-align:center">
<a href="'.$link['url'].'">'.$link['title'].'</a></div>
</body>
</html>';
file_put_contents($archive_path.$temp_dir_short.'/'.$file, $file_content);
}
}
// Add non exportable message explanation.
$lang_not_exportable = get_lang('ThisItemIsNotExportable');
$file_content = '<!DOCTYPE html>
<head>
<meta charset="'.api_get_language_isocode().'" />
<title>'.$lang_not_exportable.'</title>
<meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
</head>
<body dir="'.api_get_text_direction().'">';
$file_content .=
<<<EOD
<style>
.error-message {
font-family: arial, verdana, helvetica, sans-serif;
border-width: 1px;
border-style: solid;
left: 50%;
margin: 10px auto;
min-height: 30px;
padding: 5px;
right: 50%;
width: 500px;
background-color: #FFD1D1;
border-color: #FF0000;
color: #000;
}
</style>
<body>
<div class="error-message">
$lang_not_exportable
</div>
</body>
</html>
EOD;
if (!is_dir($archive_path.$temp_dir_short.'/document')) {
@mkdir($archive_path.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
}
file_put_contents($archive_path.$temp_dir_short.'/document/non_exportable.html', $file_content);
// Add the extra files that go along with a SCORM package.
$main_code_path = api_get_path(SYS_CODE_PATH).'newscorm/packaging/';
$extra_files = scandir($main_code_path);
foreach ($extra_files as $extra_file) {
if (strpos($extra_file, '.') === 0)
continue;
else {
$dest_file = $archive_path . $temp_dir_short . '/' . $extra_file;
$this->create_path($dest_file);
copy($main_code_path.$extra_file, $dest_file);
}
}
// Finalize the imsmanifest structure, add to the zip, then return the zip.
$manifest = @$xmldoc->saveXML();
$manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
file_put_contents($archive_path.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
$zip_folder->add($archive_path.'/'.$temp_dir_short, PCLZIP_OPT_REMOVE_PATH, $archive_path.'/'.$temp_dir_short.'/');
// Clean possible temporary files.
foreach ($files_cleanup as $file) {
$res = unlink($file);
if ($res === false) { error_log('Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__, 0); }
}
// Send file to client.
//$name = 'scorm_export_'.$this->lp_id.'.zip';
require_once api_get_path(LIBRARY_PATH).'fileUpload.lib.php';
$name = replace_dangerous_char($this->get_name()).'.zip';
DocumentManager::file_send_for_download($temp_zip_file, true, $name);
}
//Il faut modifier la fonction suivante du fichier /mod/scorm/lib.php (fonction debute ligne ~78, modification debute ligne ~143)
//La seule modification est un ajout qui est délimité par des commentaires (chercher MD_)
/**
* Given an object containing all the necessary data,
* (defined by the form in mod_form.php) this function
* will create a new instance and return the id number
* of the new instance.
*
* @global stdClass
* @global object
* @uses CONTEXT_MODULE
* @uses SCORM_TYPE_LOCAL
* @uses SCORM_TYPE_LOCALSYNC
* @uses SCORM_TYPE_EXTERNAL
* @param object $scorm Form data
* @param object $mform
* @return int new instance id
*/
function scorm_add_instance($scorm, $mform=null) {
global $CFG, $DB;
require_once($CFG->dirroot.'/mod/scorm/locallib.php');
if (empty($scorm->timeopen)) {
$scorm->timeopen = 0;
}
if (empty($scorm->timeclose)) {
$scorm->timeclose = 0;
}
if (empty($scorm->completionstatusallscos)) {
$scorm->completionstatusallscos = 0;
}
$cmid = $scorm->coursemodule;
$cmidnumber = $scorm->cmidnumber;
$courseid = $scorm->course;
$context = context_module::instance($cmid);
$scorm = scorm_option2text($scorm);
$scorm->width = (int)str_replace('%', '', $scorm->width);
$scorm->height = (int)str_replace('%', '', $scorm->height);
if (!isset($scorm->whatgrade)) {
$scorm->whatgrade = 0;
}
$id = $DB->insert_record('scorm', $scorm);
// Update course module record - from now on this instance properly exists and all function may be used.
$DB->set_field('course_modules', 'instance', $id, array('id' => $cmid));
// Reload scorm instance.
$record = $DB->get_record('scorm', array('id' => $id));
// Store the package and verify.
if ($record->scormtype === SCORM_TYPE_LOCAL) {
if (!empty($scorm->packagefile)) {
$fs = get_file_storage();
$fs->delete_area_files($context->id, 'mod_scorm', 'package');
file_save_draft_area_files($scorm->packagefile, $context->id, 'mod_scorm', 'package',
0, array('subdirs' => 0, 'maxfiles' => 1));
// Get filename of zip that was uploaded.
$files = $fs->get_area_files($context->id, 'mod_scorm', 'package', 0, '', false);
$file = reset($files);
$filename = $file->get_filename();
if ($filename !== false) {
$record->reference = $filename;
}
}
//MD_: ajout pour pouvoir faire du create_file_from_pathname : packagename contient le nom du zip, packagepath contient le chemin du zip (commence par / et fini par /
else if(!empty($scorm->packagename) and !empty($scorm->packagepath))
{
$fs = get_file_storage();
$filerecord->contextid=$context->id;
$filerecord->component='mod_scorm';
$filerecord->filearea="package";
$filerecord->itemid=0;
$filerecord->sortorder=0;
$filerecord->filepath='/';
$filerecord->filename=$scorm->packagename;
$filerecord->userid=$scorm->userid;
if($fs->file_exists($filerecord->contextid, $filerecord->component, $filerecord->filearea,$filerecord->itemid, $filerecord->filepath, $filerecord->filename))
{
echo "le fichier ".$scorm->packagepath.$scorm->packagename." existe deja \n"; //pour les logs , peu probable
}
else
{
echo "On enregistre le fichier : ".$scorm->packagepath.$scorm->packagename."\n"; //pour les logs
$stored_file=$fs->create_file_from_pathname($filerecord,$scorm->packagepath.$scorm->packagename);
$files = $fs->get_area_files($context->id, 'mod_scorm', 'package', 0, '', false);
$file = reset($files);
$filename = $file->get_filename();
if ($filename !== false) {
$record->reference = $filename;
}
}
}
//MD_ Fin ajout
} else if ($record->scormtype === SCORM_TYPE_LOCALSYNC) {
$record->reference = $scorm->packageurl;
} else if ($record->scormtype === SCORM_TYPE_EXTERNAL) {
$record->reference = $scorm->packageurl;
} else if ($record->scormtype === SCORM_TYPE_AICCURL) {
$record->reference = $scorm->packageurl;
$record->hidetoc = SCORM_TOC_DISABLED; // TOC is useless for direct AICCURL so disable it.
} else {
return false;
}
// Save reference.
$DB->update_record('scorm', $record);
// Extra fields required in grade related functions.
$record->course = $courseid;
$record->cmidnumber = $cmidnumber;
$record->cmid = $cmid;
scorm_parse($record, true);
scorm_grade_item_update($record);
scorm_update_calendar($record, $cmid);
if (!empty($scorm->completionexpected)) {
\core_completion\api::update_completion_date_event($cmid, 'scorm', $record, $scorm->completionexpected);
}
return $record->id;
}
//Fichier à crée sur le serveur Chamilo dans /main/newscorm/ (à coté de lp_controller.php étant une modification)
<?php
/* For licensing terms, see /license.txt */
/**
* Controller script. Prepares the common background variables to give to the scripts corresponding to
* the requested action
* @package chamilo.learnpath
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
//use \ChamiloSession as Session;
$debug = 0;
if ($debug > 0) error_log('New LP -+- Entered lp_controller.php -+- (action: '.$_REQUEST['action'].')', 0);
// Language files that needs to be included.
if (isset($_GET['action'])) {
if ($_GET['action'] == 'export') {
// Only needed on export.
$language_file[] = 'hotspot';
}
}
$language_file[] = 'course_home';
$language_file[] = 'scormdocument';
$language_file[] = 'document';
$language_file[] = 'scorm';
$language_file[] = 'learnpath';
$language_file[] = 'resourcelinker';
$language_file[] = 'registration';
$language_file[] = 'exercice';
// Including the global initialization file.
require_once '../inc/global.inc.php';
$current_course_tool = TOOL_LEARNPATH;
if (api_get_setting('show_glossary_in_documents') == 'ismanual' || api_get_setting('show_glossary_in_documents') == 'isautomatic' ) {
$htmlHeadXtra[] = '<script>
<!--
var jQueryFrameReadyConfigPath = \''.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.min.js\';
-->
</script>';
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.frameready.js" type="text/javascript" language="javascript"></script>';
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.highlight.js" type="text/javascript" language="javascript"></script>';
}
$htmlHeadXtra[] = '<script>
function setFocus(){
$("#idTitle").focus();
}
$(window).load(function () {
setFocus();
});
</script>
<style>
form .label {
padding: 1px 3px 2px;
font-size: 100%;
font-weight: normal;
color: #ffffff;
text-transform: none;
background: none;
border-radius: none;
color: #404040;
float: left;
line-height: 18px;
padding-top: 6px;
text-align: right;
width: 150px;
text-shadow:none;
}
</style>';
// Flag to allow for anonymous user - needs to be set before global.inc.php.
$use_anonymous = true;
// Include class definitions before session_start() to ensure availability when touching
// session vars containing learning paths.
require_once 'learnpath.class.php';
if ($debug > 0) error_log('New LP - Included learnpath', 0);
require_once 'learnpathItem.class.php';
if ($debug > 0) error_log('New LP - Included learnpathItem', 0);
require_once 'scorm.class.php';
if ($debug > 0) error_log('New LP - Included scorm', 0);
require_once 'scormItem.class.php';
if ($debug > 0) error_log('New LP - Included scormItem', 0);
require_once 'aicc.class.php';
if ($debug > 0) error_log('New LP - Included aicc', 0);
require_once 'aiccItem.class.php';
if ($debug > 0) error_log('New LP - Included aiccItem', 0);
require_once 'back_compat.inc.php';
if ($debug > 0) error_log('New LP - Included back_compat', 0);
//$session_id = api_get_session_id();
//api_protect_course_script(true);
require_once api_get_path(LIBRARY_PATH).'fckeditor/fckeditor.php';
$lpfound = false;
$myrefresh = 0;
$myrefresh_id = 0;
if (!empty($_SESSION['refresh']) && $_SESSION['refresh'] == 1) {
// Check if we should do a refresh of the oLP object (for example after editing the LP).
// If refresh is set, we regenerate the oLP object from the database (kind of flush).
Session::erase('refresh');
$myrefresh = 1;
if ($debug > 0) error_log('New LP - Refresh asked', 0);
}
if ($debug > 0) error_log('New LP - Passed refresh check', 0);
if (!empty($_REQUEST['dialog_box'])) {
$dialog_box = stripslashes(urldecode($_REQUEST['dialog_box']));
}
$lp_controller_touched = 1;
$lp_found = false;
if (isset($_SESSION['lpobject'])) {
if ($debug > 0) error_log('New LP - SESSION[lpobject] is defined', 0);
$oLP = unserialize($_SESSION['lpobject']);
if (isset($oLP) && is_object($oLP)) {
if ($debug > 0) error_log('New LP - oLP is object', 0);
if ($myrefresh == 1 OR empty($oLP->cc) OR $oLP->cc != api_get_course_id() OR $oLP->lp_view_session_id != $session_id OR $oLP->scorm_debug == '1') {
if ($debug > 0) error_log('New LP - Course has changed, discard lp object', 0);
if ($myrefresh == 1) { $myrefresh_id = $oLP->get_id(); }
$oLP = null;
Session::erase('oLP');
Session::erase('lpobject');
} else {
$_SESSION['oLP'] = $oLP;
$lp_found = true;
}
}
}
$course_id = api_get_course_int_id();
if ($debug>0) error_log('New LP - Passed data remains check', 0);
if (!$lp_found || (!empty($_REQUEST['lp_id']) && $_SESSION['oLP']->get_id() != $_REQUEST['lp_id'])) {
if ($debug > 0) error_log('New LP - oLP is not object, has changed or refresh been asked, getting new', 0);
// Regenerate a new lp object? Not always as some pages don't need the object (like upload?)
if (!empty($_REQUEST['lp_id']) || !empty($myrefresh_id)) {
if ($debug > 0) error_log('New LP - lp_id is defined', 0);
// Select the lp in the database and check which type it is (scorm/dokeos/aicc) to generate the
// right object.
if (!empty($_REQUEST['lp_id'])) {
$lp_id = intval($_REQUEST['lp_id']);
} else {
$lp_id = intval($myrefresh_id);
}
$lp_table = Database::get_course_table(TABLE_LP_MAIN);
if (is_numeric($lp_id)) {
$sel = "SELECT lp_type FROM $lp_table WHERE c_id = $course_id AND id = $lp_id";
if ($debug > 0) error_log('New LP - querying '.$sel, 0);
$res = Database::query($sel);
if (Database::num_rows($res)) {
$row = Database::fetch_array($res);
$type = $row['lp_type'];
if ($debug > 0) error_log('New LP - found row - type '.$type. ' - Calling constructor with '.api_get_course_id().' - '.$lp_id.' - '.api_get_user_id(), 0);
switch ($type) {
case 1:
if ($debug > 0) error_log('New LP - found row - type dokeos - Calling constructor with '.api_get_course_id().' - '.$lp_id.' - '.api_get_user_id(), 0);
$oLP = new learnpath(api_get_course_id(), $lp_id, api_get_user_id());
if ($oLP !== false) { $lp_found = true; } else { error_log($oLP->error, 0); }
break;
case 2:
if ($debug > 0) error_log('New LP - found row - type scorm - Calling constructor with '.api_get_course_id().' - '.$lp_id.' - '.api_get_user_id(), 0);
$oLP = new scorm(api_get_course_id(), $lp_id, api_get_user_id());
if ($oLP !== false) { $lp_found = true; } else { error_log($oLP->error, 0); }
break;
case 3:
if ($debug > 0) error_log('New LP - found row - type aicc - Calling constructor with '.api_get_course_id().' - '.$lp_id.' - '.api_get_user_id(), 0);
$oLP = new aicc(api_get_course_id(),$lp_id,api_get_user_id());
if ($oLP !== false) { $lp_found = true; } else { error_log($oLP->error, 0); }
break;
default:
if ($debug > 0) error_log('New LP - found row - type other - Calling constructor with '.api_get_course_id().' - '.$lp_id.' - '.api_get_user_id(), 0);
$oLP = new learnpath(api_get_course_id(),$lp_id,api_get_user_id());
if ($oLP !== false) { $lp_found = true; } else { error_log($oLP->error, 0); }
break;
}
}
} else {
if ($debug > 0) error_log('New LP - Request[lp_id] is not numeric', 0);
}
} else {
if ($debug > 0) error_log('New LP - Request[lp_id] and refresh_id were empty', 0);
}
if ($lp_found) {
$_SESSION['oLP'] = $oLP;
}
}
if ($debug > 0) error_log('New LP - Passed oLP creation check', 0);
$is_allowed_to_edit = api_is_allowed_to_edit(false, true, false, false);
if (isset($_SESSION['oLP'])) {
$_SESSION['oLP']->update_queue = array(); // Reinitialises array used by javascript to update items in the TOC.
$_SESSION['oLP']->message = ''; // Should use ->clear_message() method but doesn't work.
}
if (isset($_GET['isStudentView']) && $_GET['isStudentView'] == 'true') {
if ($_REQUEST['action'] != 'list' AND $_REQUEST['action'] != 'view') {
if (!empty($_REQUEST['lp_id'])) {
$_REQUEST['action'] = 'view';
} else {
$_REQUEST['action'] = 'list';
}
}
$_SESSION['studentview'] = "studentview";
} else {
if ($is_allowed_to_edit) {
if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'view' && !isset($_REQUEST['exeId'])) {
$_REQUEST['action'] = 'build';
}
$_SESSION['studentview'] = "teacherview";
}
}
$action = (!empty($_REQUEST['action']) ? $_REQUEST['action'] : '');
switch ($action) {
case 'export_perso':
//if (!$is_allowed_to_edit) {
// api_not_allowed(true);
//}
if ($debug > 0) error_log('New LP - export action triggered', 0);
if (!$lp_found) { error_log('New LP - No learnpath given for export', 0); require 'lp_list.php'; }
else {
$_SESSION['oLP']->scorm_export_md();
exit();
//require 'lp_list.php';
}
break;
default:
if ($debug > 0) error_log('New LP - default action triggered', 0);
require 'lp_list.php';
break;
}
if (!empty($_SESSION['oLP'])) {
$_SESSION['lpobject'] = serialize($_SESSION['oLP']);
if ($debug > 0) error_log('New LP - lpobject is serialized in session', 0);
}
//Il faut modifier la fonction suivante du fichier /course/modlib.php (fonction debute ligne ~32, modification debute ligne ~125)
//La seule modification correspond au remplacement d'un die par un throw new Exception
/**
* Add course module.
*
* The function does not check user capabilities.
* The function creates course module, module instance, add the module to the correct section.
* It also trigger common action that need to be done after adding/updating a module.
*
* @param object $moduleinfo the moudle data
* @param object $course the course of the module
* @param object $mform this is required by an existing hack to deal with files during MODULENAME_add_instance()
* @return object the updated module info
*/
function add_moduleinfo($moduleinfo, $course, $mform = null) {
global $DB, $CFG;
// Attempt to include module library before we make any changes to DB.
include_modulelib($moduleinfo->modulename);
$moduleinfo->course = $course->id;
$moduleinfo = set_moduleinfo_defaults($moduleinfo);
if (!empty($course->groupmodeforce) or !isset($moduleinfo->groupmode)) {
$moduleinfo->groupmode = 0; // Do not set groupmode.
}
// First add course_module record because we need the context.
$newcm = new stdClass();
$newcm->course = $course->id;
$newcm->module = $moduleinfo->module;
$newcm->instance = 0; // Not known yet, will be updated later (this is similar to restore code).
$newcm->visible = $moduleinfo->visible;
$newcm->visibleold = $moduleinfo->visible;
if (isset($moduleinfo->visibleoncoursepage)) {
$newcm->visibleoncoursepage = $moduleinfo->visibleoncoursepage;
}
if (isset($moduleinfo->cmidnumber)) {
$newcm->idnumber = $moduleinfo->cmidnumber;
}
$newcm->groupmode = $moduleinfo->groupmode;
$newcm->groupingid = $moduleinfo->groupingid;
$completion = new completion_info($course);
if ($completion->is_enabled()) {
$newcm->completion = $moduleinfo->completion;
$newcm->completiongradeitemnumber = $moduleinfo->completiongradeitemnumber;
$newcm->completionview = $moduleinfo->completionview;
$newcm->completionexpected = $moduleinfo->completionexpected;
}
if(!empty($CFG->enableavailability)) {
// This code is used both when submitting the form, which uses a long
// name to avoid clashes, and by unit test code which uses the real
// name in the table.
$newcm->availability = null;
if (property_exists($moduleinfo, 'availabilityconditionsjson')) {
if ($moduleinfo->availabilityconditionsjson !== '') {
$newcm->availability = $moduleinfo->availabilityconditionsjson;
}
} else if (property_exists($moduleinfo, 'availability')) {
$newcm->availability = $moduleinfo->availability;
}
// If there is any availability data, verify it.
if ($newcm->availability) {
$tree = new \core_availability\tree(json_decode($newcm->availability));
// Save time and database space by setting null if the only data
// is an empty tree.
if ($tree->is_empty()) {
$newcm->availability = null;
}
}
}
if (isset($moduleinfo->showdescription)) {
$newcm->showdescription = $moduleinfo->showdescription;
} else {
$newcm->showdescription = 0;
}
// From this point we make database changes, so start transaction.
$transaction = $DB->start_delegated_transaction();
if (!$moduleinfo->coursemodule = add_course_module($newcm)) {
print_error('cannotaddcoursemodule');
}
if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true) &&
isset($moduleinfo->introeditor)) {
$introeditor = $moduleinfo->introeditor;
unset($moduleinfo->introeditor);
$moduleinfo->intro = $introeditor['text'];
$moduleinfo->introformat = $introeditor['format'];
}
$addinstancefunction = $moduleinfo->modulename."_add_instance";
try {
$returnfromfunc = $addinstancefunction($moduleinfo, $mform);
} catch (moodle_exception $e) {
$returnfromfunc = $e;
}
catch (Exception $e) { //ajout MD_
$DB->force_transaction_rollback();
throw $e;
} //Fin ajout
if (!$returnfromfunc or !is_number($returnfromfunc)) {
// Undo everything we can. This is not necessary for databases which
// support transactions, but improves consistency for other databases.
context_helper::delete_instance(CONTEXT_MODULE, $moduleinfo->coursemodule);
$DB->delete_records('course_modules', array('id'=>$moduleinfo->coursemodule));
if ($returnfromfunc instanceof moodle_exception) {
throw $returnfromfunc;
} else if (!is_number($returnfromfunc)) {
print_error('invalidfunction', '', course_get_url($course, $moduleinfo->section));
} else {
print_error('cannotaddnewmodule', '', course_get_url($course, $moduleinfo->section), $moduleinfo->modulename);
}
}
$moduleinfo->instance = $returnfromfunc;
$DB->set_field('course_modules', 'instance', $returnfromfunc, array('id'=>$moduleinfo->coursemodule));
// Update embedded links and save files.
$modcontext = context_module::instance($moduleinfo->coursemodule);
if (!empty($introeditor)) {
// This will respect a module that has set a value for intro in it's modname_add_instance() function.
$introeditor['text'] = $moduleinfo->intro;
$moduleinfo->intro = file_save_draft_area_files($introeditor['itemid'], $modcontext->id,
'mod_'.$moduleinfo->modulename, 'intro', 0,
array('subdirs'=>true), $introeditor['text']);
$DB->set_field($moduleinfo->modulename, 'intro', $moduleinfo->intro, array('id'=>$moduleinfo->instance));
}
// Add module tags.
if (core_tag_tag::is_enabled('core', 'course_modules') && isset($moduleinfo->tags)) {
core_tag_tag::set_item_tags('core', 'course_modules', $moduleinfo->coursemodule, $modcontext, $moduleinfo->tags);
}
// Course_modules and course_sections each contain a reference to each other.
// So we have to update one of them twice.
$sectionid = course_add_cm_to_section($course, $moduleinfo->coursemodule, $moduleinfo->section);
// Trigger event based on the action we did.
// Api create_from_cm expects modname and id property, and we don't want to modify $moduleinfo since we are returning it.
$eventdata = clone $moduleinfo;
$eventdata->modname = $eventdata->modulename;
$eventdata->id = $eventdata->coursemodule;
$event = \core\event\course_module_created::create_from_cm($eventdata, $modcontext);
$event->trigger();
$moduleinfo = edit_module_post_actions($moduleinfo, $course);
$transaction->allow_commit();
return $moduleinfo;
}
//Il faut modifier la fonction suivante du fichier /mod/scorm/datamodels/scormlib.php (fonction debute ligne ~910, modification debute ligne ~925)
//La seule modification correspond au remplacement d'un die par un throw new Exception
/**
* Parse an XML text string and create an array tree that rapresent the XML structure
*
* @param string $strinputxml The XML string
* @return array
*/
public function parse($strinputxml) {
$this->resparser = xml_parser_create ('UTF-8');
xml_set_object($this->resparser, $this);
xml_set_element_handler($this->resparser, "tagopen", "tagclosed");
xml_set_character_data_handler($this->resparser, "tagdata");
$this->strxmldata = xml_parse($this->resparser, $strinputxml );
if (!$this->strxmldata) {
throw new Exception('XML Parse Error'); //MD_ : modification pour que le script se poursuive
/* die(sprintf("XML error: %s at line %d",
xml_error_string(xml_get_error_code($this->resparser)),
xml_get_current_line_number($this->resparser)));
*/
}
xml_parser_free($this->resparser);
return $this->arroutput;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment