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