20 declare(strict_types=1);
40 public const CONVERT_XSL =
'../components/ILIAS/Scorm2004/templates/xsl/op/scorm12To2004.xsl';
41 public const WRAPPER_HTML =
'../components/ILIAS/Scorm2004/scripts/converter/GenericRunTimeWrapper1.0_aadlc/GenericRunTimeWrapper.htm';
42 public const WRAPPER_JS =
'../components/ILIAS/Scorm2004/scripts/converter/GenericRunTimeWrapper1.0_aadlc/SCOPlayerWrapper.js';
49 public function __construct(
int $a_id = 0,
bool $a_call_by_reference =
true)
53 $this->
lng = $DIC->language();
54 $this->error = $DIC[
"ilErr"];
55 $this->db = $DIC->database();
57 $this->
user = $DIC->user();
58 $this->
tabs = $DIC->tabs();
70 $this->import_sequencing = $a_val;
93 if (!function_exists(
'json_encode') || !function_exists(
'json_decode')) {
97 $needs_convert =
false;
104 $check_for_manifest_file = is_file($manifest_file);
109 if (!$check_for_manifest_file) {
110 $ilErr->raiseError($this->
lng->txt(
"Manifestfile $manifest_file not found!"),
$ilErr->MESSAGE);
115 if ($check_for_manifest_file) {
116 $manifest_file_array = file($manifest_file);
118 foreach ($manifest_file_array as $mfa) {
120 if (@iconv(
'UTF-8',
'UTF-8', $mfa) != $mfa) {
121 $needs_convert =
true;
129 $estimated_manifest_filesize = filesize($manifest_file) * 2;
133 $check_disc_free = 2;
138 if ($needs_convert) {
140 if ($check_for_manifest_file && ($check_disc_free > 1)) {
142 if (!copy($manifest_file, $manifest_file .
".old")) {
143 echo
"Failed to copy $manifest_file...<br>\n";
148 $f_write_handler = fopen($manifest_file .
".new",
"w");
149 $f_read_handler = fopen($manifest_file .
".old",
"r");
150 while (!feof($f_read_handler)) {
151 $zeile = fgets($f_read_handler);
153 fwrite($f_write_handler, utf8_encode($zeile));
155 fclose($f_read_handler);
156 fclose($f_write_handler);
159 if (!copy($manifest_file .
".new", $manifest_file)) {
160 echo
"Failed to copy $manifest_file...<br>\n";
163 if (!@is_file($manifest_file)) {
164 $ilErr->raiseError($this->
lng->txt(
"cont_no_manifest"),
$ilErr->WARNING);
169 if (!($check_disc_free > 1)) {
170 $ilErr->raiseError($this->
lng->txt(
"Not enough space left on device!"),
$ilErr->MESSAGE);
176 $hmani = fopen($manifest_file,
"r");
177 $start = fread($hmani, 3);
178 if (strtolower(bin2hex($start)) ===
"efbbbf") {
179 $f_write_handler = fopen($manifest_file .
".new",
"w");
180 while (!feof($hmani)) {
181 $n = fread($hmani, 900);
182 fwrite($f_write_handler, $n);
184 fclose($f_write_handler);
188 if (!copy($manifest_file .
".new", $manifest_file)) {
189 echo
"Failed to copy $manifest_file...<br>\n";
205 $out = file_get_contents($this->imsmanifestFile);
206 $check =
'/xmlns="http:\/\/www.imsglobal.org\/xsd\/imscp_v1p1"/';
207 $replace =
"xmlns=\"http://www.imsproject.org/xsd/imscp_rootv1p1p2\"";
209 file_put_contents($this->imsmanifestFile,
$out);
218 ##check manifest-file for version. Check for schemaversion as this is a required element for SCORM 2004 219 ##accept 2004 3rd Edition an CAM 1.3 as valid schemas 223 $this->imsmanifestFile = $manifest;
224 $doc =
new DomDocument();
228 $doc->load($this->imsmanifestFile);
229 $elements = $doc->getElementsByTagName(
"schemaversion");
231 if (isset($elements->item(0)->nodeValue)) {
232 $schema = $elements->item(0)->nodeValue;
234 if (strtolower(trim($schema)) ===
"cam 1.3" || strtolower(trim($schema)) ===
"2004 3rd edition" || strtolower(trim($schema)) ===
"2004 4th edition") {
244 $organizations = $doc->getElementsByTagName(
"organizations");
246 if ($organizations->item(0) ==
null) {
247 die(
"organizations missing in manifest");
249 $default = $organizations->item(0)->getAttribute(
"default");
250 if ($default ==
"" || $default ==
null) {
252 $organization = $doc->getElementsByTagName(
"organization");
253 $item = $organization->item(0);
254 if ($item !==
null) {
255 $ident = $item->getAttribute(
"identifier");
256 $organizations->item(0)->setAttribute(
"default", $ident);
264 $wrapperdir = $this->packageFolder .
"/GenericRunTimeWrapper1.0_aadlc";
265 if (!mkdir($wrapperdir) && !is_dir($wrapperdir)) {
266 throw new \RuntimeException(sprintf(
'Directory "%s" was not created', $wrapperdir));
268 copy(self::WRAPPER_HTML, $wrapperdir .
"/GenericRunTimeWrapper.htm");
269 copy(self::WRAPPER_JS, $wrapperdir .
"/SCOPlayerWrapper.js");
272 $this->backupManifest = $this->packageFolder .
"/imsmanifest.xml.back";
273 $ret = copy($this->imsmanifestFile, $this->backupManifest);
276 $this->totransform = $doc;
277 $ilLog->write(
"SCORM: about to transform to SCORM 2004");
281 $xsl->load(self::CONVERT_XSL);
282 $prc =
new XSLTProcessor();
283 $r = @$prc->importStyleSheet($xsl);
285 file_put_contents($this->imsmanifestFile, $prc->transformToXML($this->totransform));
287 $ilLog->write(
"SCORM: Transformation completed");
297 $ilDB = $DIC->database();
299 $result =
$ilDB->queryF(
301 SELECT MAX(c_timestamp) last_access 302 FROM cmi_node, cp_node 303 WHERE cmi_node.cp_node_id = cp_node.cp_node_id 304 AND cp_node.slm_id = %s 306 GROUP BY c_timestamp',
307 array(
'integer',
'integer'),
308 array($a_obj_id, $a_usr_id)
310 if (
$ilDB->numRows($result)) {
311 $row =
$ilDB->fetchAssoc($result);
312 return (
string) $row[
"last_access"];
323 foreach ($a_users as $user) {
340 $sco_set =
$ilDB->queryF(
342 SELECT DISTINCT cmi_node.cp_node_id id 343 FROM cp_node, cmi_node 345 AND cp_node.cp_node_id = cmi_node.cp_node_id 346 ORDER BY cmi_node.cp_node_id ',
348 array($this->
getId())
353 while ($sco_rec =
$ilDB->fetchAssoc($sco_set)) {
354 $item[
'id'] = $sco_rec[
"id"];
355 $item[
'title'] = self::_lookupItemTitle((
int) $sco_rec[
"id"]);
373 $val_set =
$ilDB->queryF(
374 'SELECT cp_node_id FROM cp_node 376 AND cp_node.slm_id = %s',
377 array(
'text',
'integer'),
378 array(
'item',$this->
getId())
380 while ($val_rec =
$ilDB->fetchAssoc($val_set)) {
381 $scos[] = $val_rec[
'cp_node_id'];
384 foreach ($scos as $sco) {
385 $data_set =
$ilDB->queryF(
387 SELECT c_timestamp last_access, total_time, success_status, completion_status, 388 c_raw, scaled, cp_node_id 390 WHERE cp_node_id = %s 392 array(
'integer',
'integer'),
393 array($sco,$a_user_id)
396 while ($data_rec =
$ilDB->fetchAssoc($data_set)) {
397 if ($data_rec[
"success_status"] !=
"" && $data_rec[
"success_status"] !==
"unknown") {
398 $status = $data_rec[
"success_status"];
400 if ($data_rec[
"completion_status"] ==
"") {
403 $status = $data_rec[
"completion_status"];
409 if ($data_rec[
"c_raw"] !=
null) {
410 $score = $data_rec[
"c_raw"];
411 if ($data_rec[
"scaled"] !=
null) {
415 if ($data_rec[
"scaled"] !=
null) {
416 $score .= ($data_rec[
"scaled"] * 100) .
"%";
418 $title = self::_lookupItemTitle((
int) $data_rec[
"cp_node_id"]);
420 $data[] = array(
"sco_id" => $data_rec[
"cp_node_id"],
421 "score" => $score,
"time" => $time,
"status" => $status,
"last_access" => $last_access,
"title" =>
$title);
423 $data_rec[
"total_time"] = self::_ISODurationToCentisec($data_rec[
"total_time"]) / 100;
424 $data[$data_rec[
"cp_node_id"]] = $data_rec;
438 $val_set =
$ilDB->queryF(
439 'SELECT package_attempts FROM sahs_user WHERE user_id = %s AND obj_id = %s',
440 array(
'integer',
'integer'),
441 array($a_user_id, $this->
getId())
444 $val_rec =
$ilDB->fetchAssoc($val_set);
446 if ($val_rec[
"package_attempts"] ==
null) {
447 $val_rec[
"package_attempts"] = 0;
450 return (
int) $val_rec[
"package_attempts"];
459 $val_set =
$ilDB->queryF(
460 'SELECT module_version FROM sahs_user WHERE user_id = %s AND obj_id = %s',
461 array(
'integer',
'integer'),
462 array($a_user_id, $this->
getId())
465 $val_rec =
$ilDB->fetchAssoc($val_set);
467 if ($val_rec[
"module_version"] ==
null) {
468 $val_rec[
"module_version"] =
"";
470 return $val_rec[
"module_version"];
479 $collection = $olp->getCollectionInstance();
481 $scos = $collection->getItems();
484 $fhandle = fopen($a_file,
"rb");
486 $obj_id = $this->getID();
488 $usersToDelete = array();
489 $fields = fgetcsv($fhandle, 4096,
';',
'"',
'\\');
490 while (($csv_rows = fgetcsv($fhandle, 4096,
";",
'"',
'\\')) !==
false) {
492 $data = array_combine($fields, $csv_rows);
494 if (isset(
$data[
"Login"])) {
497 if (isset(
$data[
"login"])) {
501 if (isset(
$data[
"user"]) && is_numeric(
$data[
"user"])) {
506 if (isset(
$data[
'LastAccess']) &&
$data[
'LastAccess']) {
508 } elseif (isset(
$data[
'Date']) &&
$data[
'Date']) {
514 if (isset(
$data[
"Status"])) {
515 if (is_numeric(
$data[
"Status"])) {
516 $status =
$data[
"Status"];
526 if (isset(
$data[
"Attempts"])) {
527 $attempts = (
int)
$data[
"Attempts"];
530 $percentage_completed = 0;
532 $percentage_completed = 100;
533 } elseif (isset(
$data[
'percentageCompletedSCOs'])) {
534 $percentage_completed = (
int)
$data[
'percentageCompletedSCOs'];
537 $sco_total_time_sec =
null;
538 if (isset(
$data[
'SumTotal_timeSeconds'])) {
539 $sco_total_time_sec = (
int)
$data[
'SumTotal_timeSeconds'];
550 foreach ($scos as $sco_id) {
553 SELECT completion_status, success_status, user_id FROM cmi_node WHERE cp_node_id = %s AND user_id = %s',
554 array(
'integer',
'integer'),
559 $nextId =
$ilDB->nextId(
'cmi_node');
560 $val_set =
$ilDB->manipulateF(
561 'INSERT INTO cmi_node 562 (cp_node_id,user_id,completion_status,c_timestamp,cmi_node_id) 563 VALUES(%s,%s,%s,%s,%s)',
564 array(
'integer',
'integer',
'text',
'timestamp',
'integer'),
565 [$sco_id,
$user_id,
'completed', $last_access?->format(
'Y-m-d H:i:s'), $nextId]
570 if (($row[
"completion_status"] ===
"completed" && $row[
"success_status"] !==
"failed") || $row[
"success_status"] ===
"passed") {
571 if ($doUpdate !=
true) {
578 if ($doUpdate ==
true) {
582 'completion_status' => array(
'text',
'completed'),
583 'success_status' => array(
'text',
''),
584 'suspend_data' => array(
'text',
''),
585 'c_timestamp' => array(
'timestamp', $last_access?->format(
'Y-m-d H:i:s')),
588 'user_id' => array(
'integer',
$user_id),
589 'cp_node_id' => array(
'integer', $sco_id)
601 if (count($usersToDelete) > 0) {
616 $aV = array(0, 0, 0, 0, 0, 0);
619 if (strpos($str,
"P") != 0) {
623 $aT = array(
"Y",
"M",
"D",
"H",
"M",
"S");
626 $str = substr($str, 1);
627 for ($i = 0, $max = count($aT); $i < $max; $i++) {
628 if (strpos($str,
"T") === 0) {
629 $str = substr($str, 1);
633 $p = strpos($str, $aT[$i]);
636 if ($i == 1 && strpos($str,
"T") > -1 && strpos($str,
"T") < $p) {
639 if ($aT[$i] ===
"S") {
640 $aV[$i] = substr($str, 0, $p);
642 $aV[$i] = intval(substr($str, 0, $p));
644 if (!is_numeric($aV[$i])) {
649 if ($i > 2 && !$bTFound) {
653 $str = substr($str, $p + 1);
656 if (!$bErr && strlen($str) != 0) {
664 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);
670 $val_set = $DIC->database()->queryF(
672 SELECT distinct(cp_node.cp_node_id) FROM cp_node,cp_resource,cp_item 673 WHERE cp_item.cp_node_id = cp_node.cp_node_id 674 AND cp_item.resourceid = cp_resource.id 677 AND cp_node.slm_id = %s ',
678 array(
'text',
'text',
'integer'),
679 array(
'sco',
'item',$a_slm_id)
681 return $DIC->database()->numRows($val_set);
692 $ilDB = $DIC->database();
693 $ilUser = $DIC->user();
697 $val_set =
$ilDB->queryF(
699 SELECT cp_node.cp_node_id FROM cp_node,cp_resource,cp_item 700 WHERE cp_item.cp_node_id = cp_node.cp_node_id 701 AND cp_item.resourceid = cp_resource.id 704 AND cp_node.slm_id = %s',
705 array(
'text',
'text',
'integer'),
706 array(
'sco' ,
'item',$a_id)
708 while ($val_rec =
$ilDB->fetchAssoc($val_set)) {
709 $scos[] = $val_rec[
'cp_node_id'];
715 foreach ($scos as $i => $value) {
716 $val_set =
$ilDB->queryF(
718 SELECT * FROM cmi_node 721 AND (completion_status = %s OR success_status = %s))',
722 array(
'integer',
'integer',
'text',
'text'),
723 array($a_user, $value,
'completed',
'passed')
726 if (
$ilDB->numRows($val_set) > 0) {
728 $key = array_search($value, $scos_c);
729 unset($scos_c[$key]);
733 if (count($scos_c) == 0) {
750 $ilDB = $DIC->database();
751 $ilUser = $DIC->user();
754 $val_set =
$ilDB->queryF(
755 "SELECT cp_node.cp_node_id FROM cp_node,cp_resource,cp_item WHERE" .
756 " 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",
760 while ($val_rec =
$ilDB->fetchAssoc($val_set)) {
761 $scos[] = $val_rec[
'cp_node_id'];
765 foreach ($scos as $i => $iValue) {
766 $val_set =
$ilDB->queryF(
767 "SELECT scaled FROM cmi_node WHERE (user_id = %s AND cp_node_id = %s)",
768 array(
'integer',
'integer'),
769 array($a_user, $scos[$i])
771 if ($val_set->numRows() > 0) {
772 $val_rec =
$ilDB->fetchAssoc($val_set);
773 if ($val_rec[
'scaled'] !=
null) {
775 $scaled = $val_rec[
'scaled'];
779 return ($set == 1) ? $scaled : -1;
791 $ilDB = $DIC->database();
794 $item_set =
$ilDB->queryF(
796 SELECT cp_item.* FROM cp_node, cp_item WHERE slm_id = %s 797 AND cp_node.cp_node_id = cp_item.cp_node_id 798 ORDER BY cp_node.cp_node_id ',
804 while ($item_rec =
$ilDB->fetchAssoc($item_set)) {
807 SELECT cp_resource.* FROM cp_node, cp_resource 809 AND cp_node.cp_node_id = cp_resource.cp_node_id 810 AND cp_resource.id = %s ',
811 array(
'integer',
'text'),
812 array($a_obj_id,$item_rec[
"resourceid"])
817 if (
$res[
"scormtype"] ===
"sco") {
818 $items[] = array(
"id" => $item_rec[
"cp_node_id"],
819 "title" => $item_rec[
"title"]);
827 public static function _getStatus(
int $a_obj_id,
int $a_user_id): bool|string
831 $ilDB = $DIC->database();
833 $status_set =
$ilDB->queryF(
835 SELECT * FROM cmi_gobjective 837 AND objective_id = %s 839 array(
'integer',
'text',
'integer'),
840 array($a_obj_id,
'-course_overall_status-',$a_user_id)
843 if ($status_rec =
$ilDB->fetchAssoc($status_set)) {
844 return $status_rec[
"status"];
850 public static function _getSatisfied(
int $a_obj_id,
int $a_user_id): bool|string
854 $ilDB = $DIC->database();
857 $status_set =
$ilDB->queryF(
859 SELECT * FROM cmi_gobjective 861 AND objective_id = %s 863 array(
'integer',
'text',
'integer'),
864 array($a_obj_id,
'-course_overall_status-',$a_user_id)
867 if ($status_rec =
$ilDB->fetchAssoc($status_set)) {
868 return $status_rec[
"satisfied"];
874 public static function _getMeasure(
int $a_obj_id,
int $a_user_id): float|bool
878 $ilDB = $DIC->database();
880 $status_set =
$ilDB->queryF(
882 SELECT * FROM cmi_gobjective 884 AND objective_id = %s 886 array(
'integer',
'text',
'integer'),
887 array($a_obj_id,
'-course_overall_status-',$a_user_id)
890 if ($status_rec =
$ilDB->fetchAssoc($status_set)) {
891 return (
float) $status_rec[
"measure"];
901 $ilDB = $DIC->database();
905 SELECT * FROM cp_item 906 WHERE cp_node_id = %s',
911 if ($i =
$ilDB->fetchAssoc(
$r)) {
924 $ilDB = $DIC->database();
928 $result =
$ilDB->query(
929 'SELECT cp_node.cp_node_id ' 930 .
'FROM cp_node, cp_resource, cp_item ' 931 .
'WHERE cp_item.cp_node_id = cp_node.cp_node_id ' 932 .
'AND cp_item.resourceId = cp_resource.id ' 933 .
'AND scormType = ' .
$ilDB->quote(
'sco',
'text') .
' ' 934 .
'AND nodeName = ' .
$ilDB->quote(
'item',
'text') .
' ' 935 .
'AND cp_node.slm_id = ' .
$ilDB->quote($a_id,
'integer') .
' ' 936 .
'GROUP BY cp_node.cp_node_id' 939 while ($row =
$ilDB->fetchAssoc($result)) {
940 $scos[] = $row[
'cp_node_id'];
945 foreach ($scos as $i => $value) {
947 'SELECT c_max FROM cmi_node WHERE (user_id = %s AND cp_node_id = %s)',
948 array(
'integer',
'integer'),
949 array($a_user, $value)
954 if ($row[
'c_max'] !=
null) {
956 $max = floatval($row[
'c_max']);
960 return ($set == 1) ? $max :
null;
970 $ilDB = $DIC->database();
971 $retAr = array(
"raw" =>
null,
"max" =>
null,
"scaled" =>
null);
972 $val_set =
$ilDB->queryF(
973 "SELECT c_raw, c_max, scaled FROM cmi_node WHERE (user_id = %s AND cp_node_id = %s)",
974 array(
'integer',
'integer'),
975 array($a_user, $a_cp_node_id)
977 if ($val_set->numRows() > 0) {
978 $val_rec =
$ilDB->fetchAssoc($val_set);
979 $retAr[
"raw"] = $val_rec[
'c_raw'];
980 $retAr[
"max"] = $val_rec[
'c_max'];
981 $retAr[
"scaled"] = $val_rec[
'scaled'];
982 if ($val_rec[
'scaled'] ==
null && $val_rec[
'c_raw'] !=
null && $val_rec[
'c_max'] !=
null) {
983 $retAr[
"scaled"] = ($val_rec[
'c_raw'] / $val_rec[
'c_max']);
const LP_STATUS_COMPLETED_NUM
get_user_id(string $a_login)
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
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
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
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
__construct(Container $dic, ilPlugin $plugin)
Class ilObjSCORM2004LearningModule.
static _getCourseCompletionForUser(int $a_id, int $a_user)
Get the completion of a SCORM module for a given user.
static formatDate(ilDateTime $date, bool $a_skip_day=false, bool $a_include_wd=false, bool $include_seconds=false, ?ilObjUser $user=null,)
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)
Class ilObjSCORMLearningModule.
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.