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