ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilObjSCORMLearningModule.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
30{
36 public function __construct(int $a_id = 0, bool $a_call_by_reference = true)
37 {
38 $this->type = "sahs";
39 parent::__construct($a_id, $a_call_by_reference);
40 }
41
42 public function getTrackingItems(): array
43 {
45 }
46
50 public static function _getTrackingItems(int $a_obj_id): array
51 {
52 $tree = new ilSCORMTree($a_obj_id);
53 $root_id = $tree->readRootId();
54
55 $items = array();
56 $childs = $tree->getSubTree($tree->getNodeData($root_id));
57
58 foreach ($childs as $child) {
59 if ($child["c_type"] === "sit") {
60 $sc_item = new ilSCORMItem((int) $child["obj_id"]);
61 if ($sc_item->getIdentifierRef() != "") {
62 $items[] = $sc_item;
63 }
64 }
65 }
66
67 return $items;
68 }
69
74 public function readObject(): string
75 {
76 global $DIC;
77 $ilErr = $DIC['ilErr'];
78
79 $needs_convert = false;
80
81 // convert imsmanifest.xml file in iso to utf8 if needed
82
83 $manifest_file = $this->getDataDirectory() . "/imsmanifest.xml";
84
85 // check if manifestfile exists and space left on device...
86 $check_for_manifest_file = is_file($manifest_file);
87
88 // if no manifestfile
89 if (!$check_for_manifest_file) {
90 $ilErr->raiseError($this->lng->txt("Manifestfile $manifest_file not found!"), $ilErr->MESSAGE);
91 return "";
92 }
93
94 if ($check_for_manifest_file) {
95 $manifest_file_array = file($manifest_file);
96 foreach ($manifest_file_array as $mfa) {
97 // if (seems_not_utf8($mfa))
98 if (@iconv('UTF-8', 'UTF-8', $mfa) != $mfa) {
99 $needs_convert = true;
100 break;
101 }
102 }
103
104 // to copy the file we need some extraspace, counted in bytes *2 ... we need 2 copies....
105 $estimated_manifest_filesize = filesize($manifest_file) * 2;
106
107 // i deactivated this, because it seems to fail on some windows systems (see bug #1795)
108 //$check_disc_free = disk_free_space($this->getDataDirectory()) - $estimated_manifest_filesize;
109 $check_disc_free = 2;
110 }
111
112 // if $manifest_file needs to be converted to UTF8
113 if ($needs_convert) {
114 // if file exists and enough space left on device
115 if ($check_for_manifest_file && ($check_disc_free > 1)) {
116 // create backup from original
117 if (!copy($manifest_file, $manifest_file . ".old")) {
118 echo "Failed to copy $manifest_file...<br>\n";
119 }
120
121 // read backupfile, convert each line to utf8, write line to new file
122 // php < 4.3 style
123 $f_write_handler = fopen($manifest_file . ".new", "w");
124 $f_read_handler = fopen($manifest_file . ".old", "r");
125 while (!feof($f_read_handler)) {
126 $zeile = fgets($f_read_handler);
127 //echo mb_detect_encoding($zeile);
128 fwrite($f_write_handler, utf8_encode($zeile));
129 }
130 fclose($f_read_handler);
131 fclose($f_write_handler);
132
133 // copy new utf8-file to imsmanifest.xml
134 if (!copy($manifest_file . ".new", $manifest_file)) {
135 echo "Failed to copy $manifest_file...<br>\n";
136 }
137
138 if (!@is_file($manifest_file)) {
139 $ilErr->raiseError($this->lng->txt("cont_no_manifest"), $ilErr->WARNING);
140 }
141 } else {
142 // gives out the specific error
143
144 if (!($check_disc_free > 1)) {
145 $ilErr->raiseError($this->lng->txt("Not enough space left on device!"), $ilErr->MESSAGE);
146 }
147 return "";
148 }
149 } else {
150 // check whether file starts with BOM (that confuses some sax parsers, see bug #1795)
151 $hmani = fopen($manifest_file, "r");
152 $start = fread($hmani, 3);
153 if (strtolower(bin2hex($start)) === "efbbbf") {
154 $f_write_handler = fopen($manifest_file . ".new", "w");
155 while (!feof($hmani)) {
156 $n = fread($hmani, 900);
157 fwrite($f_write_handler, $n);
158 }
159 fclose($f_write_handler);
160 fclose($hmani);
161
162 // copy new utf8-file to imsmanifest.xml
163 if (!copy($manifest_file . ".new", $manifest_file)) {
164 echo "Failed to copy $manifest_file...<br>\n";
165 }
166 } else {
167 fclose($hmani);
168 }
169 }
170
171 // todo determine imsmanifest.xml path here...
172 $slmParser = new ilSCORMPackageParser($this, $manifest_file);
173 $slmParser->startParsing();
174 return (string) $slmParser->getPackageTitle();
175 }
176
181 {
182 global $DIC;
183 $ilSetting = $DIC->settings();
184 //condition 1
185 $lm_set = new ilSetting("lm");
186 if ($lm_set->get('scorm_lp_auto_activate') != 1) {
187 return;
188 }
190 return;
191 }
192 $lm_set = new ilLPObjSettings($this->getId());
194 $lm_set->insert();
195 $collection = new ilLPCollectionOfSCOs($this->getId(), ilLPObjSettings::LP_MODE_SCORM);
196 $scos = array();
197 foreach ($collection->getPossibleItems() as $sco_id => $item) {
198 $scos[] = $sco_id;
199 }
200 $collection->activateEntries($scos);
201 }
202
206 public function getTrackedItems(): array
207 {
208 global $DIC;
209 $ilDB = $DIC->database();
210 $ilUser = $DIC->user();
211
212 $sco_set = $ilDB->queryF(
213 '
214 SELECT DISTINCT sco_id FROM scorm_tracking WHERE obj_id = %s',
215 array('integer'),
216 array($this->getId())
217 );
218
219 $items = array();
220 while ($sco_rec = $ilDB->fetchAssoc($sco_set)) {
221 $sc_item = new ilSCORMItem((int) $sco_rec["sco_id"]);
222 if ($sc_item->getIdentifierRef() != "") {
223 $items[] = $sc_item;
224 }
225 }
226
227 return $items;
228 }
229
230 // /**
231 // * Return the last access timestamp for a given user
232 // *
233 // * @param int $a_obj_id object id
234 // * @param int $user_id user id
235 // * @return timestamp
236 // */
237 // public static function _lookupLastAccess(int $a_obj_id, $a_usr_id)
238 // {
239 // global $DIC;
240 // $ilDB = $DIC->database();
241 //
242 // $result = $ilDB->queryF(
243 // '
244 // SELECT last_access FROM sahs_user
245 // WHERE obj_id = %s
246 // AND user_id = %s',
247 // array('integer','integer'),
248 // array($a_obj_id,$a_usr_id)
249 // );
250 //
251 // if ($ilDB->numRows($result)) {
252 // $row = $ilDB->fetchAssoc($result);
253 // return $row["last_access"];
254 // }
255 // return "";
256 // }
257
258 public function getTrackedUsers(string $a_search): array
259 {
260 global $DIC;
261 $ilDB = $DIC->database();
262 $ilUser = $DIC->user();
263 //TODO: UK last_access is not correct if no Commit or last_visited_sco
264 // $query = 'SELECT user_id,MAX(c_timestamp) last_access, lastname, firstname FROM scorm_tracking st ' .
265 $query = 'SELECT user_id, last_access, lastname, firstname FROM sahs_user st ' .
266 'JOIN usr_data ud ON st.user_id = ud.usr_id ' .
267 'WHERE obj_id = ' . $ilDB->quote($this->getId(), 'integer');
268 if ($a_search) {
269 // $query .= ' AND (' . $ilDB->like('lastname', 'text', '%' . $a_search . '%') . ' OR ' . $ilDB->like('firstname', 'text', '%' . $a_search . '%') .')';
270 $query .= ' AND ' . $ilDB->like('lastname', 'text', '%' . $a_search . '%');
271 }
272 $query .= ' GROUP BY user_id, lastname, firstname, last_access';
273 $sco_set = $ilDB->query($query);
274
275 $items = array();
276 while ($sco_rec = $ilDB->fetchAssoc($sco_set)) {
277 $items[] = $sco_rec;
278 }
279 return $items;
280 }
281
282 //toDo
288 public function getAttemptsForUsers(): array
289 {
290 global $DIC;
291 $ilDB = $DIC->database();
292 $query = 'SELECT user_id, package_attempts FROM sahs_user WHERE obj_id = ' . $ilDB->quote($this->getId(), 'integer') . ' ';
293 $res = $ilDB->query($query);
294
295 $attempts = array();
296 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
297 $attempts[$row['user_id']] = (int) $row['package_attempts'];
298 }
299 return $attempts;
300 }
301
302 //todo
303
307 public function getAttemptsForUser(int $a_user_id): int
308 {
309 global $DIC;
310 $ilDB = $DIC->database();
311 $val_set = $ilDB->queryF(
312 'SELECT package_attempts FROM sahs_user WHERE obj_id = %s AND user_id = %s',
313 array('integer','integer'),
314 array($this->getId(),$a_user_id)
315 );
316
317 $val_rec = $ilDB->fetchAssoc($val_set);
318
319 if ($val_rec["package_attempts"] == null) {
320 $val_rec["package_attempts"] = 0;
321 }
322 return (int) $val_rec["package_attempts"];
323 }
324
328 public function getModuleVersionForUsers(): array
329 {
330 global $DIC;
331 $ilDB = $DIC->database();
332 $query = 'SELECT user_id, module_version FROM sahs_user WHERE obj_id = ' . $ilDB->quote($this->getId(), 'integer') . ' ';
333 $res = $ilDB->query($query);
334
335 $versions = array();
336 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
337 $versions[$row['user_id']] = (int) $row['module_version'];
338 }
339 return $versions;
340 }
341
345 public function getModuleVersionForUser(int $a_user_id): string
346 {
347 global $DIC;
348 $ilDB = $DIC->database();
349 $val_set = $ilDB->queryF(
350 'SELECT module_version FROM sahs_user WHERE obj_id = %s AND user_id = %s',
351 array('integer','integer'),
352 array($this->getId(),$a_user_id,0)
353 );
354
355 $val_rec = $ilDB->fetchAssoc($val_set);
356
357 if ($val_rec["module_version"] == null) {
358 $val_rec["module_version"] = "";
359 }
360 return $val_rec["module_version"];
361 }
362
366 public function getTrackingDataPerUser(int $a_sco_id, int $a_user_id): array
367 {
368 global $DIC;
369 $ilDB = $DIC->database();
370
371 $data_set = $ilDB->queryF(
372 '
373 SELECT * FROM scorm_tracking
374 WHERE user_id = %s
375 AND sco_id = %s
376 AND obj_id = %s
377 ORDER BY lvalue',
378 array('integer','integer','integer'),
379 array($a_user_id,$a_sco_id,$this->getId())
380 );
381
382 $data = array();
383 while ($data_rec = $ilDB->fetchAssoc($data_set)) {
384 $data[] = $data_rec;
385 }
386
387 return $data;
388 }
389
390 public function getTrackingDataAgg(int $a_user_id): array
391 {
392 global $DIC;
393 $ilDB = $DIC->database();
394
395 // get all users with any tracking data
396 $sco_set = $ilDB->queryF(
397 '
398 SELECT DISTINCT sco_id FROM scorm_tracking
399 WHERE obj_id = %s
400 AND user_id = %s
401 AND sco_id <> %s',
402 array('integer','integer','integer'),
403 array($this->getId(),$a_user_id,0)
404 );
405
406 $data = array();
407 while ($sco_rec = $ilDB->fetchAssoc($sco_set)) {
408 $data_set = $ilDB->queryF(
409 '
410 SELECT * FROM scorm_tracking
411 WHERE obj_id = %s
412 AND sco_id = %s
413 AND user_id = %s
414 AND lvalue <> %s
415 AND (lvalue = %s
416 OR lvalue = %s
417 OR lvalue = %s)',
418 array('integer','integer','integer','text','text','text','text'),
419 array($this->getId(),
420 $sco_rec["sco_id"],
421 $a_user_id,
422 "package_attempts",
423 "cmi.core.lesson_status",
424 "cmi.core.total_time",
425 "cmi.core.score.raw")
426 );
427
428 $score = $time = $status = "";
429
430 while ($data_rec = $ilDB->fetchAssoc($data_set)) {
431 switch ($data_rec["lvalue"]) {
432 case "cmi.core.lesson_status":
433 $status = $data_rec["rvalue"];
434 break;
435
436 case "cmi.core.total_time":
437 $time = $data_rec["rvalue"];
438 break;
439
440 case "cmi.core.score.raw":
441 $score = $data_rec["rvalue"];
442 break;
443 }
444 }
445 $sc_item = new ilSCORMItem((int) $sco_rec["sco_id"]);
446 $data[] = array("sco_id" => (int) $sco_rec["sco_id"], "title" => $sc_item->getTitle(),
447 "score" => $score, "time" => $time, "status" => $status);
448 }
449 return (array) $data;
450 }
451
452 public function getTrackingDataAggSco(int $a_sco_id): array
453 {
454 global $DIC;
455 $ilDB = $DIC->database();
456
457 // get all users with any tracking data
458 $user_set = $ilDB->queryF(
459 '
460 SELECT DISTINCT user_id FROM scorm_tracking
461 WHERE obj_id = %s
462 AND sco_id = %s',
463 array('integer','integer'),
464 array($this->getId(),$a_sco_id)
465 );
466
467 $data = array();
468 while ($user_rec = $ilDB->fetchAssoc($user_set)) {
469 $data_set = $ilDB->queryF(
470 '
471 SELECT * FROM scorm_tracking
472 WHERE obj_id = %s
473 AND sco_id = %s
474 AND user_id = %s
475 AND (lvalue = %s
476 OR lvalue = %s
477 OR lvalue = %s)',
478 array('integer','integer','integer','text','text','text'),
479 array($this->getId(),
480 $a_sco_id,
481 $user_rec["user_id"],
482 "cmi.core.lesson_status",
483 "cmi.core.total_time",
484 "cmi.core.score.raw")
485 );
486
487 $score = $time = $status = "";
488
489 while ($data_rec = $ilDB->fetchAssoc($data_set)) {
490 switch ($data_rec["lvalue"]) {
491 case "cmi.core.lesson_status":
492 $status = $data_rec["rvalue"];
493 break;
494
495 case "cmi.core.total_time":
496 $time = $data_rec["rvalue"];
497 break;
498
499 case "cmi.core.score.raw":
500 $score = $data_rec["rvalue"];
501 break;
502 }
503 }
504
505 $data[] = array("user_id" => $user_rec["user_id"],
506 "score" => $score, "time" => $time, "status" => $status);
507 }
508
509 return $data;
510 }
511
516 public function exportSelected(bool $a_all, array $a_users = array()): void
517 {
518 global $DIC;
519 $ilDB = $DIC->database();
520 $ilUser = $DIC->user();
522 $allowExportPrivacy = $privacy->enabledExportSCORM();
523
524 $csv = "";
525 $query = 'SELECT * FROM sahs_user WHERE obj_id = %s';
526 if (count($a_users) > 0) {
527 $query .= ' AND ' . $ilDB->in('user_id', $a_users, false, 'integer');
528 }
529 $res = $ilDB->queryF(
530 $query,
531 array('integer'),
532 array($this->getId())
533 );
534 while ($data = $ilDB->fetchAssoc($res)) {
535 $csv = $csv . $data["obj_id"]
536 . ";\"" . $this->getTitle() . "\""
537 . ";" . $data["module_version"]
538 . ";\"" . implode("\";\"", ilSCORMTrackingItems::userDataArrayForExport((int) $data["user_id"], $allowExportPrivacy)) . "\""
539 . ";\"" . $data["last_access"] . "\""
540 . ";\"" . ilLearningProgressBaseGUI::__readStatus((int) $data["obj_id"], (int) $data["user_id"]) . "\"" //not $data["status"] because modifications to learning progress could have made before export
541 . ";" . $data["package_attempts"]
542 . ";" . $data["percentage_completed"]
543 . ";" . $data["sco_total_time_sec"]
544// . ";\"" . $certificateDate ."\""
545 . "\n";
546 }
548 $header = "LearningModuleId;LearningModuleTitle;LearningModuleVersion;" . str_replace(',', ';', $udh["cols"]) . ";"
549 . "LastAccess;Status;Attempts;percentageCompletedSCOs;SumTotal_timeSeconds\n";
550
551 $this->sendExportFile($header, $csv);
552 }
553
554 public function importTrackingData(string $a_file): bool
555 {
556 global $DIC;
557 $ilDB = $DIC->database();
558 $ilUser = $DIC->user();
559
560 $success = false;
561 //echo file_get_contents($a_file);
562 $method = null;
563
564 //lets import
565 $fhandle = fopen($a_file, "r");
566
567 //the top line is the field names
568 $fields = fgetcsv($fhandle, 2 ** 16, ';', '"', '\\');
569 //lets check the import method
570 fclose($fhandle);
571
572 switch ($fields[0]) {
573 case "Scoid":
574 case "SCO-Identifier":
575 $success = $this->importRaw($a_file);
576 break;
577 case "Department":
578 case "LearningModuleId":
579 $success = $this->importSuccess($a_file);
580 break;
581 default:
582 return false;
583 }
584 return $success;
585 }
586
587 public function importSuccess(string $a_file): bool
588 {
589 global $DIC;
590 $ilDB = $DIC->database();
591 $ilUser = $DIC->user();
592 $scos = array();
593 $olp = ilObjectLP::getInstance($this->getId());
594 $collection = $olp->getCollectionInstance();
595 if ($collection) {
596 $scos = $collection->getItems();
597 }
598
599 $fhandle = fopen($a_file, "r");
600
601 $obj_id = $this->getID();
602 $fields = fgetcsv($fhandle, 2 ** 16, ';', '"', '\\');
603 $users = array();
604 $usersToDelete = array();
605 while (($csv_rows = fgetcsv($fhandle, 2 ** 16, ";", '"', '\\')) !== false) {
606 $user_id = 0;
607 $data = array_combine($fields, $csv_rows);
608 //no check the format - sufficient to import users
609 if (isset($data["Login"])) {
610 $user_id = $this->get_user_id($data["Login"]);
611 }
612 if (isset($data["login"])) {
613 $user_id = $this->get_user_id($data["login"]);
614 }
615 //add mail in future
616 if (isset($data["user"]) && is_numeric($data["user"])) {
617 $user_id = (int) $data["user"];
618 }
619
620 if ($user_id > 0) {
621 $last_access = new DateTimeImmutable('now');
622 if (isset($data['LastAccess']) && $data['LastAccess']) {
623 $last_access = $this->kindlyToDateTime('Y-m-d H:i:s', $data['LastAccess']);
624 } elseif (isset($data['Date']) && $data['Date']) {
625 $last_access = $this->kindlyToDateTime('d.m.Y', $data['Date']);
626 }
627
629
630 if (isset($data["Status"])) {
631 if (is_int($data["Status"])) {
632 $status = $data["Status"];
633 } elseif ($data["Status"] == "0" || $data["Status"] == "1" || $data["Status"] == "2" || $data["Status"] == "3") {
634 $status = (int) $data["Status"];
635 } elseif ($data["Status"] == ilLPStatus::LP_STATUS_NOT_ATTEMPTED) {
637 } elseif ($data["Status"] == ilLPStatus::LP_STATUS_IN_PROGRESS) {
639 } elseif ($data["Status"] == ilLPStatus::LP_STATUS_FAILED) {
641 }
642 }
643
644 $attempts = null;
645 if (isset($data["Attempts"])) {
646 $attempts = (int) $data["Attempts"];
647 }
648
649 $percentage_completed = 0;
651 $percentage_completed = 100;
652 }
653 if (isset($data['percentageCompletedSCOs'])) {
654 $percentage_completed = (int) $data['percentageCompletedSCOs'];
655 }
656
657 $sco_total_time_sec = null;
658 if (isset($data['SumTotal_timeSeconds'])) {
659 $sco_total_time_sec = (int) $data['SumTotal_timeSeconds'];
660 }
661
663 $usersToDelete[] = $user_id;
664 } else {
665 $this->importSuccessForSahsUser($user_id, $last_access, $status, $attempts, $percentage_completed, $sco_total_time_sec);
666 $users[] = $user_id;
667 }
668
670 foreach ($scos as $sco_id) {
671 $statement = $ilDB->queryF(
672 '
673 SELECT * FROM scorm_tracking
674 WHERE user_id = %s
675 AND sco_id = %s
676 AND lvalue = %s
677 AND obj_id = %s',
678 array('integer','integer','text','integer'),
679 array($user_id, $sco_id, 'cmi.core.lesson_status',$obj_id)
680 );
681 if ($ilDB->numRows($statement) > 0) {
682 $ilDB->update(
683 'scorm_tracking',
684 array(
685 'rvalue' => array('clob', 'completed'),
686 'c_timestamp' => array('timestamp', $last_access?->format('Y-m-d H:i:s'))
687 ),
688 array(
689 'user_id' => array('integer', $user_id),
690 'sco_id' => array('integer', $sco_id),
691 'lvalue' => array('text', 'cmi.core.lesson_status'),
692 'obj_id' => array('integer', $obj_id)
693 )
694 );
695 } else {
696 $ilDB->insert('scorm_tracking', array(
697 'obj_id' => array('integer', $obj_id),
698 'user_id' => array('integer', $user_id),
699 'sco_id' => array('integer', $sco_id),
700 'lvalue' => array('text', 'cmi.core.lesson_status'),
701 'rvalue' => array('clob', 'completed'),
702 'c_timestamp' => array('timestamp', $last_access?->format('Y-m-d H:i:s'))
703 ));
704 }
705 }
706 }
707 } else {
708 //echo "Warning! User $csv_rows[0] does not exist in ILIAS. Data for this user was skipped.\n";
709 }
710 }
711
712 if (count($usersToDelete) > 0) {
713 // include_once("../components/ILIAS/Tracking/classes/class.ilLPMarks.php");
714 // ilLPMarks::_deleteForUsers($this->getId(), $usersToDelete);
715 $this->deleteTrackingDataOfUsers($usersToDelete);
716 }
718 return true;
719 }
720
721 protected function kindlyToDateTime(
722 string $format,
723 string $maybe_datetime,
724 ?DateTimeImmutable $default = null
725 ): ?DateTimeImmutable {
726 $datetime = $default;
727
728 if ($maybe_datetime === '0000-00-00 00:00:00' || $maybe_datetime === '0000-00-00') {
729 return null;
730 }
731
732 try {
733 $parsed_date = DateTimeImmutable::createFromFormat($format, $maybe_datetime);
734 if ($parsed_date !== false) {
735 $datetime = $parsed_date;
736 }
737 } catch (Throwable) {
738 // Ignore
739 }
740
741 return $datetime;
742 }
743
745 int $user_id,
746 ?DateTimeImmutable $last_access,
747 int $status,
748 ?int $attempts = null,
749 ?int $percentage_completed = null,
750 ?int $sco_total_time_sec = null
751 ): void {
752 global $DIC;
753 $ilDB = $DIC->database();
754 $statement = $ilDB->queryF(
755 'SELECT * FROM sahs_user WHERE obj_id = %s AND user_id = %s',
756 array('integer','integer'),
757 array($this->getID(),$user_id)
758 );
759 if ($ilDB->numRows($statement) > 0) {
760 $ilDB->update(
761 'sahs_user',
762 array(
763 'last_access' => array('timestamp', $last_access?->format('Y-m-d H:i:s')),
764 'status' => array('integer', $status),
765 'package_attempts' => array('integer', $attempts),
766 'percentage_completed' => array('integer', $percentage_completed),
767 'sco_total_time_sec' => array('integer', $sco_total_time_sec)
768 ),
769 array(
770 'obj_id' => array('integer', $this->getID()),
771 'user_id' => array('integer', $user_id)
772 )
773 );
774 } else {
775 $ilDB->insert('sahs_user', array(
776 'obj_id' => array('integer', $this->getID()),
777 'user_id' => array('integer', $user_id),
778 'last_access' => array('timestamp', $last_access?->format('Y-m-d H:i:s')),
779 'status' => array('integer', $status),
780 'package_attempts' => array('integer', $attempts),
781 'percentage_completed' => array('integer', $percentage_completed),
782 'sco_total_time_sec' => array('integer', $sco_total_time_sec)
783 ));
784 }
785 // since 8 necessary because attempts can be null
786 if (ilChangeEvent::_lookupReadEvents($this->getID(), $user_id) == []) {
787 ilChangeEvent::_recordReadEvent("sahs", $DIC->http()->wrapper()->query()->retrieve('ref_id', $DIC->refinery()->kindlyTo()->int()), $this->getID(), $user_id, false);
788 }
789
790 ilChangeEvent::_recordReadEvent("sahs", $DIC->http()->wrapper()->query()->retrieve('ref_id', $DIC->refinery()->kindlyTo()->int()), $this->getID(), $user_id, false, $attempts, $sco_total_time_sec);
791 }
792
796 private function parseUserId(string $il_id): int
797 {
798 global $DIC;
799 $ilSetting = $DIC->settings();
800
801 $parts = explode('_', $il_id);
802
803 if (!count((array) $parts)) {
804 return 0;
805 }
806 if (!isset($parts[2]) or !isset($parts[3])) {
807 return 0;
808 }
809 if ($parts[2] != $ilSetting->get('inst_id', $parts[2])) {
810 return 0;
811 }
812 return (int) $parts[3];
813 }
814
818 private function importRaw(string $a_file): bool
819 {
820 global $DIC;
821 $ilDB = $DIC->database();
822 $ilUser = $DIC->user();
823 $lng = $DIC->language();
824 $lng->loadLanguageModule("scormtrac");
825
826 $fhandle = fopen($a_file, "r");
827
828 $fields = fgetcsv($fhandle, 2 ** 16, ';', '"', '\\');
829 $users = array();
830 $a_last_access = array();
831 $a_time = array();
832 $a_package_attempts = array();
833 $a_module_version = array();
834 while (($csv_rows = fgetcsv($fhandle, 2 ** 16, ";", '"', '\\')) !== false) {
835 $data = array_combine($fields, $csv_rows);
836 if ($data['Userid']) {
837 $user_id = $this->parseUserId($data['Userid']);
838 } elseif ($data[$lng->txt("user")]) {
839 if (is_int($data[$lng->txt("user")])) {
840 $user_id = $data[$lng->txt("user")];
841 }
842 }
843 if ($data[$lng->txt("login")]) {
844 $user_id = $this->get_user_id($data[$lng->txt("login")]);
845 }
846 if (!$user_id) {
847 continue;
848 }
849
850 if ($data['Scoid']) {
851 $il_sco_id = $this->lookupSCOId($data['Scoid']);
852 }
853 if ($data[$lng->txt("identifierref")]) {
854 $il_sco_id = $this->lookupSCOId($data[$lng->txt("identifierref")]);
855 }
856 if (!$il_sco_id) {
857 continue;
858 }
859
860 $c_timestamp = null;
861 foreach ([$lng->txt("c_timestamp"), 'Timestamp'] as $key) {
862 if (!empty($data[$key])) {
863 $c_timestamp = $this->kindlyToDateTime('Y-m-d H:i:s', $data[$key]);
864 break;
865 }
866 }
867
868 $c_timestamp ??= new DateTimeImmutable('now');
869 if (!isset($a_last_access[$user_id]) || $a_last_access[$user_id] < $c_timestamp) {
870 $a_last_access[$user_id] = $c_timestamp;
871 }
872
873 if (!$data['Key']) {
874 continue;
875 }
876 if (!$data['Value']) {
877 $data['Value'] = "";
878 }
879
880 if ($data['Key'] === "cmi.core.total_time" && $data['Value'] != "") {
881 $tarr = explode(":", $data['Value']);
882 $sec = (int) $tarr[2] + (int) $tarr[1] * 60 +
883 (int) substr($tarr[0], strlen($tarr[0]) - 3) * 60 * 60;
884 if ($a_time[$user_id]) {
885 $a_time[$user_id] += $sec;
886 } else {
887 $a_time[$user_id] = $sec;
888 }
889 }
890 //do the actual import
891 if ($il_sco_id > 0) {
892 $statement = $ilDB->queryF(
893 '
894 SELECT * FROM scorm_tracking
895 WHERE user_id = %s
896 AND sco_id = %s
897 AND lvalue = %s
898 AND obj_id = %s',
899 array('integer', 'integer', 'text', 'integer'),
900 array($user_id, $il_sco_id, $data['Key'], $this->getID())
901 );
902 if ($ilDB->numRows($statement) > 0) {
903 $ilDB->update(
904 'scorm_tracking',
905 array(
906 'rvalue' => array('clob', $data['Value']),
907 'c_timestamp' => array('timestamp', $c_timestamp)
908 ),
909 array(
910 'user_id' => array('integer', $user_id),
911 'sco_id' => array('integer', $il_sco_id),
912 'lvalue' => array('text', $data['Key']),
913 'obj_id' => array('integer', $this->getId())
914 )
915 );
916 } else {
917 $ilDB->insert('scorm_tracking', array(
918 'obj_id' => array('integer', $this->getId()),
919 'user_id' => array('integer', $user_id),
920 'sco_id' => array('integer', $il_sco_id),
921 'lvalue' => array('text', $data['Key']),
922 'rvalue' => array('clob', $data['Value']),
923 'c_timestamp' => array('timestamp', $data['Timestamp'])
924 ));
925 }
926 }
927 // $package_attempts = 1;
928 if ($il_sco_id == 0) {
929 if ($data['Key'] === "package_attempts") {
930 $a_package_attempts[$user_id] = $data['Value'];
931 }
932 // if ($data['Key'] == "module_version") $a_module_version[$user_id] = $data['Value'];
933 }
934 if (!in_array($user_id, $users)) {
935 $users[] = $user_id;
936 }
937 }
938 fclose($fhandle);
940 foreach ($users as $user_id) {
941 $attempts = 1;
942 if ($a_package_attempts[$user_id]) {
943 $attempts = $a_package_attempts[$user_id];
944 }
945 // $module_version = 1;
946 // if ($a_module_version[$user_id]) $module_version = $a_module_version[$user_id];
947 $sco_total_time_sec = null;
948 if ($a_time[$user_id]) {
949 $sco_total_time_sec = $a_time[$user_id];
950 }
951 $last_access = null;
952 if (isset($a_last_access[$user_id])) {
953 $last_access = $a_last_access[$user_id];
954 }
955 // $status = ilLPStatusWrapper::_determineStatus($this->getId(),$user_id);
956 $status = (int) ilLPStatus::_lookupStatus($this->getId(), $user_id);
957 // $percentage_completed = ilLPStatusSCORM::determinePercentage($this->getId(),$user_id);
958 $percentage_completed = ilLPStatus::_lookupPercentage($this->getId(), $user_id);
959
960 $this->importSuccessForSahsUser($user_id, $last_access, $status, $attempts, $percentage_completed, $sco_total_time_sec);
961 }
962
963 return true;
964 }
965
966
967
968 //helper function
969
970 public function get_user_id(string $a_login): ?int
971 {
972 global $DIC;
973 $ilDB = $DIC->database();
974 $ilUser = $DIC->user();
975
976 $val_set = $ilDB->queryF(
977 'SELECT * FROM usr_data WHERE(login=%s)',
978 array('text'),
979 array($a_login)
980 );
981 $val_rec = $ilDB->fetchAssoc($val_set);
982
983 if (count($val_rec) > 0) {
984 return (int) $val_rec['usr_id'];
985 }
986
987 return null;
988 }
989
993 private function lookupSCOId(string $a_referrer): int
994 {
995 global $DIC;
996 $ilDB = $DIC->database();
997 $ilUser = $DIC->user();
998
999 //non specific SCO entries
1000 if ($a_referrer == "0") {
1001 return 0;
1002 }
1003
1004 $val_set = $ilDB->queryF(
1005 '
1006 SELECT obj_id FROM sc_item,scorm_tree
1007 WHERE (obj_id = child
1008 AND identifierref = %s
1009 AND slm_id = %s)',
1010 array('text','integer'),
1011 array($a_referrer,$this->getID())
1012 );
1013 $val_rec = $ilDB->fetchAssoc($val_set);
1014
1015 return (int) $val_rec["obj_id"];
1016 }
1017
1021 public function getUserIdEmail(string $a_mail): int
1022 {
1023 global $DIC;
1024 $ilDB = $DIC->database();
1025 $ilUser = $DIC->user();
1026
1027 $val_set = $ilDB->queryF(
1028 'SELECT usr_id FROM usr_data WHERE(email=%s)',
1029 array('text'),
1030 array($a_mail)
1031 );
1032 $val_rec = $ilDB->fetchAssoc($val_set);
1033
1034
1035 return (int) $val_rec["usr_id"];
1036 }
1037
1038 //todo
1042 public function sendExportFile(string $a_header, string $a_content): void
1043 {
1044 $timestamp = time();
1045 $refid = $this->getRefId();
1046 $filename = "scorm_tracking_" . $refid . "_" . $timestamp . ".csv";
1047 ilUtil::deliverData($a_header . $a_content, $filename);
1048 exit;
1049 }
1050
1056 public static function _getAllScoIds(int $a_id): array
1057 {
1058 global $DIC;
1059 $ilDB = $DIC->database();
1060
1061 $scos = array();
1062
1063 $val_set = $ilDB->queryF(
1064 '
1065 SELECT scorm_object.obj_id,
1066 scorm_object.title,
1067 scorm_object.c_type,
1068 scorm_object.slm_id,
1069 scorm_object.obj_id scoid
1070 FROM scorm_object,sc_item,sc_resource
1071 WHERE(scorm_object.slm_id = %s
1072 AND scorm_object.obj_id = sc_item.obj_id
1073 AND sc_item.identifierref = sc_resource.import_id
1074 AND sc_resource.scormtype = %s)
1075 GROUP BY scorm_object.obj_id,
1076 scorm_object.title,
1077 scorm_object.c_type,
1078 scorm_object.slm_id,
1079 scorm_object.obj_id ',
1080 array('integer', 'text'),
1081 array($a_id,'sco')
1082 );
1083
1084 while ($val_rec = $ilDB->fetchAssoc($val_set)) {
1085 $scos[] = $val_rec['scoid'];
1086 }
1087 return $scos;
1088 }
1089
1098 public static function _getStatusForUser(int $a_id, int $a_user, array $a_allScoIds, bool $a_numerical = false): bool
1099 {
1100 global $DIC;
1101 $ilDB = $DIC->database();
1102 $lng = $DIC->language();
1103
1104 $scos = $a_allScoIds;
1105 //check if all SCO's are completed
1106 $scos_c = implode(',', $scos);
1107
1108 $val_set = $ilDB->queryF(
1109 '
1110 SELECT * FROM scorm_tracking
1111 WHERE (user_id = %s
1112 AND obj_id = %s
1113 AND ' . $ilDB->in('sco_id', $scos, false, 'integer') . '
1114 AND ((lvalue = %s AND ' . $ilDB->like('rvalue', 'clob', 'completed') . ')
1115 OR (lvalue = %s AND ' . $ilDB->like('rvalue', 'clob', 'passed') . ')))',
1116 array('integer','integer','text','text'),
1117 array($a_user,$a_id,'cmi.core.lesson_status', 'cmi.core.lesson_status')
1118 );
1119 while ($val_rec = $ilDB->fetchAssoc($val_set)) {
1120 $key = array_search($val_rec['sco_id'], $scos);
1121 unset($scos[$key]);
1122 }
1123 $completion = false;
1124 //check for completion
1125 if (count($scos) == 0) {
1126 $completion = ($a_numerical === true) ? true : $lng->txt("cont_complete");
1127 }
1128 if (count($scos) > 0) {
1129 $completion = ($a_numerical === true) ? false : $lng->txt("cont_incomplete");
1130 }
1131 return $completion;
1132 }
1133
1140 public static function _getCourseCompletionForUser(int $a_id, int $a_user): bool
1141 {
1143 }
1144
1148 public function getAllScoIds(): array
1149 {
1150 global $DIC;
1151 $ilDB = $DIC->database();
1152
1153 $scos = array();
1154 //get all SCO's of this object
1155 $val_set = $ilDB->queryF(
1156 '
1157 SELECT scorm_object.obj_id,
1158 scorm_object.title,
1159 scorm_object.c_type,
1160 scorm_object.slm_id,
1161 scorm_object.obj_id scoid
1162 FROM scorm_object, sc_item,sc_resource
1163 WHERE(scorm_object.slm_id = %s
1164 AND scorm_object.obj_id = sc_item.obj_id
1165 AND sc_item.identifierref = sc_resource.import_id
1166 AND sc_resource.scormtype = %s )
1167 GROUP BY scorm_object.obj_id,
1168 scorm_object.title,
1169 scorm_object.c_type,
1170 scorm_object.slm_id,
1171 scorm_object.obj_id',
1172 array('integer','text'),
1173 array($this->getId(),'sco')
1174 );
1175
1176 while ($val_rec = $ilDB->fetchAssoc($val_set)) {
1177 $scos[] = $val_rec['scoid'];
1178 }
1179 return $scos;
1180 }
1181
1182 public function getStatusForUser(int $a_user, array $a_allScoIds, bool $a_numerical = false): bool
1183 {
1184 global $DIC;
1185 $ilDB = $DIC->database();
1186 $scos = $a_allScoIds;
1187 //loook up status
1188 //check if all SCO's are completed
1189 $scos_c = implode(',', $scos);
1190
1191 $val_set = $ilDB->queryF(
1192 '
1193 SELECT sco_id FROM scorm_tracking
1194 WHERE (user_id = %s
1195 AND obj_id = %s
1196 AND ' . $ilDB->in('sco_id', $scos, false, 'integer') . '
1197 AND ((lvalue = %s AND ' . $ilDB->like('rvalue', 'clob', 'completed') . ') OR (lvalue = %s AND ' . $ilDB->like('rvalue', 'clob', 'passed') . ') ) )',
1198 array('integer','integer','text','text',),
1199 array($a_user,$this->getID(),'cmi.core.lesson_status','cmi.core.lesson_status')
1200 );
1201 while ($val_rec = $ilDB->fetchAssoc($val_set)) {
1202 $key = array_search($val_rec['sco_id'], $scos);
1203 unset($scos[$key]);
1204 }
1205 //check for completion
1206 if (count($scos) == 0) {
1207 $completion = ($a_numerical === true) ? true : $this->lng->txt("cont_complete");
1208 }
1209 if (count($scos) > 0) {
1210 $completion = ($a_numerical === true) ? false : $this->lng->txt("cont_incomplete");
1211 }
1212 return $completion;
1213 }
1214
1215 public function getCourseCompletionForUser(int $a_user): bool
1216 {
1217 return $this->getStatusForUser($a_user, $this->getAllScoIds(), true);
1218 }
1219
1223 public static function _removeTrackingDataForUser(int $user_id): void
1224 {
1225 global $DIC;
1226 $ilDB = $DIC->database();
1227 //gobjective
1228 $ilDB->manipulateF(
1229 'DELETE FROM scorm_tracking WHERE user_id = %s',
1230 array('integer'),
1231 array($user_id)
1232 );
1233 $ilDB->manipulateF(
1234 'DELETE FROM sahs_user WHERE user_id = %s',
1235 array('integer'),
1236 array($user_id)
1237 );
1238 }
1239
1240 public static function _getScoresForUser(int $a_item_id, int $a_user_id): array
1241 {
1242 global $DIC;
1243 $ilDB = $DIC->database();
1244
1245 $retAr = array("raw" => null, "max" => null, "scaled" => null);
1246 $val_set = $ilDB->queryF(
1247 "
1248 SELECT lvalue, rvalue FROM scorm_tracking
1249 WHERE sco_id = %s
1250 AND user_id = %s
1251 AND (lvalue = 'cmi.core.score.raw' OR lvalue = 'cmi.core.score.max')",
1252 array('integer', 'integer'),
1253 array($a_item_id, $a_user_id)
1254 );
1255 while ($val_rec = $ilDB->fetchAssoc($val_set)) {
1256 if ($val_rec['lvalue'] === "cmi.core.score.raw") {
1257 $retAr["raw"] = $val_rec["rvalue"];
1258 }
1259 if ($val_rec['lvalue'] === "cmi.core.score.max") {
1260 $retAr["max"] = $val_rec["rvalue"];
1261 }
1262 }
1263 if ($retAr["raw"] != null && $retAr["max"] != null) {
1264 $retAr["scaled"] = ($retAr["raw"] / $retAr["max"]);
1265 }
1266
1267 return $retAr;
1268 }
1269
1270 public function getLastVisited(int $user_id): string
1271 {
1272 global $DIC;
1273 $ilDB = $DIC->database();
1274 $val_set = $ilDB->queryF(
1275 'SELECT last_visited FROM sahs_user WHERE obj_id = %s AND user_id = %s',
1276 array('integer','integer'),
1277 array($this->getID(),$user_id)
1278 );
1279 while ($val_rec = $ilDB->fetchAssoc($val_set)) {
1280 if ($val_rec["last_visited"] != null) {
1281 return "" . $val_rec["last_visited"];
1282 }
1283 }
1284 return '0';
1285 }
1286
1287 public function deleteTrackingDataOfUsers(array $a_users): void
1288 {
1289 global $DIC;
1290 $ilDB = $DIC->database();
1291
1293
1294 foreach ($a_users as $usr) {
1295 $user = (int) $usr;
1296 $ilDB->manipulateF(
1297 '
1298 DELETE FROM scorm_tracking
1299 WHERE user_id = %s
1300 AND obj_id = %s',
1301 array('integer', 'integer'),
1302 array($user, $this->getID())
1303 );
1304
1305 $ilDB->manipulateF(
1306 '
1307 DELETE FROM sahs_user
1308 WHERE user_id = %s
1309 AND obj_id = %s',
1310 array('integer', 'integer'),
1311 array($user, $this->getID())
1312 );
1313
1314 ilLPStatusWrapper::_updateStatus($this->getId(), $user);
1315 }
1316 }
1317}
$datetime
$filename
Definition: buildRTE.php:78
foreach($mandatory_scripts as $file) $timestamp
Definition: buildRTE.php:70
static _recordReadEvent(string $a_type, int $a_ref_id, int $obj_id, int $usr_id, bool $isCatchupWriteEvents=true, $a_ext_rc=null, $a_ext_time=null)
static _lookupReadEvents($obj_id, $usr_id=null)
Reads all read events which occured on the object.
static _deleteReadEventsForUsers(int $a_obj_id, array $a_user_ids)
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
static _lookupPercentage(int $a_obj_id, int $a_user_id)
Lookup percentage.
const LP_STATUS_FAILED
const LP_STATUS_IN_PROGRESS_NUM
const LP_STATUS_NOT_ATTEMPTED_NUM
static _lookupStatus(int $a_obj_id, int $a_user_id, bool $a_create=true)
Lookup status.
const LP_STATUS_FAILED_NUM
const LP_STATUS_NOT_ATTEMPTED
const LP_STATUS_IN_PROGRESS
static __readStatus(int $a_obj_id, int $user_id)
@noinspection PhpInconsistentReturnPointsInspection
Class ilObjSCORMLearningModule.
getDataDirectory(?string $mode="filesystem")
get data directory of lm
Class ilObjSCORMLearningModule.
kindlyToDateTime(string $format, string $maybe_datetime, ?DateTimeImmutable $default=null)
getTrackingDataPerUser(int $a_sco_id, int $a_user_id)
Get tracking data per user.
getUserIdEmail(string $a_mail)
assumes that only one account exists for a mailadress
setLearningProgressSettingsAtUpload()
set settings for learning progress determination per default at upload
getModuleVersionForUsers()
Get module version for users.
__construct(int $a_id=0, bool $a_call_by_reference=true)
Constructor.
getModuleVersionForUser(int $a_user_id)
get module version that tracking data for a user was recorded on
importRaw(string $a_file)
Import raw data.
static _getStatusForUser(int $a_id, int $a_user, array $a_allScoIds, bool $a_numerical=false)
Get the status of a SCORM module for a given user.
static _removeTrackingDataForUser(int $user_id)
to be called from IlObjUser
getAttemptsForUser(int $a_user_id)
get number of attempts for a certain user and package
getAttemptsForUsers()
Get attempts for all users.
exportSelected(bool $a_all, array $a_users=array())
Export selected user tracking data @global ilObjUser $ilUser.
lookupSCOId(string $a_referrer)
resolves manifest SCOID to internal ILIAS SCO ID
sendExportFile(string $a_header, string $a_content)
send export file to browser
static _getTrackingItems(int $a_obj_id)
get all tracking items of scorm object
static _getCourseCompletionForUser(int $a_id, int $a_user)
Get the completion of a SCORM module for a given user.
parseUserId(string $il_id)
Parse il_usr_123_6 id.
static _getAllScoIds(int $a_id)
Get an array of id's for all Sco's in the module.
importSuccessForSahsUser(int $user_id, ?DateTimeImmutable $last_access, int $status, ?int $attempts=null, ?int $percentage_completed=null, ?int $sco_total_time_sec=null)
getTrackedItems()
get all tracked items of current user
static _getScoresForUser(int $a_item_id, int $a_user_id)
getStatusForUser(int $a_user, array $a_allScoIds, bool $a_numerical=false)
static getInstance(int $obj_id)
ilTree $tree
static userDataArrayForExport(int $user, bool $b_allowExportPrivacy=false)
SCORM Object Tree.
ILIAS Setting Class.
getNodeData(int $a_node_id, ?int $a_tree_pk=null)
get all information of a node.
getSubTree(array $a_node, bool $a_with_data=true, array $a_type=[])
get all nodes in the subtree under specified node
static deliverData(string $a_data, string $a_filename, string $mime="application/octet-stream")
exit
$res
Definition: ltiservices.php:69
if($clientAssertionType !='urn:ietf:params:oauth:client-assertion-type:jwt-bearer'|| $grantType !='client_credentials') $parts
Definition: ltitoken.php:61
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
global $lng
Definition: privfeed.php:31
global $ilSetting
Definition: privfeed.php:31
$ilErr
Definition: raiseError.php:33
global $DIC
Definition: shib_login.php:26
$lm_set