No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ApiScorm2004.php 40KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896
  1. <?php
  2. namespace Logipro\Bundle\SCORMBundle\ClientAPI;
  3. use Doctrine\Common\Persistence\ManagerRegistry;
  4. use Doctrine\ORM\EntityManagerInterface;
  5. use Logipro\Bundle\SCORMBundle\Crypto\Crypto;
  6. use Logipro\Bundle\SCORMBundle\Entity\Learner;
  7. use Logipro\Bundle\SCORMBundle\Entity\Registration;
  8. use Logipro\Bundle\SCORMBundle\Entity\RegistrationAttempt;
  9. use Logipro\Bundle\SCORMBundle\Entity\ZipFile;
  10. use Logipro\Bundle\SCORMBundle\Entity\Scorm2004\Scorm2004AttemptProgressInformation;
  11. use Logipro\Bundle\SCORMBundle\Entity\Scorm2004\Scorm2004Comment;
  12. use Logipro\Bundle\SCORMBundle\Entity\Scorm2004\Scorm2004Interaction;
  13. use Logipro\Bundle\SCORMBundle\Entity\Scorm2004\Scorm2004MapTrackItem;
  14. use Logipro\Bundle\SCORMBundle\Entity\Scorm2004\Scorm2004Note;
  15. use Logipro\Bundle\SCORMBundle\Entity\Scorm2004\Scorm2004Track;
  16. use Logipro\Bundle\SCORMBundle\Entity\Scorm2004\Scorm2004ObjectiveProgressInformation;
  17. use Logipro\Bundle\SCORMBundle\LearningModels\DOMSCORM2004;
  18. use Logipro\Bundle\SCORMBundle\Tools\TimeTools;
  19. use Logipro\Bundle\SCORMBundle\Tools\UnicodeTools;
  20. use Symfony\Component\DependencyInjection\ContainerInterface;
  21. use Symfony\Component\DependencyInjection\ContainerAwareTrait;
  22. use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  23. use Symfony\Component\Routing\Generator\UrlGenerator;
  24. class ApiScorm2004 implements ContainerAwareInterface
  25. {
  26. use ContainerAwareTrait;
  27. public function __construct(ContainerInterface $container)
  28. {
  29. $this->setContainer($container);
  30. }
  31. public function getAPI(DOMSCORM2004 $dom, string $registrationKey, string $organization, string $item)
  32. {
  33. $em = $this->getDoctrine()->getManager();
  34. $debugMode = true;
  35. // DATA
  36. $dataArray = array(
  37. 'regKey' => $registrationKey,
  38. 'org' => $organization,
  39. 'current' => $item
  40. );
  41. $serialize = \serialize($dataArray);
  42. $currentData = Crypto::crypt($serialize);
  43. // INSCRIPTION
  44. $registration = $this->requireRegistration($registrationKey);
  45. if (!$registration) {
  46. return;
  47. }
  48. // ESSAI
  49. $registrationAttemptRepository = $em->getRepository(RegistrationAttempt::class);
  50. $registrationAttempt = $registrationAttemptRepository->findOneBy(array('registration' => $registration), array('registrationAttemptId' => 'desc'));
  51. if (!$registrationAttempt) {
  52. return;
  53. }
  54. // APPRENANT
  55. $learner = $registration->getLearner();
  56. if (!$learner) {
  57. return;
  58. }
  59. $learnerId = $learner->getLearnerId();
  60. // TRACK
  61. $trackRepository = $em->getRepository(Scorm2004Track:: class);
  62. $track = $trackRepository->findOneBy(array('organizationId' => $organization, 'registrationAttempt' => $registrationAttempt));
  63. if (!$track) {
  64. return;
  65. }
  66. // MAP ITEM/TRACK
  67. $mapTrackItemRepository = $em->getRepository(Scorm2004MapTrackItem:: class);
  68. $mapTrackItem = $mapTrackItemRepository->findOneBy(array('scorm2004Track' => $track, 'item_identifier' => $item));
  69. // Progress Information
  70. $attemptInfoRepository = $em->getRepository(Scorm2004AttemptProgressInformation:: class);
  71. $attemptProgressInfo = $attemptInfoRepository->findOneBy(array('scorm2004Track' => $track, 'item_identifier' => $item), array('attempt_id' => 'desc'));
  72. //Nom de l'apprenant
  73. $learner_name = $this->jsEscape($learner->getFamilyName().' '.$learner->getGivenName());
  74. // cmi.mode c'est mode dans lequel le sco va être présenté à l'aprenant.
  75. // ils exitent 3 modes : borwse,normal,review.
  76. // La norme ne definie aucun mécanisme pour determiner la valeur de cmi.mode
  77. // c'est le LMS qui doit prendre en charge cette tâche si le LMS veut l'implémenter.
  78. // le mode par défaut exigé par la nore est le mode "normal". Pour les deux autres modes
  79. // "review" et "browse" la norme ne definit aucune contrainte et ne propose aucun mecanisme pour ces deux mode.
  80. // il est libre au LMS de supporter 'à sa guise' ou pas ces deux mode.
  81. // si on sait jamais le LMS décide de supporter ces deux modes, les information envoyées par le sco lors du commit
  82. // devient des informations qui servent comme une information et n'impacte pas le processus de séquençage.
  83. // pour plus de détail, regarder le livre RTE page 152.
  84. $mode = 'normal'; //normal est le mode par défaut.
  85. switch ($mode) {
  86. case 'normal':
  87. $credit = $this->jsEscape('credit');
  88. break;
  89. case 'review':
  90. case 'browse':
  91. default:
  92. $credit = $this->jsEscape('no-credit');
  93. }
  94. $mode = $this->jsEscape($mode);
  95. //================================ Completion Threshold ===========================================//
  96. // completion Threshold et definie dans le manifest.
  97. // au moment de l'initialisation de cmi.completion_threshold, il faut regarder ses attributs : completedByMeasure & minProgressMeasure.
  98. // pour plus de détails regarder le document RTE page 97.
  99. $completedByMeasure = $dom->getItemElementContent($item, 'adlcp:completionThreshold', 'completedByMeasure');
  100. $completion_threshold = null;
  101. // si l'attribut completed By Measure est à false alors completion_threshold ne doit pas être initialisé.
  102. if ($completedByMeasure === 'false') {
  103. $completion_threshold = null; //not initialized.
  104. } else // sinon completion_threshold va prendre la valeur de l'attribut minProgressMeasure
  105. {
  106. $completion_threshold = $dom->getAttributeByName($item, 'minProgressMeasure');
  107. }
  108. //============================= progress measure ==================================================//
  109. $progress_measure = null;
  110. if ($attemptProgressInfo->getCompletionAmountStatus()) {
  111. $progress_measure = $attemptProgressInfo->getCompletionAmount();
  112. } else {
  113. $progress_measure = 0.0;
  114. }
  115. //============================ completion status ================================================//
  116. $completion_status = null;
  117. $valueOfAttempProgressStatus = $attemptProgressInfo->getProgressStatus();
  118. $valueOfAttempCompletionStatus = $attemptProgressInfo->getCompletionStatus();
  119. if ($valueOfAttempCompletionStatus && $valueOfAttempProgressStatus) {
  120. $completion_status = 'completed';
  121. } elseif (!$valueOfAttempCompletionStatus && !$valueOfAttempProgressStatus) {
  122. $completion_status = 'unknown';
  123. } elseif (!$valueOfAttempCompletionStatus && $valueOfAttempProgressStatus) {
  124. $completion_status = 'incomplete';
  125. }
  126. //=========================scaled passing score : cmi.scaled_passing_score ==================================//
  127. // Le LMS est responsable d'initialiser cet element.
  128. // l'initialisation de cet element repose sur les valeur définie dans imsss:satisfiedByMeasure associé avec <imsss:primaryObjective>
  129. // et <imsss:minNormalizedMeasure> element associe avec le <imsss:primaryObjective>.
  130. // Pour plus de détails regarder le livre RTE page 174.
  131. $scaled_passing_score = null;
  132. // on recupère l'objectif primaire associe avec le plannode courant
  133. $primaryObjective = $dom->getPrimaryObjective($item);
  134. // si on trouve l'objectif primaire ....
  135. if (!empty($primaryObjective)) {
  136. // on cherche le satisfied by mesure
  137. $satisfiedByMeasure = $dom->getSatisfiedByMeasure($item, $primaryObjective['objective'], true);
  138. // si le satisfiedByMeasure == true, alors on va regarder minNormalizedMeasure
  139. if ($satisfiedByMeasure === 'true') {
  140. $minNormalizedMeasure = $dom->getMinNormalizeMeasure($item, $primaryObjective['objective']);
  141. $scaled_passing_score = $minNormalizedMeasure;
  142. } else {
  143. // sinon: (satisfiedByMeasure == true), dans ce cas la norme dit qu'il faut pas initialisé scaled_passing_score.
  144. // dans notre cas, ça revient à mettre $scaled_passing_score = null, ce qui est déja fait au dessus.
  145. }
  146. }
  147. //============================ Scaled Score : cmi.score.scaled ==================================================//
  148. // Le sco est responsable de determiner le scaled score.
  149. // le mappage pour le score.scaled est fait avec measure_status et normalized_measure.
  150. $objectiveInfoRepository = $em->getRepository(Scorm2004ObjectiveProgressInformation::class);
  151. $primaryInfo = null;
  152. if (!empty($primaryObjective)) {
  153. $primaryInfo = $objectiveInfoRepository->findLocalInfoByObjectiveAndAttempt($primaryObjective, $attemptProgressInfo, $track, true, $dom);
  154. }
  155. $score_scaled = null;
  156. if ($primaryInfo && $primaryInfo->getMeasureStatus()) {
  157. $score_scaled = $primaryInfo->getNormalizedMeasure();
  158. } else {
  159. $score_scaled = 0.0;
  160. }
  161. //=========================== success status : cmi.success_status =============================//
  162. // cet element dépend de scaled_passing_score et de score.scaled. RTE page 181.
  163. $success_status = null;
  164. // si le scaled_passing_score n'est pas définie alors c'est le sco qui gère le success status.
  165. if ($scaled_passing_score == null) {
  166. // donc on recupère la valeur de sucess status mise par le sco.
  167. // Si objectif primaire elle est stockée dans objectiveInfo
  168. // Sinon dans Attempt
  169. if ($primaryInfo) {
  170. $success_status = $primaryInfo->getSuccessStatus();
  171. } else {
  172. $success_status = $attemptProgressInfo->getSuccessStatus();
  173. }
  174. } elseif ($score_scaled == 0) { // no valeur mise par le sco pour score.scaled.
  175. $success_status = 'unknown';
  176. } elseif ($score_scaled < $scaled_passing_score) {
  177. $success_status = 'failed';
  178. } elseif ($score_scaled >= $scaled_passing_score) {
  179. $success_status = 'passed';
  180. } else // ce else a été rajouté par moi même pour traiter les eventuels cas non prévus.
  181. {
  182. $success_status = 'unknown';
  183. }
  184. // formatage de données pour initialiser l'API.
  185. $completion_threshold = $this->jsEscape($completion_threshold);
  186. $completion_status = $this->jsEscape($completion_status);
  187. $score_scaled = $this->jsEscape($score_scaled);
  188. $scaled_passing_score = $this->jsEscape($scaled_passing_score);
  189. $success_status = $this->jsEscape($success_status);
  190. $progress_measure = $this->jsEscape($progress_measure);
  191. $suspend_data = $this->jsEscape($attemptProgressInfo->getSuspendData());
  192. $session_time = $this->jsEscape(0);
  193. $totalTime = $attemptProgressInfo->getTotalTime();
  194. $totalTime = $totalTime ? $totalTime : 0;
  195. $convertedTotalTime = TimeTools::convertSecondsToIso8601Duration($totalTime);
  196. $total_time = $this->jsEscape($convertedTotalTime);
  197. // information relative au plannode.
  198. $time_limit_action = $this->jsEscape($dom->getItemElementContent($item, '*[name()="adlcp:timeLimitAction"]'));
  199. $launch_data = $this->jsEscape($dom->getAttributeByName($item, 'dataFromLMS'));
  200. $max_time_allowed = $this->jsEscape($dom->getSequencingAttribute('imsss:limitConditions', 'attemptAbsoluteDurationLimit', $item));
  201. // informations relative au tracking.
  202. // le cmi.exit.
  203. $exit = $mapTrackItem->getExit();
  204. // determination de la valeur de entry.
  205. // Le LMS est responsable de determiner la valeur de entry en se basant sur la valeur de cmi.exit
  206. // pour les détails des règles qui permettent d'initialiser le cmi.entry regarder RTE page 100.
  207. // si c'est la premiere entré dans le sco (first launch of the sco), ou une nouvelle tentative apres un logout.
  208. if (!$exit || $exit == 'logout') {
  209. $entry = $this->jsEscape('ab-initio');
  210. } elseif ($exit == 'suspend') { // si on a fait un resume apres suspension
  211. $entry = $this->jsEscape('resume');
  212. } else { // pour tous les autres cas
  213. $entry = $this->jsEscape('');
  214. }
  215. $exit = $this->jsEscape($exit);
  216. $location = $this->jsEscape($mapTrackItem->getLocation());
  217. // informations relative au tracking plannode (table de mappage)
  218. // information pour le score
  219. $score_raw = $this->jsEscape($mapTrackItem->getScoreRaw());
  220. $score_min = $this->jsEscape($mapTrackItem->getScoreMin());
  221. $score_max = $this->jsEscape($mapTrackItem->getScoreMax());
  222. // informations relatatives aux préférences de l'apprenant.
  223. $learner_preference_audio_level = $this->jsEscape($mapTrackItem->getLearnerPreferenceAudioLevel());
  224. $learner_preference_language = $this->jsEscape($mapTrackItem->getLearnerPreferenceLanguage());
  225. $learner_preference_delivery_speed = $this->jsEscape($mapTrackItem->getLearnerPreferenceDeliverySpeed());
  226. $learner_preference_audio_captioning = $this->jsEscape($mapTrackItem->getLearnerPreferenceAudioCaptioning());
  227. //=================================================================================================//
  228. // RECUPERATION DES INTERACTION + OBJECTIFS DES INTERACTION //
  229. //=================================================================================================//
  230. // on recupère les interactions.
  231. $interactionRepository = $em->getRepository(Scorm2004Interaction::class);
  232. $interactions = $interactionRepository->findBy(array('scorm2004Track' => $track, 'scorm2004AttemptProgressInformation' => $attemptProgressInfo));
  233. $interactionList = array(); // TABLE QUI VA CONTENIR TOUTES LES INTERACTIONS
  234. foreach ($interactions as $interaction) {
  235. // Récupération des objectifs créés par l'interaction
  236. $interactionObjectives = $objectiveInfoRepository->findBy(array('scorm2004Interaction' => $interaction));
  237. $objectives = array();
  238. foreach ($interactionObjectives as $interactionObjective) {
  239. $objectives[]['id'] = $interactionObjective->getObjectiveIdentifier();
  240. }
  241. // Préparation des données de l'interaction
  242. $latency = $interaction->getLatency();
  243. $convertedLatency = TimeTools::convertSecondsToIso8601Duration($latency);
  244. $convertedTimeStamp ='';
  245. $timeStamp = $interaction->getTime();
  246. if ($timeStamp) {
  247. $convertedTimeStamp = $timeStamp->format('Y-m-d\TH:i:s');
  248. }
  249. // récupération des données de l'interaction
  250. $inter = array(
  251. 'id'=>$interaction->getIdentifier(),
  252. 'type'=>$interaction->getType(),
  253. 'weighting'=>$interaction->getWeighting(),
  254. 'result'=>$interaction->getResult(),
  255. 'learner_response'=>$interaction->getStudentResponse(),
  256. 'correcte_responses'=>$interaction->getCorrectResponses(),
  257. 'timestamp'=>$convertedTimeStamp,
  258. 'latency'=>$convertedLatency,
  259. 'description'=>$interaction->getDescription(),
  260. 'objectives'=>$objectives
  261. );
  262. $interactionList[] = $inter;
  263. }
  264. //=================================================================================================//
  265. // RECUPERATION DES OBJECTIFS cmi.objectives //
  266. //=================================================================================================//
  267. //on récupère les objectifs de l'item courant.
  268. $itemObjectives = $dom->getObjectives($item);
  269. $scoreObjective = new \stdClass(); //object qui va contenir le score de l'objectif
  270. $listObjectives = array();
  271. $objective = array();
  272. $scoreScaled = 0.0;
  273. foreach ($itemObjectives as $itemObjective) {
  274. // $itemObjectiveInfos = $objectiveInfoRepository->findOneBy(array('objectiveIdentifier' => $itemObjective['objective'], 'itemIdentifier' => $itemObjective['item'], 'isPrimary' => $itemObjective['isPrimary']));
  275. $itemObjectiveInfos = $objectiveInfoRepository->findLocalInfoByObjectiveAndAttempt($itemObjective, $attemptProgressInfo, $track, true, $dom);
  276. // si on a pas d'objectiveInfo de l'objectif: un problème.
  277. if (!$itemObjectiveInfos) {
  278. return ;
  279. }
  280. // IMPORTANT : on traite le mapping entre le modele de tracking et le modele RTE.
  281. // Pour plus de détails, regarder le livre SN, page 140.
  282. // Debut de partie de mappage.
  283. // I . les éléments qu'il faut mappés avec les progess objective infos sont les suivants:
  284. // cmi.objectives.n.success_status, cmi.objectives.n.score.scaled.
  285. // 1. cmi.objectives.n.success_status dépend de : Objective Progress Status et Objective Satisfied Status.
  286. $successStatus = $itemObjectiveInfos->getSuccessStatus();
  287. // 2. cmi.objectives.n.score.scaled dépend de : Objective Measure Status et Objective Normalized Measure.
  288. if ($itemObjectiveInfos->getMeasureStatus()) {
  289. $scoreScaled = $itemObjectiveInfos->getNormalizedMeasure();
  290. }
  291. // II . les éléments qu'il faut mappés avec Attemps progess infos sont :
  292. // cmi.objectives.n.completion_status et cmi.objectives.n.progress_measure dans le cas d'un objectif primaire.
  293. // cmi.objectives.n.completion_status dépned de : Attempt Progress Status et Attempt Completion Status
  294. // si c'est un objectif primaire, sinon elle dépende objective Progress Status et objective Completion Status
  295. $completionStatus = null;
  296. // cmi.objectives.n.progress_measure dépend de Attempt Completion Amount.
  297. $progressMeasure = null;
  298. // variable utilisées pour recupérer les données nécessaire pour le mappage
  299. // variables utilisées pour recupérer les données servant à faire le mappage pour $completionStatus.
  300. $progStatus = false;
  301. $compStatus = false;
  302. // variables utilisées pour recupérer les données servant à faire le mappage pour $progressMeasure.
  303. $compleAmount = null;
  304. $compleAmountStatus = false;
  305. // si c'est un objectif primaire, alors il faut recupérer les info de l'attempt, sinon il faut recupérer les infos de l'objectif.
  306. if ($itemObjective['isPrimary']) {
  307. // donnes pour completion status
  308. $progStatus = $attemptProgressInfo->getProgressStatus();
  309. $compStatus = $attemptProgressInfo->getCompletionStatus();
  310. // donnes pour progress measure.
  311. $compleAmount = $attemptProgressInfo->getCompletionAmount();
  312. $compleAmountStatus = $attemptProgressInfo->getCompletionAmountStatus();
  313. } else {
  314. $progStatus = $itemObjectiveInfos->getProgressStatus();
  315. $compStatus = $itemObjectiveInfos->getCompletionStatus();
  316. // donnes pour progress measure.
  317. $compleAmount = $itemObjectiveInfos->getAttemptCompletionAmount();
  318. $compleAmountStatus = $itemObjectiveInfos->getAttemptCompletionAmountStatus();
  319. }
  320. // on fait le mappage par rapport aux données qu'on a récupérées pour completion Status.
  321. if ($progStatus && $compStatus) {
  322. $completionStatus = 'completed';
  323. } elseif ($progStatus && !$compStatus) {
  324. $completionStatus = 'incomplete';
  325. } elseif (!$progStatus) {
  326. $completionStatus = 'unknown';
  327. }
  328. // on fait le mappage par rapport aux données qu'on a récupérées pour progress Measure.
  329. if ($compleAmountStatus) {
  330. $progressMeasure = $compleAmount;
  331. }
  332. // III. initialisation des autres éléments des objectives qui n'ont pas de mappage entre le RTE et le SN.
  333. // Le score de l'objectif
  334. $scoreObjective->raw = $itemObjectiveInfos->getScoreRaw();
  335. $scoreObjective->min = $itemObjectiveInfos->getScoreMin();
  336. $scoreObjective->max = $itemObjectiveInfos->getScoreMax();
  337. // si le score.scaled est initialisé.
  338. $scoreObjective->scaled = $scoreScaled;
  339. // l'objectif
  340. $objective['id'] = $itemObjective['objective'];
  341. $objective['score'] = $scoreObjective;
  342. $objective['success_status'] = $successStatus;
  343. $objective['completion_status'] = $completionStatus;
  344. $objective['description'] = $itemObjectiveInfos->getDescription();
  345. // si progress mesure est initialisé.
  346. $objective['progress_measure'] = $progressMeasure;
  347. //on remplie le tableau des objectifs
  348. $listObjectives[] = $objective;
  349. }
  350. //=================================================================================================//
  351. // RECUPERATION DES DATA_MAP DE L'ITEM //
  352. //=================================================================================================//
  353. // Ces données vont être utilisées par le sco pour interchanger des notes: ces données corréspondent
  354. // à l'element du modele de données de type adl.data. Le sco peut donc chercher cet element par son id (adl.data.i.id) afin
  355. // d'en rajouter une note. cette note sera stocker dans adl.data.i.storage de cet element. Le LMS est responsable de stocker adl.data.id (à partir
  356. // du mainfest), et de le fournir au sco. Ce dernier l'utilise pour rajouter une note (du texte) dans adl.data.i.store. Cependant le LMS
  357. // n'est pas responsable de stocker cette note (adl.data.i.store). Donc, ces notes sont rajoutées par le sco au moment de son execution(dans
  358. // un tableau).
  359. // On recupère les données de data Map.
  360. $arrayDataMap = array();
  361. $dataMaps = $dom->getDataMap($item);
  362. $noteRepository = $em->getRepository(Scorm2004Note::class);
  363. if (!empty($dataMaps)) {
  364. foreach ($dataMaps as $dataMap) {
  365. // on recupère les notes prise par l'apprenant sur les différents SCO
  366. // qui ont un data map avec le même id target que le plannode current.
  367. $targetID_data_map = $dataMap['targetID'];
  368. $stored_note = "";
  369. $note = null;
  370. // Global non pris en compte dans SMART
  371. // A corriger
  372. /* if ($dataMap['IsGlobalToSystem']) {
  373. $note = $noteRepository->findOneBy(array('target_id_data_map' => $targetID_data_map));
  374. } else { */
  375. $note = $noteRepository->findOneBy(array('target_id_data_map' => $targetID_data_map, 'scorm2004Track' => $track));
  376. /* } */
  377. // si on a des notes prise par l'apprenant par rapport au data map identifié par targetID.
  378. if (!empty($note)) {
  379. $stored_note = $note->getNote();
  380. }
  381. // on met à jour le tableau de notes qui va être envoyer à l'API
  382. $data = array(
  383. 'id' => $targetID_data_map,
  384. 'store' => $stored_note
  385. );
  386. $arrayDataMap[] = $data;
  387. }
  388. }
  389. //=================================================================================================//
  390. // RECUPERATION DES COMMENTAIRES (comments_from_learner). //
  391. //=================================================================================================//
  392. $commentRepository = $em->getRepository(Scorm2004Comment::class);
  393. $learnerComments = $commentRepository->findBy(array('scorm2004Track' => $track, 'resourceIdentifier' => $item, 'learner' => $learner));
  394. $arrayLearnerComments = array();
  395. $index = 0;
  396. foreach ($learnerComments as $comment) {
  397. $com = new \stdClass();
  398. $com->comment = $comment->getComment();
  399. $com->location = $comment->getLocation();
  400. $com->timestamp = $comment->getTimestamp();
  401. $arrayLearnerComments[$index] = $com;
  402. $index++;
  403. }
  404. //=================================================================================================//
  405. // RECUPERATION DES COMMENTAIRES (comments_from_LMS). //
  406. //=================================================================================================//
  407. $lmsComments = $commentRepository->findBy(array('scorm2004Track' => $track, 'resourceIdentifier' => $item, 'learner' => null));
  408. $arrayLmsComments = array();
  409. $index = 0;
  410. foreach ($lmsComments as $comment) {
  411. $com = new \stdClass();
  412. $com->comment = $comment->getComment();
  413. $com->location = $comment->getLocation();
  414. $com->timestamp = $comment->getTimestamp();
  415. $arrayLmsComments[$index] = $com;
  416. $index++;
  417. }
  418. //================================================//
  419. // ROUTES //
  420. //================================================//
  421. // DEFINIR COMMENT ON FAIT LES COMMITS
  422. $url = $this->container->get('router')->generate('logipro_scorm_2004_commit', [], UrlGenerator::ABSOLUTE_URL);
  423. $ajaxURL = $this->jsEscape($url);
  424. //=================================================================================================//
  425. // PARTIE API //
  426. //=================================================================================================//
  427. $apiString =<<<EOT
  428. <script>
  429. EOT;
  430. $cmiStructure = file_get_contents(dirname(__FILE__).'/../Resources/public/js/CMIStructure.js');
  431. $scorm2004 = file_get_contents(dirname(__FILE__).'/../Resources/public/js/scorm2004.js');
  432. $apiString .= $cmiStructure;
  433. $apiString .= $scorm2004;
  434. $apiString.=<<<EOT
  435. //=================================================//
  436. // CREATION DE L'API SCORM 2004 //
  437. //=================================================//
  438. var API = new ScormApi();
  439. var API_1484_11 = API ;
  440. console.log('API_1484_11');
  441. console.log('API_1484_11: '+API_1484_11);
  442. //=================================================//
  443. // INITIALISATION D'ACTION //
  444. //=================================================//
  445. API.currentData = '{$currentData}';
  446. API.ajaxURL = '{$ajaxURL}';
  447. API.ajaxURLRedirection = '{$ajaxURL}';
  448. API.debugMode = '{$debugMode}';
  449. //=================================================//
  450. // INITIALISATION DU CMI DATA //
  451. //=================================================//
  452. API.cmi.learner_id = '{$learnerId}';
  453. API.cmi.learner_name = '{$learner_name}';
  454. API.cmi.completion_status = '{$completion_status}';
  455. API.cmi.success_status = '{$success_status}';
  456. API.cmi.completion_threshold= '{$completion_threshold}';
  457. API.cmi.credit = '{$credit}';
  458. API.cmi.entry = '{$entry}';
  459. API.cmi.exit = '{$exit}';
  460. API.cmi.suspend_data = '{$suspend_data}';
  461. API.cmi.launch_data = '{$launch_data}';
  462. API.cmi.mode = '{$mode}';
  463. API.cmi.location = '{$location}';
  464. API.cmi.total_time = '{$total_time}';
  465. API.cmi.session_time = '{$session_time}';
  466. API.cmi.progress_measure = '{$progress_measure}';
  467. API.cmi.scaled_passing_score= '{$scaled_passing_score}';
  468. API.cmi.max_time_allowed = '{$max_time_allowed}';
  469. API.cmi.time_limit_action = '{$time_limit_action}';
  470. API.cmi.score.scaled = '{$score_scaled}';
  471. API.cmi.score.raw = '{$score_raw}';
  472. API.cmi.score.max = '{$score_max}';
  473. API.cmi.score.min = '{$score_min}';
  474. API.cmi.learner_preference.audio_level = '{$learner_preference_audio_level}';
  475. API.cmi.learner_preference.language = '{$learner_preference_language}';
  476. API.cmi.learner_preference.delivery_speed = '{$learner_preference_delivery_speed}';
  477. API.cmi.learner_preference.audio_captioning = '{$learner_preference_audio_captioning}';
  478. EOT;
  479. //===================================================================================//
  480. // INITIALISATION DES ELEMENTS QUI CONTIENTS UN TABLEAUX DE VALEURS //
  481. //=================================================================================//
  482. // 1. Initialisation des data map de l'API
  483. $index = 0;
  484. foreach ($arrayDataMap as $__dataMap) {
  485. $targetID = $this->jsEscape($__dataMap['id']);
  486. $store = $this->jsEscape($__dataMap['store']);
  487. $apiString .=<<<EOT
  488. var __tmp = new ADLElements();
  489. __tmp.id ='{$targetID}';
  490. __tmp.store ='{$store}';
  491. API.adl.data[{$index}] = __tmp;
  492. EOT;
  493. $index++;
  494. }
  495. // 2. Initialisation des interactions de l'API
  496. $index = 0;
  497. foreach ($interactionList as $__interaction) {
  498. $interID = $this->jsEscape($__interaction['id']);
  499. $interType = $this->jsEscape($__interaction['type']);
  500. $interWeighting = $this->jsEscape($__interaction['weighting']);
  501. $interResult = $this->jsEscape($__interaction['result']);
  502. $interLearner_response = $this->jsEscape($__interaction['learner_response']);
  503. $interTimestamp = $this->jsEscape($__interaction['timestamp']);
  504. $interLatency = $this->jsEscape($__interaction['latency']);
  505. $interDescription = $this->jsEscape($__interaction['description']);
  506. // on recupère les réponses correctes.
  507. $correctResponses = $__interaction['correcte_responses'];
  508. $arrayCorrectResponses = explode('#', $correctResponses);
  509. $nbCorrectResponse = count($arrayCorrectResponses);
  510. $apiString .=<<<EOT
  511. var __tmpCorrectResponse = new Array();
  512. EOT;
  513. $i = 0;
  514. for ($i = 0; $i<$nbCorrectResponse; $i++) {
  515. $corResponse = $this->jsEscape($arrayCorrectResponses[$i]);
  516. $apiString .=<<<EOT
  517. var __tmpCrResponse = new Object();
  518. __tmpCrResponse.pattern = '{$corResponse}';
  519. __tmpCorrectResponse[{$i}] = __tmpCrResponse;
  520. EOT;
  521. }
  522. // on recupères les objectifs des intreactions.
  523. $interObjectives = $__interaction['objectives'];
  524. $apiString .=<<<EOT
  525. var arrayObjectif = new Array();
  526. EOT;
  527. $i = 0;
  528. foreach ($interObjectives as $__obj) {
  529. // informations sur les objectifs des interactions.
  530. $idObjectifOfInteraction = $this->jsEscape($__obj['id']);
  531. $apiString .=<<<EOT
  532. var __tmpObjectifs = new Object();
  533. __tmpObjectifs.id = '{$idObjectifOfInteraction}';
  534. arrayObjectif[{$i}] = __tmpObjectifs;
  535. EOT;
  536. $i++;
  537. }
  538. // informations des interactions.
  539. $apiString .=<<<EOT
  540. var __tmpInteraction = new CMIInteraction();
  541. __tmpInteraction.id = '{$interID}';
  542. __tmpInteraction.type = '{$interType}';
  543. __tmpInteraction.weighting = '{$interWeighting}';
  544. __tmpInteraction.result = '{$interResult}';
  545. __tmpInteraction.learner_response = '{$interLearner_response}';
  546. __tmpInteraction.timestamp = '{$interTimestamp}';
  547. __tmpInteraction.latency = '{$interLatency}';
  548. __tmpInteraction.description = '{$interDescription}';
  549. __tmpInteraction.objectives = arrayObjectif;
  550. __tmpInteraction.correct_responses = __tmpCorrectResponse;
  551. API.cmi.interactions[{$index}] = __tmpInteraction;
  552. EOT;
  553. $index++;
  554. }
  555. // 3. Initialisation des objectifs de l'API
  556. $index = 0;
  557. foreach ($listObjectives as $__objective) {
  558. // données relatives au score de l'objectif.
  559. $score = $__objective['score'];
  560. $scaled = $this->jsEscape($score->scaled);
  561. $raw = $this->jsEscape($score->raw);
  562. $min = $this->jsEscape($score->min);
  563. $max = $this->jsEscape($score->max);
  564. // données relatives à l'objectif.
  565. $id = $this->jsEscape($__objective['id']);
  566. $success_status = $this->jsEscape($__objective['success_status']);
  567. $completion_status = $this->jsEscape($__objective['completion_status']);
  568. $progress_measure = $this->jsEscape($__objective['progress_measure']);
  569. $description = $this->jsEscape($__objective['description']);
  570. // on initialise les données de l'API.
  571. $apiString .=<<<EOT
  572. var tmpScore = new Object();
  573. tmpScore.scaled = '{$scaled}';
  574. tmpScore.raw = '{$raw}';
  575. tmpScore.min = '{$min}';
  576. tmpScore.max = '{$max}';
  577. var tmpObjective = new CMIObjective();
  578. tmpObjective.id = '{$id}';
  579. tmpObjective.success_status = '{$success_status}';
  580. tmpObjective.completion_status = '{$completion_status}';
  581. tmpObjective.progress_measure = '{$progress_measure}';
  582. tmpObjective.description = '{$description}';
  583. tmpObjective.score = tmpScore;
  584. API.cmi.objectives[{$index}] = tmpObjective;
  585. EOT;
  586. $index++;
  587. }
  588. // 4. Initialisation des commentaire.
  589. // 4.1. commentaire du LMS : comments_from_lms.
  590. $index = 0;
  591. foreach ($arrayLmsComments as $lmsComment) {
  592. $comment = $this->jsEscape($lmsComment->comment);
  593. $location = $this->jsEscape($lmsComment->location);
  594. $timestamp = $this->jsEscape($lmsComment->timestamp);
  595. $apiString .=<<<EOT
  596. var lmsComment = new CMIComment();
  597. lmsComment.comment = '{$comment}';
  598. lmsComment.location = '{$location}';
  599. lmsComment.timestamp = '{$timestamp}';
  600. API.cmi.comments_from_lms[{$index}] = lmsComment;
  601. EOT;
  602. $index++;
  603. }
  604. // 4.2. commentaire de l'apprenant : comments_from_learner.
  605. $index = 0;
  606. foreach ($arrayLearnerComments as $learnerComment) {
  607. $comment = $this->jsEscape($learnerComment->comment);
  608. $location = $this->jsEscape($learnerComment->location);
  609. $timestamp = $this->jsEscape($learnerComment->timestamp);
  610. $apiString .=<<<EOT
  611. var learnerComment = new CMIComment();
  612. learnerComment.comment = '{$comment}';
  613. learnerComment.location = '{$location}';
  614. learnerComment.timestamp = '{$timestamp}';
  615. API.cmi.comments_from_learner[{$index}] = learnerComment;
  616. EOT;
  617. $index++;
  618. }
  619. // $apiString .=<<<EOT
  620. // window.setInterval(periodical, 20000);
  621. // function periodical() {
  622. // API_1484_11.Commit("");
  623. // }
  624. // EOT;
  625. /* // 4.3 Gestion de la fermeture de la page
  626. $apiString .=<<<EOT
  627. //Au cas d'une sortie inatendu du navigateur, par exemple clique sur le bouton [x] du navigateur
  628. //il a été décidé de sauvgarder les données actuelles. Afin de gérer ça, l'evenement onbeforeunload unload
  629. //est utilisé qui nous permet de faire le traitement qu'on veut (sauvgarde des données) juste avant la fermeture du navigateur
  630. window.onbeforeunload = function ()
  631. {
  632. SaveData();
  633. }
  634. // Fonction qui permet de rechercher l'api et sauvgarder l'etat actuel du systeme.
  635. function SaveData()
  636. {
  637. // on s'assure que l'api existe et que la connexion n'a pas été fermé.
  638. if (API_1484_11 == null || API_1484_11 == 'undefined' || API_1484_11.terminated == true)
  639. {
  640. return;
  641. }
  642. API_1484_11.SetValue("cmi.exit", "normal");
  643. API_1484_11.Commit("");
  644. API_1484_11.SetValue("adl.nav.request", "suspendAll");
  645. if (API_1484_11.processedRequest || API_1484_11.terminated)
  646. {
  647. return;
  648. }
  649. API_1484_11.processRequest();
  650. }
  651. EOT; */
  652. $apiString .=<<<EOT
  653. </script>
  654. EOT;
  655. return $apiString;
  656. }
  657. public function getSmallAPI()
  658. {
  659. $mode = 'browse';
  660. $mode = $this->jsEscape($mode);
  661. $apiString =<<<EOT
  662. <script>
  663. EOT;
  664. $cmiStructure = file_get_contents(dirname(__FILE__).'/../Resources/public/js/CMIStructure.js');
  665. $scorm2004 = file_get_contents(dirname(__FILE__).'/../Resources/public/js/scorm2004.js');
  666. $apiString .= $cmiStructure;
  667. $apiString .= $scorm2004;
  668. $apiString .=<<<EOT
  669. //=================================================//
  670. // CREATION DE L'API SCORM 2004 //
  671. //=================================================//
  672. var API = new ScormApi();
  673. var API_1484_11 = API ;
  674. console.log('API_1484_11');
  675. console.log('API_1484_11: '+API_1484_11);
  676. //=================================================//
  677. // INITIALISATION DU CMI DATA //
  678. //=================================================//
  679. API.cmi.mode = '{$mode}';
  680. </script>
  681. EOT;
  682. return $apiString;
  683. }
  684. /**
  685. * encode les chaines de caractères en js friendly
  686. */
  687. protected function jsEscape(?string $value)
  688. {
  689. // quote
  690. $value = str_replace('"', '\u0022', $value);
  691. $value = str_replace("'", '\u0027', $value);
  692. // Retours chariot
  693. $value = str_replace("\n", '\u000A', $value);
  694. $value = str_replace("\r", '\u000D', $value);
  695. // UTF-8
  696. $value = UnicodeTools::strToUTF8($value);
  697. return $value;
  698. }
  699. /**
  700. * Retourne l'inscription
  701. *
  702. * @param string $registrationKey
  703. *
  704. * @return Registration
  705. */
  706. protected function requireRegistration(string $registrationKey)
  707. {
  708. $em = $this->getDoctrine()->getManager();
  709. $repository = $em->getRepository(Registration::class);
  710. $registration = $repository->findOneBy(array('registrationKey' => $registrationKey));
  711. return $registration;
  712. }
  713. /**
  714. *
  715. * @throws \LogicException
  716. * @return ManagerRegistry
  717. */
  718. protected function getDoctrine(): ManagerRegistry
  719. {
  720. if (!$this->container->has('doctrine')) {
  721. throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".');
  722. }
  723. return $this->container->get('doctrine');
  724. }
  725. }