5 declare(strict_types=1);
41 public const CONVERT_XSL =
'./Modules/Scorm2004/templates/xsl/op/scorm12To2004.xsl';
42 public const WRAPPER_HTML =
'./Modules/Scorm2004/scripts/converter/GenericRunTimeWrapper1.0_aadlc/GenericRunTimeWrapper.htm';
43 public const WRAPPER_JS =
'./Modules/Scorm2004/scripts/converter/GenericRunTimeWrapper1.0_aadlc/SCOPlayerWrapper.js';
50 public function __construct(
int $a_id = 0,
bool $a_call_by_reference =
true)
54 $this->
lng = $DIC->language();
55 $this->error = $DIC[
"ilErr"];
56 $this->db = $DIC->database();
58 $this->
user = $DIC->user();
59 $this->
tabs = $DIC->tabs();
71 $this->import_sequencing = $a_val;
94 if (!function_exists(
'json_encode') || !function_exists(
'json_decode')) {
98 $needs_convert =
false;
105 $check_for_manifest_file = is_file($manifest_file);
110 if (!$check_for_manifest_file) {
111 $ilErr->raiseError($this->
lng->txt(
"Manifestfile $manifest_file not found!"),
$ilErr->MESSAGE);
116 if ($check_for_manifest_file) {
117 $manifest_file_array = file($manifest_file);
119 foreach ($manifest_file_array as $mfa) {
121 if (@iconv(
'UTF-8',
'UTF-8', $mfa) != $mfa) {
122 $needs_convert =
true;
130 $estimated_manifest_filesize = filesize($manifest_file) * 2;
134 $check_disc_free = 2;
139 if ($needs_convert) {
141 if ($check_for_manifest_file && ($check_disc_free > 1)) {
143 if (!copy($manifest_file, $manifest_file .
".old")) {
144 echo
"Failed to copy $manifest_file...<br>\n";
149 $f_write_handler = fopen($manifest_file .
".new",
"w");
150 $f_read_handler = fopen($manifest_file .
".old",
"r");
151 while (!feof($f_read_handler)) {
152 $zeile = fgets($f_read_handler);
154 fwrite($f_write_handler, mb_convert_encoding($zeile,
"UTF-8", mb_detect_encoding($zeile)));
156 fclose($f_read_handler);
157 fclose($f_write_handler);
160 if (!copy($manifest_file .
".new", $manifest_file)) {
161 echo
"Failed to copy $manifest_file...<br>\n";
164 if (!@is_file($manifest_file)) {
165 $ilErr->raiseError($this->
lng->txt(
"cont_no_manifest"),
$ilErr->WARNING);
170 if (!($check_disc_free > 1)) {
171 $ilErr->raiseError($this->
lng->txt(
"Not enough space left on device!"),
$ilErr->MESSAGE);
177 $hmani = fopen($manifest_file,
"r");
178 $start = fread($hmani, 3);
179 if (strtolower(bin2hex($start)) ===
"efbbbf") {
180 $f_write_handler = fopen($manifest_file .
".new",
"w");
181 while (!feof($hmani)) {
182 $n = fread($hmani, 900);
183 fwrite($f_write_handler, $n);
185 fclose($f_write_handler);
189 if (!copy($manifest_file .
".new", $manifest_file)) {
190 echo
"Failed to copy $manifest_file...<br>\n";
206 $out = file_get_contents($this->imsmanifestFile);
207 $check =
'/xmlns="http:\/\/www.imsglobal.org\/xsd\/imscp_v1p1"/';
208 $replace =
"xmlns=\"http://www.imsproject.org/xsd/imscp_rootv1p1p2\"";
210 file_put_contents($this->imsmanifestFile,
$out);
219 ##check manifest-file for version. Check for schemaversion as this is a required element for SCORM 2004 220 ##accept 2004 3rd Edition an CAM 1.3 as valid schemas 224 $this->imsmanifestFile = $manifest;
225 $doc =
new DomDocument();
229 $doc->load($this->imsmanifestFile);
230 $elements = $doc->getElementsByTagName(
"schemaversion");
232 if (isset($elements->item(0)->nodeValue)) {
233 $schema = $elements->item(0)->nodeValue;
235 if (strtolower(trim($schema)) ===
"cam 1.3" || strtolower(trim($schema)) ===
"2004 3rd edition" || strtolower(trim($schema)) ===
"2004 4th edition") {
245 $organizations = $doc->getElementsByTagName(
"organizations");
247 if ($organizations->item(0) == null) {
248 die(
"organizations missing in manifest");
250 $default = $organizations->item(0)->getAttribute(
"default");
251 if ($default ==
"" || $default == null) {
253 $organization = $doc->getElementsByTagName(
"organization");
254 $item = $organization->item(0);
255 if ($item !== null) {
256 $ident = $item->getAttribute(
"identifier");
257 $organizations->item(0)->setAttribute(
"default", $ident);
265 $wrapperdir = $this->packageFolder .
"/GenericRunTimeWrapper1.0_aadlc";
266 if (!mkdir($wrapperdir) && !is_dir($wrapperdir)) {
267 throw new \RuntimeException(sprintf(
'Directory "%s" was not created', $wrapperdir));
269 copy(self::WRAPPER_HTML, $wrapperdir .
"/GenericRunTimeWrapper.htm");
270 copy(self::WRAPPER_JS, $wrapperdir .
"/SCOPlayerWrapper.js");
273 $this->backupManifest = $this->packageFolder .
"/imsmanifest.xml.back";
274 $ret = copy($this->imsmanifestFile, $this->backupManifest);
277 $this->totransform = $doc;
278 $ilLog->write(
"SCORM: about to transform to SCORM 2004");
282 $xsl->load(self::CONVERT_XSL);
283 $prc =
new XSLTProcessor();
284 $r = @$prc->importStyleSheet($xsl);
286 file_put_contents($this->imsmanifestFile, $prc->transformToXML($this->totransform));
288 $ilLog->write(
"SCORM: Transformation completed");
298 $ilDB = $DIC->database();
300 $result =
$ilDB->queryF(
302 SELECT MAX(c_timestamp) last_access 303 FROM cmi_node, cp_node 304 WHERE cmi_node.cp_node_id = cp_node.cp_node_id 305 AND cp_node.slm_id = %s 307 GROUP BY c_timestamp',
308 array(
'integer',
'integer'),
309 array($a_obj_id, $a_usr_id)
311 if (
$ilDB->numRows($result)) {
312 $row =
$ilDB->fetchAssoc($result);
313 return (
string) $row[
"last_access"];
324 foreach ($a_users as $user) {
341 $sco_set =
$ilDB->queryF(
343 SELECT DISTINCT cmi_node.cp_node_id id 344 FROM cp_node, cmi_node 346 AND cp_node.cp_node_id = cmi_node.cp_node_id 347 ORDER BY cmi_node.cp_node_id ',
349 array($this->
getId())
354 while ($sco_rec =
$ilDB->fetchAssoc($sco_set)) {
355 $item[
'id'] = $sco_rec[
"id"];
356 $item[
'title'] = self::_lookupItemTitle((
int) $sco_rec[
"id"]);
374 $val_set =
$ilDB->queryF(
375 'SELECT cp_node_id FROM cp_node 377 AND cp_node.slm_id = %s',
378 array(
'text',
'integer'),
379 array(
'item',$this->
getId())
381 while ($val_rec =
$ilDB->fetchAssoc($val_set)) {
382 $scos[] = $val_rec[
'cp_node_id'];
385 foreach ($scos as $sco) {
386 $data_set =
$ilDB->queryF(
388 SELECT c_timestamp last_access, total_time, success_status, completion_status, 389 c_raw, scaled, cp_node_id 391 WHERE cp_node_id = %s 393 array(
'integer',
'integer'),
394 array($sco,$a_user_id)
397 while ($data_rec =
$ilDB->fetchAssoc($data_set)) {
398 if ($data_rec[
"success_status"] !=
"" && $data_rec[
"success_status"] !==
"unknown") {
399 $status = $data_rec[
"success_status"];
401 if ($data_rec[
"completion_status"] ==
"") {
404 $status = $data_rec[
"completion_status"];
410 if ($data_rec[
"c_raw"] != null) {
411 $score = $data_rec[
"c_raw"];
412 if ($data_rec[
"scaled"] != null) {
416 if ($data_rec[
"scaled"] != null) {
417 $score .= ($data_rec[
"scaled"] * 100) .
"%";
419 $title = self::_lookupItemTitle((
int) $data_rec[
"cp_node_id"]);
421 $data[] = array(
"sco_id" => $data_rec[
"cp_node_id"],
422 "score" => $score,
"time" => $time,
"status" => $status,
"last_access" => $last_access,
"title" =>
$title);
424 $data_rec[
"total_time"] = self::_ISODurationToCentisec($data_rec[
"total_time"]) / 100;
425 $data[$data_rec[
"cp_node_id"]] = $data_rec;
439 $val_set =
$ilDB->queryF(
440 'SELECT package_attempts FROM sahs_user WHERE user_id = %s AND obj_id = %s',
441 array(
'integer',
'integer'),
442 array($a_user_id, $this->
getId())
445 $val_rec =
$ilDB->fetchAssoc($val_set);
447 if ($val_rec[
"package_attempts"] == null) {
448 $val_rec[
"package_attempts"] = 0;
451 return (
int) $val_rec[
"package_attempts"];
460 $val_set =
$ilDB->queryF(
461 'SELECT module_version FROM sahs_user WHERE user_id = %s AND obj_id = %s',
462 array(
'integer',
'integer'),
463 array($a_user_id, $this->
getId())
466 $val_rec =
$ilDB->fetchAssoc($val_set);
468 if ($val_rec[
"module_version"] == null) {
469 $val_rec[
"module_version"] =
"";
471 return $val_rec[
"module_version"];
480 $collection = $olp->getCollectionInstance();
482 $scos = $collection->getItems();
485 $fhandle = fopen($a_file,
"rb");
487 $obj_id = $this->getID();
489 $usersToDelete = array();
490 $fields = fgetcsv($fhandle, 4096,
';');
491 while (($csv_rows = fgetcsv($fhandle, 4096,
";")) !==
false) {
493 $data = array_combine($fields, $csv_rows);
495 if (isset(
$data[
"Login"])) {
498 if (isset(
$data[
"login"])) {
502 if (isset(
$data[
"user"]) && is_numeric(
$data[
"user"])) {
507 if (isset(
$data[
'LastAccess']) &&
$data[
'LastAccess']) {
509 } elseif (isset(
$data[
'Date']) &&
$data[
'Date']) {
515 if (isset(
$data[
"Status"])) {
516 if (is_numeric(
$data[
"Status"])) {
517 $status =
$data[
"Status"];
527 if (isset(
$data[
"Attempts"])) {
528 $attempts = (
int)
$data[
"Attempts"];
531 $percentage_completed = 0;
533 $percentage_completed = 100;
534 } elseif (isset(
$data[
'percentageCompletedSCOs'])) {
535 $percentage_completed = (
int)
$data[
'percentageCompletedSCOs'];
538 $sco_total_time_sec = null;
539 if (isset(
$data[
'SumTotal_timeSeconds'])) {
540 $sco_total_time_sec = (
int)
$data[
'SumTotal_timeSeconds'];
544 $usersToDelete[] = $user_id;
546 $this->
importSuccessForSahsUser($user_id, $last_access, $status, $attempts, $percentage_completed, $sco_total_time_sec);
551 foreach ($scos as $sco_id) {
554 SELECT completion_status, success_status, user_id FROM cmi_node WHERE cp_node_id = %s AND user_id = %s',
555 array(
'integer',
'integer'),
556 array($sco_id,$user_id)
560 $nextId =
$ilDB->nextId(
'cmi_node');
561 $val_set =
$ilDB->manipulateF(
562 'INSERT INTO cmi_node 563 (cp_node_id,user_id,completion_status,c_timestamp,cmi_node_id) 564 VALUES(%s,%s,%s,%s,%s)',
565 array(
'integer',
'integer',
'text',
'timestamp',
'integer'),
566 [$sco_id, $user_id,
'completed', $last_access?->format(
'Y-m-d H:i:s'), $nextId]
571 if (($row[
"completion_status"] ===
"completed" && $row[
"success_status"] !==
"failed") || $row[
"success_status"] ===
"passed") {
572 if ($doUpdate !=
true) {
579 if ($doUpdate ==
true) {
583 'completion_status' => array(
'text',
'completed'),
584 'success_status' => array(
'text',
''),
585 'suspend_data' => array(
'text',
''),
586 'c_timestamp' => array(
'timestamp', $last_access?->format(
'Y-m-d H:i:s')),
589 'user_id' => array(
'integer', $user_id),
590 'cp_node_id' => array(
'integer', $sco_id)
602 if (count($usersToDelete) > 0) {
617 $aV = array(0, 0, 0, 0, 0, 0);
620 if (strpos($str,
"P") != 0) {
624 $aT = array(
"Y",
"M",
"D",
"H",
"M",
"S");
627 $str = substr($str, 1);
628 for ($i = 0, $max = count($aT); $i < $max; $i++) {
629 if (strpos($str,
"T") === 0) {
630 $str = substr($str, 1);
634 $p = strpos($str, $aT[$i]);
637 if ($i == 1 && strpos($str,
"T") > -1 && strpos($str,
"T") < $p) {
640 if ($aT[$i] ===
"S") {
641 $aV[$i] = substr($str, 0, $p);
643 $aV[$i] = intval(substr($str, 0, $p));
645 if (!is_numeric($aV[$i])) {
650 if ($i > 2 && !$bTFound) {
654 $str = substr($str, $p + 1);
657 if (!$bErr && strlen($str) != 0) {
665 return $aV[0] * 3_155_760_000 + $aV[1] * 262_980_000 + $aV[2] * 8_640_000 + $aV[3] * 360000 + $aV[4] * 6000 + round($aV[5] * 100);
671 $val_set = $DIC->database()->queryF(
673 SELECT distinct(cp_node.cp_node_id) FROM cp_node,cp_resource,cp_item 674 WHERE cp_item.cp_node_id = cp_node.cp_node_id 675 AND cp_item.resourceid = cp_resource.id 678 AND cp_node.slm_id = %s ',
679 array(
'text',
'text',
'integer'),
680 array(
'sco',
'item',$a_slm_id)
682 return $DIC->database()->numRows($val_set);
693 $ilDB = $DIC->database();
694 $ilUser = $DIC->user();
698 $val_set =
$ilDB->queryF(
700 SELECT cp_node.cp_node_id FROM cp_node,cp_resource,cp_item 701 WHERE cp_item.cp_node_id = cp_node.cp_node_id 702 AND cp_item.resourceid = cp_resource.id 705 AND cp_node.slm_id = %s',
706 array(
'text',
'text',
'integer'),
707 array(
'sco' ,
'item',$a_id)
709 while ($val_rec =
$ilDB->fetchAssoc($val_set)) {
710 $scos[] = $val_rec[
'cp_node_id'];
716 foreach ($scos as $i => $value) {
717 $val_set =
$ilDB->queryF(
719 SELECT * FROM cmi_node 722 AND (completion_status = %s OR success_status = %s))',
723 array(
'integer',
'integer',
'text',
'text'),
724 array($a_user, $value,
'completed',
'passed')
727 if (
$ilDB->numRows($val_set) > 0) {
729 $key = array_search($value, $scos_c);
730 unset($scos_c[
$key]);
734 if (count($scos_c) == 0) {
751 $ilDB = $DIC->database();
752 $ilUser = $DIC->user();
755 $val_set =
$ilDB->queryF(
756 "SELECT cp_node.cp_node_id FROM cp_node,cp_resource,cp_item WHERE" .
757 " cp_item.cp_node_id=cp_node.cp_node_id AND cp_item.resourceId = cp_resource.id AND scormType='sco' AND nodeName='item' AND cp_node.slm_id = %s GROUP BY cp_node.cp_node_id",
761 while ($val_rec =
$ilDB->fetchAssoc($val_set)) {
762 $scos[] = $val_rec[
'cp_node_id'];
766 foreach ($scos as $i => $iValue) {
767 $val_set =
$ilDB->queryF(
768 "SELECT scaled FROM cmi_node WHERE (user_id = %s AND cp_node_id = %s)",
769 array(
'integer',
'integer'),
770 array($a_user, $scos[$i])
772 if ($val_set->numRows() > 0) {
773 $val_rec =
$ilDB->fetchAssoc($val_set);
774 if ($val_rec[
'scaled'] != null) {
776 $scaled = $val_rec[
'scaled'];
780 return ($set == 1) ? $scaled : -1;
792 $ilDB = $DIC->database();
795 $item_set =
$ilDB->queryF(
797 SELECT cp_item.* FROM cp_node, cp_item WHERE slm_id = %s 798 AND cp_node.cp_node_id = cp_item.cp_node_id 799 ORDER BY cp_node.cp_node_id ',
805 while ($item_rec =
$ilDB->fetchAssoc($item_set)) {
808 SELECT cp_resource.* FROM cp_node, cp_resource 810 AND cp_node.cp_node_id = cp_resource.cp_node_id 811 AND cp_resource.id = %s ',
812 array(
'integer',
'text'),
813 array($a_obj_id,$item_rec[
"resourceid"])
818 if (
$res[
"scormtype"] ===
"sco") {
819 $items[] = array(
"id" => $item_rec[
"cp_node_id"],
820 "title" => $item_rec[
"title"]);
828 public static function _getStatus(
int $a_obj_id,
int $a_user_id): bool|string
832 $ilDB = $DIC->database();
834 $status_set =
$ilDB->queryF(
836 SELECT * FROM cmi_gobjective 838 AND objective_id = %s 840 array(
'integer',
'text',
'integer'),
841 array($a_obj_id,
'-course_overall_status-',$a_user_id)
844 if ($status_rec =
$ilDB->fetchAssoc($status_set)) {
845 return $status_rec[
"status"];
851 public static function _getSatisfied(
int $a_obj_id,
int $a_user_id): bool|string
855 $ilDB = $DIC->database();
858 $status_set =
$ilDB->queryF(
860 SELECT * FROM cmi_gobjective 862 AND objective_id = %s 864 array(
'integer',
'text',
'integer'),
865 array($a_obj_id,
'-course_overall_status-',$a_user_id)
868 if ($status_rec =
$ilDB->fetchAssoc($status_set)) {
869 return $status_rec[
"satisfied"];
875 public static function _getMeasure(
int $a_obj_id,
int $a_user_id): float|bool
879 $ilDB = $DIC->database();
881 $status_set =
$ilDB->queryF(
883 SELECT * FROM cmi_gobjective 885 AND objective_id = %s 887 array(
'integer',
'text',
'integer'),
888 array($a_obj_id,
'-course_overall_status-',$a_user_id)
891 if ($status_rec =
$ilDB->fetchAssoc($status_set)) {
892 return (
float) $status_rec[
"measure"];
902 $ilDB = $DIC->database();
906 SELECT * FROM cp_item 907 WHERE cp_node_id = %s',
912 if ($i =
$ilDB->fetchAssoc(
$r)) {
925 $ilDB = $DIC->database();
929 $result =
$ilDB->query(
930 'SELECT cp_node.cp_node_id ' 931 .
'FROM cp_node, cp_resource, cp_item ' 932 .
'WHERE cp_item.cp_node_id = cp_node.cp_node_id ' 933 .
'AND cp_item.resourceId = cp_resource.id ' 934 .
'AND scormType = ' .
$ilDB->quote(
'sco',
'text') .
' ' 935 .
'AND nodeName = ' .
$ilDB->quote(
'item',
'text') .
' ' 936 .
'AND cp_node.slm_id = ' .
$ilDB->quote($a_id,
'integer') .
' ' 937 .
'GROUP BY cp_node.cp_node_id' 940 while ($row =
$ilDB->fetchAssoc($result)) {
941 $scos[] = $row[
'cp_node_id'];
946 foreach ($scos as $i => $value) {
948 'SELECT c_max FROM cmi_node WHERE (user_id = %s AND cp_node_id = %s)',
949 array(
'integer',
'integer'),
950 array($a_user, $value)
955 if ($row[
'c_max'] != null) {
957 $max = floatval($row[
'c_max']);
961 return ($set == 1) ? $max : null;
971 $ilDB = $DIC->database();
972 $retAr = array(
"raw" => null,
"max" => null,
"scaled" => null);
973 $val_set =
$ilDB->queryF(
974 "SELECT c_raw, c_max, scaled FROM cmi_node WHERE (user_id = %s AND cp_node_id = %s)",
975 array(
'integer',
'integer'),
976 array($a_user, $a_cp_node_id)
978 if ($val_set->numRows() > 0) {
979 $val_rec =
$ilDB->fetchAssoc($val_set);
980 $retAr[
"raw"] = $val_rec[
'c_raw'];
981 $retAr[
"max"] = $val_rec[
'c_max'];
982 $retAr[
"scaled"] = $val_rec[
'scaled'];
983 if ($val_rec[
'scaled'] == null && $val_rec[
'c_raw'] != null && $val_rec[
'c_max'] != null) {
984 $retAr[
"scaled"] = ($val_rec[
'c_raw'] / $val_rec[
'c_max']);
const LP_STATUS_COMPLETED_NUM
get_user_id(string $a_login)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
importSuccess(string $a_file)
static getLogger(string $a_component_id)
Get component logger.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
txt(string $a_topic, string $a_default_lang_fallback_mod="")
gets the text for a given topic if the topic is not in the list, the topic itself with "-" will be re...
static _getStatus(int $a_obj_id, int $a_user_id)
kindlyToDateTime(string $format, string $maybe_datetime, ?DateTimeImmutable $default=null)
const LP_STATUS_NOT_ATTEMPTED
static _getSatisfied(int $a_obj_id, int $a_user_id)
const LP_STATUS_IN_PROGRESS_NUM
static formatDate(ilDateTime $date, bool $a_skip_day=false, bool $a_include_wd=false, bool $include_seconds=false)
deleteTrackingDataOfUsers(array $a_users)
static _ISODurationToCentisec(string $str)
convert ISO 8601 Timeperiods to centiseconds
static _lookupLastAccess(int $a_obj_id, int $a_usr_id)
Return the last access timestamp for a given user.
setImportSequencing(bool $a_val)
Set import sequencing.
static secondsToString(int $seconds, bool $force_with_seconds=false, ?ilLanguage $a_lng=null)
converts seconds to string: Long: 7 days 4 hour(s) ...
getAttemptsForUser(int $a_user_id)
get number of atttempts for a certain user and package
const LP_STATUS_IN_PROGRESS
static _getMeasure(int $a_obj_id, int $a_user_id)
getTrackedItems()
get all tracked items of current user
static _getUniqueScaledScoreForUser(int $a_id, int $a_user)
Get the Unique Scaled Score of a course Conditions: Only one SCO may set cmi.score.scaled.
static getQuantityOfSCOs(int $a_slm_id)
static _lookupItemTitle(int $a_node_id)
static _refreshStatus(int $a_obj_id, ?array $a_users=null)
__construct(int $a_id=0, bool $a_call_by_reference=true)
Constructor.
static _getScores2004ForUser(int $a_cp_node_id, int $a_user)
const LP_STATUS_NOT_ATTEMPTED_NUM
getDataDirectory(?string $mode="filesystem")
get data directory of lm
getModuleVersionForUser(int $a_user_id)
get module version that tracking data for a user was recorded on
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static _getCourseCompletionForUser(int $a_id, int $a_user)
Get the completion of a SCORM module for a given user.
static _getTrackingItems(int $a_obj_id)
get all tracking items of scorm object currently a for learning progress only
static _deleteReadEventsForUsers(int $a_obj_id, array $a_user_ids)
getTrackingDataAgg(int $a_user_id, ?bool $raw=false)
importSuccessForSahsUser(int $user_id, ?DateTimeImmutable $last_access, int $status, ?int $attempts=null, ?int $percentage_completed=null, ?int $sco_total_time_sec=null)
convert_1_2_to_2004(string $manifest)
readObject()
read manifest file
static getInstance(int $obj_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static removeCMIDataForUserAndPackage(int $user_id, int $packageId)
static _getMaxScoreForUser(int $a_id, int $a_user)
Returns score.max for the learning module, refered to the last sco where score.max is set...
const LP_STATUS_FAILED_NUM
static _updateStatus(int $a_obj_id, int $a_usr_id, ?object $a_obj=null, bool $a_percentage=false, bool $a_force_raise=false)
getImportSequencing()
Get import sequencing.