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