ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilObjSCORM2004LearningModule.php
Go to the documentation of this file.
1<?php
2
20declare(strict_types=1);
21
28{
29 private string $packageFolder;
30 private string $backupManifest;
31 private DomDocument $totransform;
32 protected ilObjUser $user;
33
34 protected ilTabsGUI $tabs;
35
36 protected bool $import_sequencing = false;
37
38 protected string $imsmanifestFile;
39
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';
43
49 public function __construct(int $a_id = 0, bool $a_call_by_reference = true)
50 {
51 global $DIC;
52
53 $this->lng = $DIC->language();
54 $this->error = $DIC["ilErr"];
55 $this->db = $DIC->database();
56 $this->log = ilLoggerFactory::getLogger('sc13');
57 $this->user = $DIC->user();
58 $this->tabs = $DIC->tabs();
59 $this->type = "sahs";
60 parent::__construct($a_id, $a_call_by_reference);
61 }
62
68 public function setImportSequencing(bool $a_val): void
69 {
70 $this->import_sequencing = $a_val;
71 }
72
78 public function getImportSequencing(): bool
79 {
81 }
82
86 public function readObject(): string
87 {
88 global $DIC;
91
92 //check for json_encode,json_decode
93 if (!function_exists('json_encode') || !function_exists('json_decode')) {
94 $ilErr->raiseError($lng->txt('scplayer_phpmysqlcheck'), $ilErr->WARNING);
95 }
96
97 $needs_convert = false;
98
99 // convert imsmanifest.xml file in iso to utf8 if needed
100
101 $manifest_file = $this->getDataDirectory() . "/imsmanifest.xml";
102
103 // check if manifestfile exists and space left on device...
104 $check_for_manifest_file = is_file($manifest_file);
105
106
107
108 // if no manifestfile
109 if (!$check_for_manifest_file) {
110 $ilErr->raiseError($this->lng->txt("Manifestfile $manifest_file not found!"), $ilErr->MESSAGE);
111 return "";
112 }
113
114
115 if ($check_for_manifest_file) {
116 $manifest_file_array = file($manifest_file);
117
118 foreach ($manifest_file_array as $mfa) {
119 // if (seems_not_utf8($mfa))
120 if (@iconv('UTF-8', 'UTF-8', $mfa) != $mfa) {
121 $needs_convert = true;
122 break;
123 }
124 }
125
126
127
128 // to copy the file we need some extraspace, counted in bytes *2 ... we need 2 copies....
129 $estimated_manifest_filesize = filesize($manifest_file) * 2;
130
131 // i deactivated this, because it seems to fail on some windows systems (see bug #1795)
132 //$check_disc_free = disk_free_space($this->getDataDirectory()) - $estimated_manifest_filesize;
133 $check_disc_free = 2;
134 }
135
136
137 // if $manifest_file needs to be converted to UTF8
138 if ($needs_convert) {
139 // if file exists and enough space left on device
140 if ($check_for_manifest_file && ($check_disc_free > 1)) {
141 // create backup from original
142 if (!copy($manifest_file, $manifest_file . ".old")) {
143 echo "Failed to copy $manifest_file...<br>\n";
144 }
145
146 // read backupfile, convert each line to utf8, write line to new file
147 // php < 4.3 style
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);
152 //echo mb_detect_encoding($zeile);
153 fwrite($f_write_handler, utf8_encode($zeile));
154 }
155 fclose($f_read_handler);
156 fclose($f_write_handler);
157
158 // copy new utf8-file to imsmanifest.xml
159 if (!copy($manifest_file . ".new", $manifest_file)) {
160 echo "Failed to copy $manifest_file...<br>\n";
161 }
162
163 if (!@is_file($manifest_file)) {
164 $ilErr->raiseError($this->lng->txt("cont_no_manifest"), $ilErr->WARNING);
165 }
166 } else {
167 // gives out the specific error
168
169 if (!($check_disc_free > 1)) {
170 $ilErr->raiseError($this->lng->txt("Not enough space left on device!"), $ilErr->MESSAGE);
171 }
172 return "";
173 }
174 } else {
175 // check whether file starts with BOM (that confuses some sax parsers, see bug #1795)
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);
183 }
184 fclose($f_write_handler);
185 fclose($hmani);
186
187 // copy new utf8-file to imsmanifest.xml
188 if (!copy($manifest_file . ".new", $manifest_file)) {
189 echo "Failed to copy $manifest_file...<br>\n";
190 }
191 } else {
192 fclose($hmani);
193 }
194 }
195
196 //check for SCORM 1.2
197 $this->convert_1_2_to_2004($manifest_file);
198
199 return (new ilSCORM13Package())->il_import($this->getDataDirectory(), $this->getId());
200 }
201
202
203 public function fixReload(): void
204 {
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\"";
208 $out = preg_replace($check, $replace, $out);
209 file_put_contents($this->imsmanifestFile, $out);
210 }
211
212
213 public function convert_1_2_to_2004(string $manifest): void
214 {
216 $ilLog = $this->log;
217
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
220
221 //set variables
222 $this->packageFolder = $this->getDataDirectory();
223 $this->imsmanifestFile = $manifest;
224 $doc = new DomDocument();
225
226 //fix reload errors before loading
227 $this->fixReload();
228 $doc->load($this->imsmanifestFile);
229 $elements = $doc->getElementsByTagName("schemaversion");
230 $schema = "";
231 if (isset($elements->item(0)->nodeValue)) {
232 $schema = $elements->item(0)->nodeValue;
233 }
234 if (strtolower(trim($schema)) === "cam 1.3" || strtolower(trim($schema)) === "2004 3rd edition" || strtolower(trim($schema)) === "2004 4th edition") {
235 //no conversion
236 // $this->converted = false;
237 return;
238 }
239
240 // $this->converted = true;
241 //convert to SCORM 2004
242
243 //check for broken SCORM 1.2 manifest file (missing organization default-common error in a lot of manifest files)
244 $organizations = $doc->getElementsByTagName("organizations");
245 //first check if organizations is in manifest
246 if ($organizations->item(0) == null) {
247 die("organizations missing in manifest");
248 }
249 $default = $organizations->item(0)->getAttribute("default");
250 if ($default == "" || $default == null) {
251 //lookup identifier
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);
257 }
258 }
259
260 //validate the fixed mainfest. If it's still not valid, don't transform an throw error
261
262
263 //first copy wrappers
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));
267 }
268 copy(self::WRAPPER_HTML, $wrapperdir . "/GenericRunTimeWrapper.htm");
269 copy(self::WRAPPER_JS, $wrapperdir . "/SCOPlayerWrapper.js");
270
271 //backup manifestfile
272 $this->backupManifest = $this->packageFolder . "/imsmanifest.xml.back";
273 $ret = copy($this->imsmanifestFile, $this->backupManifest);
274
275 //transform manifest file
276 $this->totransform = $doc;
277 $ilLog->write("SCORM: about to transform to SCORM 2004");
278
279 $xsl = new DOMDocument();
280 // $xsl->async = false;
281 $xsl->load(self::CONVERT_XSL);
282 $prc = new XSLTProcessor();
283 $r = @$prc->importStyleSheet($xsl);
284
285 file_put_contents($this->imsmanifestFile, $prc->transformToXML($this->totransform));
286
287 $ilLog->write("SCORM: Transformation completed");
288 }
289
293 public static function _lookupLastAccess(int $a_obj_id, int $a_usr_id): ?string
294 {
295 global $DIC;
296
297 $ilDB = $DIC->database();
298
299 $result = $ilDB->queryF(
300 '
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
305 AND user_id = %s
306 GROUP BY c_timestamp',
307 array('integer', 'integer'),
308 array($a_obj_id, $a_usr_id)
309 );
310 if ($ilDB->numRows($result)) {
311 $row = $ilDB->fetchAssoc($result);
312 return (string) $row["last_access"];
313 }
314
315 return null;
316 }
317
318 public function deleteTrackingDataOfUsers(array $a_users): void
319 {
322
323 foreach ($a_users as $user) {
326 }
327 }
328
329
334 public function getTrackedItems(): array
335 {
336 $ilUser = $this->user;
338 $ilUser = $this->user;
339
340 $sco_set = $ilDB->queryF(
341 '
342 SELECT DISTINCT cmi_node.cp_node_id id
343 FROM cp_node, cmi_node
344 WHERE slm_id = %s
345 AND cp_node.cp_node_id = cmi_node.cp_node_id
346 ORDER BY cmi_node.cp_node_id ',
347 array('integer'),
348 array($this->getId())
349 );
350
351 $items = array();
352
353 while ($sco_rec = $ilDB->fetchAssoc($sco_set)) {
354 $item['id'] = $sco_rec["id"];
355 $item['title'] = self::_lookupItemTitle((int) $sco_rec["id"]);
356 $items[] = $item;
357 }
358 return $items;
359 }
360
365 public function getTrackingDataAgg(int $a_user_id, ?bool $raw = false): array
366 {
368
369 $scos = array();
370 $data = array();
371 //get all SCO's of this object
372
373 $val_set = $ilDB->queryF(
374 'SELECT cp_node_id FROM cp_node
375 WHERE nodename = %s
376 AND cp_node.slm_id = %s',
377 array('text', 'integer'),
378 array('item',$this->getId())
379 );
380 while ($val_rec = $ilDB->fetchAssoc($val_set)) {
381 $scos[] = $val_rec['cp_node_id'];
382 }
383
384 foreach ($scos as $sco) {
385 $data_set = $ilDB->queryF(
386 '
387 SELECT c_timestamp last_access, total_time, success_status, completion_status,
388 c_raw, scaled, cp_node_id
389 FROM cmi_node
390 WHERE cp_node_id = %s
391 AND user_id = %s',
392 array('integer','integer'),
393 array($sco,$a_user_id)
394 );
395
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"];
399 } else {
400 if ($data_rec["completion_status"] == "") {
401 $status = "unknown";
402 } else {
403 $status = $data_rec["completion_status"];
404 }
405 }
406 if (!$raw) {
407 $time = ilDatePresentation::secondsToString((int) round(self::_ISODurationToCentisec($data_rec["total_time"]) / 100));
408 $score = "";
409 if ($data_rec["c_raw"] != null) {
410 $score = $data_rec["c_raw"];
411 if ($data_rec["scaled"] != null) {
412 $score .= " = ";
413 }
414 }
415 if ($data_rec["scaled"] != null) {
416 $score .= ($data_rec["scaled"] * 100) . "%";
417 }
418 $title = self::_lookupItemTitle((int) $data_rec["cp_node_id"]);
419 $last_access = ilDatePresentation::formatDate(new ilDateTime($data_rec['last_access'], IL_CAL_DATETIME));
420 $data[] = array("sco_id" => $data_rec["cp_node_id"],
421 "score" => $score, "time" => $time, "status" => $status,"last_access" => $last_access,"title" => $title);
422 } else {
423 $data_rec["total_time"] = self::_ISODurationToCentisec($data_rec["total_time"]) / 100;
424 $data[$data_rec["cp_node_id"]] = $data_rec;
425 }
426 }
427 }
428
429 return $data;
430 }
431
435 public function getAttemptsForUser(int $a_user_id): int
436 {
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())
442 );
443
444 $val_rec = $ilDB->fetchAssoc($val_set);
445
446 if ($val_rec["package_attempts"] == null) {
447 $val_rec["package_attempts"] = 0;
448 }
449
450 return (int) $val_rec["package_attempts"];
451 }
452
456 public function getModuleVersionForUser(int $a_user_id): string
457 {
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())
463 );
464
465 $val_rec = $ilDB->fetchAssoc($val_set);
466
467 if ($val_rec["module_version"] == null) {
468 $val_rec["module_version"] = "";
469 }
470 return $val_rec["module_version"];
471 }
472
473 public function importSuccess(string $a_file): bool
474 {
476 $ilUser = $this->user;
477 $scos = array();
478 $olp = ilObjectLP::getInstance($this->getId());
479 $collection = $olp->getCollectionInstance();
480 if ($collection) {
481 $scos = $collection->getItems();
482 }
483
484 $fhandle = fopen($a_file, "rb");//changed from r to rb
485
486 $obj_id = $this->getID();
487 $users = array();
488 $usersToDelete = array();
489 $fields = fgetcsv($fhandle, 4096, ';', '"', '\\');
490 while (($csv_rows = fgetcsv($fhandle, 4096, ";", '"', '\\')) !== false) {
491 $user_id = 0;
492 $data = array_combine($fields, $csv_rows);
493 //no check the format - sufficient to import users
494 if (isset($data["Login"])) {
495 $user_id = $this->get_user_id($data["Login"]);
496 }
497 if (isset($data["login"])) {
498 $user_id = $this->get_user_id($data["login"]);
499 }
500 //add mail in future
501 if (isset($data["user"]) && is_numeric($data["user"])) {
502 $user_id = (int) $data["user"];
503 }
504 if ($user_id > 0) {
505 $last_access = new DateTimeImmutable('now');
506 if (isset($data['LastAccess']) && $data['LastAccess']) {
507 $last_access = $this->kindlyToDateTime('Y-m-d H:i:s', $data['LastAccess']);
508 } elseif (isset($data['Date']) && $data['Date']) {
509 $last_access = $this->kindlyToDateTime('d.m.Y', $data['Date']);
510 }
511
513
514 if (isset($data["Status"])) {
515 if (is_numeric($data["Status"])) {
516 $status = $data["Status"];
517 } elseif ($data["Status"] == ilLPStatus::LP_STATUS_NOT_ATTEMPTED) {
519 } elseif ($data["Status"] == ilLPStatus::LP_STATUS_IN_PROGRESS) {
521 } elseif ($data["Status"] == ilLPStatus::LP_STATUS_FAILED) {
523 }
524 }
525 $attempts = null;
526 if (isset($data["Attempts"])) {
527 $attempts = (int) $data["Attempts"];
528 }
529
530 $percentage_completed = 0;
532 $percentage_completed = 100;
533 } elseif (isset($data['percentageCompletedSCOs'])) {
534 $percentage_completed = (int) $data['percentageCompletedSCOs'];
535 }
536
537 $sco_total_time_sec = null;
538 if (isset($data['SumTotal_timeSeconds'])) {
539 $sco_total_time_sec = (int) $data['SumTotal_timeSeconds'];
540 }
541
543 $usersToDelete[] = $user_id;
544 } else {
545 $this->importSuccessForSahsUser($user_id, $last_access, $status, $attempts, $percentage_completed, $sco_total_time_sec);
546 $users[] = $user_id;
547 }
548
550 foreach ($scos as $sco_id) {
551 $res = $ilDB->queryF(
552 '
553 SELECT completion_status, success_status, user_id FROM cmi_node WHERE cp_node_id = %s AND user_id = %s',
554 array('integer','integer'),
555 array($sco_id,$user_id)
556 );
557
558 if (!$ilDB->numRows($res)) {
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]
566 );
567 } else {
568 $doUpdate = false;
569 while ($row = $ilDB->fetchAssoc($res)) {
570 if (($row["completion_status"] === "completed" && $row["success_status"] !== "failed") || $row["success_status"] === "passed") {
571 if ($doUpdate != true) {
572 $doUpdate = false;
573 } //note for issue if there are 2 entries for same sco_id
574 } else {
575 $doUpdate = true;
576 }
577 }
578 if ($doUpdate == true) {
579 $ilDB->update(
580 'cmi_node',
581 array(
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')),
586 ),
587 array(
588 'user_id' => array('integer', $user_id),
589 'cp_node_id' => array('integer', $sco_id)
590 )
591 );
592 }
593 }
594 }
595 }
596 } else {
597 //echo "Warning! User $csv_rows[0] does not exist in ILIAS. Data for this user was skipped.\n";
598 }
599 }
600
601 if (count($usersToDelete) > 0) {
602 // include_once("./components/ILIAS/Tracking/classes/class.ilLPMarks.php");
603 // ilLPMarks::_deleteForUsers($this->getId(), $usersToDelete);
604 $this->deleteTrackingDataOfUsers($usersToDelete);
605 }
607
608 return true;
609 }
610
614 public static function _ISODurationToCentisec(string $str): float
615 {
616 $aV = array(0, 0, 0, 0, 0, 0);
617 $bErr = false;
618 $bTFound = false;
619 if (strpos($str, "P") != 0) {
620 $bErr = true;
621 }
622 if (!$bErr) {
623 $aT = array("Y", "M", "D", "H", "M", "S");
624 $p = 0;
625 $i = 0;
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);
630 $i = max($i, 3);
631 $bTFound = true;
632 }
633 $p = strpos($str, $aT[$i]);
634
635 if ($p > -1) {
636 if ($i == 1 && strpos($str, "T") > -1 && strpos($str, "T") < $p) {
637 continue;
638 }
639 if ($aT[$i] === "S") {
640 $aV[$i] = substr($str, 0, $p);
641 } else {
642 $aV[$i] = intval(substr($str, 0, $p));
643 }
644 if (!is_numeric($aV[$i])) {
645 $bErr = true;
646 break;
647 }
648
649 if ($i > 2 && !$bTFound) {
650 $bErr = true;
651 break;
652 }
653 $str = substr($str, $p + 1);
654 }
655 }
656 if (!$bErr && strlen($str) != 0) {
657 $bErr = true;
658 }
659 }
660
661 if ($bErr) {
662 return 0;
663 }
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);
665 }
666
667 public static function getQuantityOfSCOs(int $a_slm_id): int
668 {
669 global $DIC;
670 $val_set = $DIC->database()->queryF(
671 '
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
675 AND scormtype = %s
676 AND nodename = %s
677 AND cp_node.slm_id = %s ',
678 array('text','text','integer'),
679 array('sco','item',$a_slm_id)
680 );
681 return $DIC->database()->numRows($val_set);
682 }
683
688 public static function _getCourseCompletionForUser(int $a_id, int $a_user): bool
689 {
690 global $DIC;
691
692 $ilDB = $DIC->database();
693 $ilUser = $DIC->user();
694 $scos = array();
695 //get all SCO's of the object
696
697 $val_set = $ilDB->queryF(
698 '
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
702 AND scormtype = %s
703 AND nodename = %s
704 AND cp_node.slm_id = %s',
705 array('text','text','integer'),
706 array('sco' ,'item',$a_id)
707 );
708 while ($val_rec = $ilDB->fetchAssoc($val_set)) {
709 $scos[] = $val_rec['cp_node_id'];
710 }
711
712 $scos_c = $scos;
713 //copy SCO_array
714 //check if all SCO's are completed
715 foreach ($scos as $i => $value) {
716 $val_set = $ilDB->queryF(
717 '
718 SELECT * FROM cmi_node
719 WHERE (user_id= %s
720 AND cp_node_id= %s
721 AND (completion_status = %s OR success_status = %s))',
722 array('integer','integer','text','text'),
723 array($a_user, $value,'completed','passed')
724 );
725
726 if ($ilDB->numRows($val_set) > 0) {
727 //delete from array
728 $key = array_search($value, $scos_c);
729 unset($scos_c[$key]);
730 }
731 }
732 //check for completion
733 if (count($scos_c) == 0) {
734 $completion = true;
735 } else {
736 $completion = false;
737 }
738 return $completion;
739 }
740
746 public static function _getUniqueScaledScoreForUser(int $a_id, int $a_user): float
747 {
748 global $DIC;
749
750 $ilDB = $DIC->database();
751 $ilUser = $DIC->user();
752 $scos = array();
753
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",
757 array('integer'),
758 array($a_id)
759 );
760 while ($val_rec = $ilDB->fetchAssoc($val_set)) {
761 $scos[] = $val_rec['cp_node_id'];
762 }
763 $set = 0; //numbers of SCO that set cmi.score.scaled
764 $scaled = null;
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])
770 );
771 if ($val_set->numRows() > 0) {
772 $val_rec = $ilDB->fetchAssoc($val_set);
773 if ($val_rec['scaled'] != null) {
774 $set++;
775 $scaled = $val_rec['scaled'];
776 }
777 }
778 }
779 return ($set == 1) ? $scaled : -1;
780 }
781
787 public static function _getTrackingItems(int $a_obj_id): array
788 {
789 global $DIC;
790
791 $ilDB = $DIC->database();
792
793
794 $item_set = $ilDB->queryF(
795 '
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 ',
799 array('integer'),
800 array($a_obj_id)
801 );
802
803 $items = array();
804 while ($item_rec = $ilDB->fetchAssoc($item_set)) {
805 $s2 = $ilDB->queryF(
806 '
807 SELECT cp_resource.* FROM cp_node, cp_resource
808 WHERE slm_id = %s
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"])
813 );
814
815
816 if ($res = $ilDB->fetchAssoc($s2)) {
817 if ($res["scormtype"] === "sco") {
818 $items[] = array("id" => $item_rec["cp_node_id"],
819 "title" => $item_rec["title"]);
820 }
821 }
822 }
823
824 return $items;
825 }
826
827 public static function _getStatus(int $a_obj_id, int $a_user_id): bool|string
828 {
829 global $DIC;
830
831 $ilDB = $DIC->database();
832
833 $status_set = $ilDB->queryF(
834 '
835 SELECT * FROM cmi_gobjective
836 WHERE scope_id = %s
837 AND objective_id = %s
838 AND user_id = %s',
839 array('integer','text','integer'),
840 array($a_obj_id,'-course_overall_status-',$a_user_id)
841 );
842
843 if ($status_rec = $ilDB->fetchAssoc($status_set)) {
844 return $status_rec["status"];
845 }
846
847 return false;
848 }
849
850 public static function _getSatisfied(int $a_obj_id, int $a_user_id): bool|string
851 {
852 global $DIC;
853
854 $ilDB = $DIC->database();
855
856
857 $status_set = $ilDB->queryF(
858 '
859 SELECT * FROM cmi_gobjective
860 WHERE scope_id = %s
861 AND objective_id = %s
862 AND user_id = %s',
863 array('integer','text','integer'),
864 array($a_obj_id,'-course_overall_status-',$a_user_id)
865 );
866
867 if ($status_rec = $ilDB->fetchAssoc($status_set)) {
868 return $status_rec["satisfied"];
869 }
870
871 return false;
872 }
873
874 public static function _getMeasure(int $a_obj_id, int $a_user_id): float|bool
875 {
876 global $DIC;
877
878 $ilDB = $DIC->database();
879
880 $status_set = $ilDB->queryF(
881 '
882 SELECT * FROM cmi_gobjective
883 WHERE scope_id = %s
884 AND objective_id = %s
885 AND user_id = %s',
886 array('integer','text','integer'),
887 array($a_obj_id,'-course_overall_status-',$a_user_id)
888 );
889
890 if ($status_rec = $ilDB->fetchAssoc($status_set)) {
891 return (float) $status_rec["measure"];
892 }
893
894 return false;
895 }
896
897 public static function _lookupItemTitle(int $a_node_id): string
898 {
899 global $DIC;
900
901 $ilDB = $DIC->database();
902
903 $r = $ilDB->queryF(
904 '
905 SELECT * FROM cp_item
906 WHERE cp_node_id = %s',
907 array('integer'),
908 array($a_node_id)
909 );
910
911 if ($i = $ilDB->fetchAssoc($r)) {
912 return $i["title"];
913 }
914 return "";
915 }
916
920 public static function _getMaxScoreForUser(int $a_id, int $a_user): ?float
921 {
922 global $DIC;
923
924 $ilDB = $DIC->database();
925
926 $scos = array();
927
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'
937 );
938
939 while ($row = $ilDB->fetchAssoc($result)) {
940 $scos[] = $row['cp_node_id'];
941 }
942
943 $set = 0; //numbers of SCO that set cmi.score.scaled
944 $max = null;
945 foreach ($scos as $i => $value) {
946 $res = $ilDB->queryF(
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)
950 );
951
952 if ($ilDB->numRows($res) > 0) {
953 $row = $ilDB->fetchAssoc($res);
954 if ($row['c_max'] != null) {
955 $set++;
956 $max = floatval($row['c_max']);
957 }
958 }
959 }
960 return ($set == 1) ? $max : null;
961 }
962
966 public static function _getScores2004ForUser(int $a_cp_node_id, int $a_user): array
967 {
968 global $DIC;
969
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)
976 );
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']);
984 }
985 }
986 return $retAr;
987 }
988}
$out
Definition: buildRTE.php:24
$check
Definition: buildRTE.php:81
const IL_CAL_DATETIME
error(string $a_errmsg)
static _deleteReadEventsForUsers(int $a_obj_id, array $a_user_ids)
static formatDate(ilDateTime $date, bool $a_skip_day=false, bool $a_include_wd=false, bool $include_seconds=false, ?ilObjUser $user=null,)
static secondsToString(int $seconds, bool $force_with_seconds=false, ?ilLanguage $a_lng=null)
converts seconds to string: Long: 7 days 4 hour(s) ...
@classDescription Date and time handling
static _refreshStatus(int $a_obj_id, ?array $a_users=null)
static _updateStatus(int $a_obj_id, int $a_usr_id, ?object $a_obj=null, bool $a_percentage=false, bool $a_force_raise=false)
const LP_STATUS_COMPLETED_NUM
const LP_STATUS_FAILED
const LP_STATUS_IN_PROGRESS_NUM
const LP_STATUS_NOT_ATTEMPTED_NUM
const LP_STATUS_FAILED_NUM
const LP_STATUS_NOT_ATTEMPTED
const LP_STATUS_IN_PROGRESS
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 getLogger(string $a_component_id)
Get component logger.
getDataDirectory(?string $mode="filesystem")
get data directory of lm
Class ilObjSCORM2004LearningModule.
static _ISODurationToCentisec(string $str)
convert ISO 8601 Timeperiods to centiseconds
static _getTrackingItems(int $a_obj_id)
get all tracking items of scorm object currently a for learning progress only
static _getMeasure(int $a_obj_id, int $a_user_id)
getModuleVersionForUser(int $a_user_id)
get module version that tracking data for a user was recorded on
static _getCourseCompletionForUser(int $a_id, int $a_user)
Get the completion of a SCORM module for a given user.
static _getScores2004ForUser(int $a_cp_node_id, int $a_user)
static _getSatisfied(int $a_obj_id, int $a_user_id)
static _getStatus(int $a_obj_id, int $a_user_id)
static _lookupLastAccess(int $a_obj_id, int $a_usr_id)
Return the last access timestamp for a given 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....
getTrackedItems()
get all tracked items of current user
getTrackingDataAgg(int $a_user_id, ?bool $raw=false)
setImportSequencing(bool $a_val)
Set import sequencing.
__construct(int $a_id=0, bool $a_call_by_reference=true)
Constructor.
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.
getAttemptsForUser(int $a_user_id)
get number of atttempts for a certain user and package
Class ilObjSCORMLearningModule.
kindlyToDateTime(string $format, string $maybe_datetime, ?DateTimeImmutable $default=null)
importSuccessForSahsUser(int $user_id, ?DateTimeImmutable $last_access, int $status, ?int $attempts=null, ?int $percentage_completed=null, ?int $sco_total_time_sec=null)
User class.
static getInstance(int $obj_id)
string $title
ilLanguage $lng
ilErrorHandling $error
ilLogger $log
ilDBInterface $db
static removeCMIDataForUserAndPackage(int $user_id, int $packageId)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$res
Definition: ltiservices.php:69
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
$ilErr
Definition: raiseError.php:33
global $DIC
Definition: shib_login.php:26