ILIAS  release_8 Revision v8.19
All Data Structures Namespaces Files Functions Variables Modules Pages
class.ilObjSCORMLearningModule.php
Go to the documentation of this file.
1 <?php
2 
3 declare(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 
181  public function setLearningProgressSettingsAtUpload(): void
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();
522  $privacy = ilPrivacySettings::getInstance();
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;
653  if ($status == ilLPStatus::LP_STATUS_COMPLETED_NUM) {
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 
672  if ($status == ilLPStatus::LP_STATUS_COMPLETED_NUM) {
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  }
720  ilLPStatusWrapper::_refreshStatus($this->getId(), $users);
721  return true;
722  }
723 
724  public function importSuccessForSahsUser(
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);
926  ilLPStatusWrapper::_refreshStatus($this->getId(), $users);
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 
1279  ilChangeEvent::_deleteReadEventsForUsers($this->getId(), $a_users);
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 
1302  }
1303  }
1304 }
const LP_STATUS_COMPLETED_NUM
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.
$res
Definition: ltiservices.php:69
static _getCourseCompletionForUser(int $a_id, int $a_user)
Get the completion of a SCORM module for a given user.
exit
Definition: login.php:28
getNodeData(int $a_node_id, ?int $a_tree_pk=null)
get all information of a node.
getUserIdEmail(string $a_mail)
assumes that only one account exists for a mailadress
getAttemptsForUsers()
Get attempts for all users.
__construct(int $a_id=0, bool $a_call_by_reference=true)
Constructor.
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...
if($clientAssertionType !='urn:ietf:params:oauth:client-assertion-type:jwt-bearer'|| $grantType !='client_credentials') $parts
Definition: ltitoken.php:64
const LP_STATUS_NOT_ATTEMPTED
importSuccessForSahsUser(int $user_id, string $last_access, int $status, ?int $attempts=null, ?int $percentage_completed=null, ?int $sco_total_time_sec=null)
static _removeTrackingDataForUser(int $user_id)
to be called from IlObjUser
getTrackedItems()
get all tracked items of current user
static _lookupPercentage(int $a_obj_id, int $a_user_id)
Lookup percentage.
ilTree $tree
const LP_STATUS_IN_PROGRESS_NUM
loadLanguageModule(string $a_module)
Load language module.
static deliverData(string $a_data, string $a_filename, string $mime="application/octet-stream")
static _getTrackingItems(int $a_obj_id)
get all tracking items of scorm object
getModuleVersionForUser(int $a_user_id)
get module version that tracking data for a user was recorded on
static now()
Return current timestamp in Y-m-d H:i:s format.
$ilErr
Definition: raiseError.php:17
const LP_STATUS_IN_PROGRESS
getStatusForUser(int $a_user, array $a_allScoIds, bool $a_numerical=false)
global $DIC
Definition: feed.php:28
static _getScoresForUser(int $a_item_id, int $a_user_id)
const LP_STATUS_FAILED
lookupSCOId(string $a_referrer)
resolves manifest SCOID to internal ILIAS SCO ID
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)
ilLanguage $lng
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)
string $key
Consumer key/client ID value.
Definition: System.php:193
static _refreshStatus(int $a_obj_id, ?array $a_users=null)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
sendExportFile(string $a_header, string $a_content)
send export file to browser
getTrackingDataPerUser(int $a_sco_id, int $a_user_id)
Get tracking data per user.
$query
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static _lookupStatus(int $a_obj_id, int $a_user_id, bool $a_create=true)
Lookup status.
static _lookupReadEvents($obj_id, $usr_id=null)
Reads all read events which occured on the object.
importRaw(string $a_file)
Import raw data.
getModuleVersionForUsers()
Get module version for users.
$filename
Definition: buildRTE.php:78
foreach($mandatory_scripts as $file) $timestamp
Definition: buildRTE.php:70
const LP_STATUS_NOT_ATTEMPTED_NUM
getDataDirectory(?string $mode="filesystem")
get data directory of lm
static __readStatus(int $a_obj_id, int $user_id)
PhpInconsistentReturnPointsInspection
$lm_set
exportSelected(bool $a_all, array $a_users=array())
Export selected user tracking data ilObjUser $ilUser.
global $ilSetting
Definition: privfeed.php:17
setLearningProgressSettingsAtUpload()
set settings for learning progress determination per default at upload
__construct(Container $dic, ilPlugin $plugin)
$ilUser
Definition: imgupload.php:34
getTrackedUsers(string $a_search)
Return the last access timestamp for a given user.
static _deleteReadEventsForUsers(int $a_obj_id, array $a_user_ids)
getSubTree(array $a_node, bool $a_with_data=true, array $a_type=[])
get all nodes in the subtree under specified node
getAttemptsForUser(int $a_user_id)
get number of attempts for a certain user and package
static getInstance(int $obj_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static _getAllScoIds(int $a_id)
Get an array of id&#39;s for all Sco&#39;s in the module.
parseUserId(string $il_id)
Parse il_usr_123_6 id.
Class ilObjSCORMLearningModule.
const LP_STATUS_FAILED_NUM
static _updateStatus(int $a_obj_id, int $a_usr_id, ?object $a_obj=null, bool $a_percentage=false, bool $a_force_raise=false)
ilObjUser $user