ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
class.ilObjSCORMLearningModule.php
Go to the documentation of this file.
1 <?php
2 
3 /* Copyright (c) 1998-2010 ILIAS open source, Extended GPL, see docs/LICENSE */
4 
5 require_once "./Services/Object/classes/class.ilObject.php";
6 require_once "./Modules/ScormAicc/classes/class.ilObjSCORMValidator.php";
7 require_once "./Modules/ScormAicc/classes/class.ilObjSAHSLearningModule.php";
8 
18 {
20 
27  function __construct($a_id = 0, $a_call_by_reference = true)
28  {
29  $this->type = "sahs";
30  parent::__construct($a_id,$a_call_by_reference);
31  }
32 
33 
40  function validate($directory)
41  {
42  $this->validator = new ilObjSCORMValidator($directory);
43  $returnValue = $this->validator->validate();
44  return $returnValue;
45  }
46 
48  {
49  if(is_object($this->validator))
50  {
51  return $this->validator->getSummary();
52  }
53  return "";
54  }
55 
56  function getTrackingItems()
57  {
59  }
60 
61 
66  static function _getTrackingItems($a_obj_id)
67  {
68  include_once("./Modules/ScormAicc/classes/SCORM/class.ilSCORMTree.php");
69  $tree = new ilSCORMTree($a_obj_id);
70  $root_id = $tree->readRootId();
71 
72  $items = array();
73  $childs = $tree->getSubTree($tree->getNodeData($root_id));
74 
75  foreach($childs as $child)
76  {
77  if($child["c_type"] == "sit")
78  {
79  include_once("./Modules/ScormAicc/classes/SCORM/class.ilSCORMItem.php");
80  $sc_item = new ilSCORMItem($child["obj_id"]);
81  if ($sc_item->getIdentifierRef() != "")
82  {
83  $items[count($items)] = $sc_item;
84  }
85  }
86  }
87 
88  return $items;
89  }
90 
95  function readObject()
96  {
97  global $ilErr;
98 
99  $needs_convert = false;
100 
101  // convert imsmanifest.xml file in iso to utf8 if needed
102 
103  $manifest_file = $this->getDataDirectory()."/imsmanifest.xml";
104 
105  // check if manifestfile exists and space left on device...
106  $check_for_manifest_file = is_file($manifest_file);
107 
108  // if no manifestfile
109  if (!$check_for_manifest_file)
110  {
111  $this->ilias->raiseError($this->lng->txt("Manifestfile $manifest_file not found!"), $this->ilias->error_obj->MESSAGE);
112  return;
113  }
114 
115  if ($check_for_manifest_file)
116  {
117  $manifest_file_array = file($manifest_file);
118  foreach($manifest_file_array as $mfa)
119  {
120  // if (seems_not_utf8($mfa))
121  if (@iconv('UTF-8', 'UTF-8', $mfa) != $mfa)
122  {
123  $needs_convert = true;
124  break;
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  // if $manifest_file needs to be converted to UTF8
137  if ($needs_convert)
138  {
139  // if file exists and enough space left on device
140  if ($check_for_manifest_file && ($check_disc_free > 1))
141  {
142 
143  // create backup from original
144  if (!copy($manifest_file, $manifest_file.".old"))
145  {
146  echo "Failed to copy $manifest_file...<br>\n";
147  }
148 
149  // read backupfile, convert each line to utf8, write line to new file
150  // php < 4.3 style
151  $f_write_handler = fopen($manifest_file.".new", "w");
152  $f_read_handler = fopen($manifest_file.".old", "r");
153  while (!feof($f_read_handler))
154  {
155  $zeile = fgets($f_read_handler);
156  //echo mb_detect_encoding($zeile);
157  fputs($f_write_handler, utf8_encode($zeile));
158  }
159  fclose($f_read_handler);
160  fclose($f_write_handler);
161 
162  // copy new utf8-file to imsmanifest.xml
163  if (!copy($manifest_file.".new", $manifest_file))
164  {
165  echo "Failed to copy $manifest_file...<br>\n";
166  }
167 
168  if (!@is_file($manifest_file))
169  {
170  $this->ilias->raiseError($this->lng->txt("cont_no_manifest"),
171  $this->ilias->error_obj->WARNING);
172  }
173  }
174  else
175  {
176  // gives out the specific error
177 
178  if (!($check_disc_free > 1))
179  $this->ilias->raiseError($this->lng->txt("Not enough space left on device!"),$this->ilias->error_obj->MESSAGE);
180  return;
181  }
182 
183  }
184  else
185  {
186  // check whether file starts with BOM (that confuses some sax parsers, see bug #1795)
187  $hmani = fopen($manifest_file, "r");
188  $start = fread($hmani, 3);
189  if (strtolower(bin2hex($start)) == "efbbbf")
190  {
191  $f_write_handler = fopen($manifest_file.".new", "w");
192  while (!feof($hmani))
193  {
194  $n = fread($hmani, 900);
195  fputs($f_write_handler, $n);
196  }
197  fclose($f_write_handler);
198  fclose($hmani);
199 
200  // copy new utf8-file to imsmanifest.xml
201  if (!copy($manifest_file.".new", $manifest_file))
202  {
203  echo "Failed to copy $manifest_file...<br>\n";
204  }
205  }
206  else
207  {
208  fclose($hmani);
209  }
210  }
211 
212  //validate the XML-Files in the SCORM-Package
213  if ($_POST["validate"] == "y")
214  {
215  if (!$this->validate($this->getDataDirectory()))
216  {
217  $ilErr->raiseError("<b>Validation Error(s):</b><br>".$this->getValidationSummary(),$ilErr->MESSAGE);
218  }
219  }
220 
221  // start SCORM package parser
222  include_once ("./Modules/ScormAicc/classes/SCORM/class.ilSCORMPackageParser.php");
223  // todo determine imsmanifest.xml path here...
224  $slmParser = new ilSCORMPackageParser($this, $manifest_file);
225  $slmParser->startParsing();
226  return $slmParser->getPackageTitle();
227  }
228 
233  {
234  global $ilSetting;
235  //condition 1
236  $lm_set = new ilSetting("lm");
237  if ($lm_set->get('scorm_lp_auto_activate') != 1) return;
238  //condition 2
239  include_once("./Services/Tracking/classes/class.ilObjUserTracking.php");
240  if (ilObjUserTracking::_enabledLearningProgress() == false) return;
241 
242  //set Learning Progress to Automatic by Collection of SCORM Items
243  include_once("./Services/Tracking/classes/class.ilLPObjSettings.php");
244  $lm_set = new ilLPObjSettings($this->getId());
246  $lm_set->insert();
247 
248  //select all SCOs as relevant for Learning Progress
249  include_once("Services/Tracking/classes/collection/class.ilLPCollectionOfSCOs.php");
250  $collection = new ilLPCollectionOfSCOs($this->getId(), ilLPObjSettings::LP_MODE_SCORM);
251  $scos = array();
252  foreach($collection->getPossibleItems() as $sco_id => $item)
253  {
254  $scos[] = $sco_id;
255  }
256  $collection->activateEntries($scos);
257  }
261  function getTrackedItems()
262  {
263  global $ilDB, $ilUser;
264 
265  $sco_set = $ilDB->queryF('
266  SELECT DISTINCT sco_id FROM scorm_tracking WHERE obj_id = %s',
267  array('integer'),array($this->getId()));
268 
269  $items = array();
270  while($sco_rec = $ilDB->fetchAssoc($sco_set))
271  {
272  include_once("./Modules/ScormAicc/classes/SCORM/class.ilSCORMItem.php");
273  $sc_item = new ilSCORMItem($sco_rec["sco_id"]);
274  if ($sc_item->getIdentifierRef() != "")
275  {
276  $items[count($items)] = $sc_item;
277  }
278  }
279 
280  return $items;
281  }
282 
290  public static function _lookupLastAccess($a_obj_id, $a_usr_id)
291  {
292  global $ilDB;
293 
294  $result = $ilDB->queryF('
295  SELECT last_access FROM sahs_user
296  WHERE obj_id = %s
297  AND user_id = %s',
298  array('integer','integer'), array($a_obj_id,$a_usr_id));
299 
300  if ($ilDB->numRows($result))
301  {
302  $row = $ilDB->fetchAssoc($result);
303  return $row["last_access"];
304  }
305  return "";
306  }
307 
308  function getTrackedUsers($a_search)
309  {
310  global $ilDB, $ilUser;
311 //TODO: UK last_access is not correct if no Commit or last_visited_sco
312 // $query = 'SELECT user_id,MAX(c_timestamp) last_access, lastname, firstname FROM scorm_tracking st ' .
313  $query = 'SELECT user_id, last_access, lastname, firstname FROM sahs_user st ' .
314  'JOIN usr_data ud ON st.user_id = ud.usr_id ' .
315  'WHERE obj_id = ' . $ilDB->quote($this->getId(), 'integer');
316  if($a_search) {
317 // $query .= ' AND (' . $ilDB->like('lastname', 'text', '%' . $a_search . '%') . ' OR ' . $ilDB->like('firstname', 'text', '%' . $a_search . '%') .')';
318  $query .= ' AND ' . $ilDB->like('lastname', 'text', '%' . $a_search . '%');
319  }
320  $query .= ' GROUP BY user_id, lastname, firstname, last_access';
321  $sco_set = $ilDB->query($query);
322 
323  $items = array();
324  while($sco_rec = $ilDB->fetchAssoc($sco_set))
325  {
326  $items[] = $sco_rec;
327  }
328  return $items;
329  }
330 
331 
337  public function getAttemptsForUsers()
338  {
339  global $ilDB;
340  $query = 'SELECT user_id, package_attempts FROM sahs_user WHERE obj_id = ' . $ilDB->quote($this->getId(), 'integer') . ' ';
341  $res = $ilDB->query($query);
342 
343  $attempts = array();
344  while($row = $res->fetchRow(ilDBConstants::FETCHMODE_ASSOC))
345  {
346  $attempts[$row['user_id']] = (int) $row['package_attempts'];
347  }
348  return $attempts;
349  }
350 
351 
355  function getAttemptsForUser($a_user_id){
356  global $ilDB;
357  $val_set = $ilDB->queryF('SELECT package_attempts FROM sahs_user WHERE obj_id = %s AND user_id = %s',
358  array('integer','integer'),
359  array($this->getId(),$a_user_id,0));
360 
361  $val_rec = $ilDB->fetchAssoc($val_set);
362 
363  if ($val_rec["package_attempts"] == null) {
364  $val_rec["package_attempts"]="";
365  }
366  return $val_rec["package_attempts"];
367  }
368 
369 
374  public function getModuleVersionForUsers()
375  {
376  global $ilDB;
377  $query = 'SELECT user_id, module_version FROM sahs_user WHERE obj_id = ' . $ilDB->quote($this->getId(), 'integer') . ' ';
378  $res = $ilDB->query($query);
379 
380  $versions = array();
381  while($row = $res->fetchRow(ilDBConstants::FETCHMODE_ASSOC))
382  {
383  $versions[$row['user_id']] = (int) $row['module_version'];
384  }
385  return $versions;
386  }
387 
388 
392  function getModuleVersionForUser($a_user_id){
393  global $ilDB;
394  $val_set = $ilDB->queryF('SELECT module_version FROM sahs_user WHERE obj_id = %s AND user_id = %s',
395  array('integer','integer'),
396  array($this->getId(),$a_user_id,0));
397 
398  $val_rec = $ilDB->fetchAssoc($val_set);
399 
400  if ($val_rec["module_version"] == null) {
401  $val_rec["module_version"]="";
402  }
403  return $val_rec["module_version"];
404  }
405 
413  function getTrackingDataPerUser($a_sco_id, $a_user_id)
414  {
415  global $ilDB;
416 
417  $data_set = $ilDB->queryF('
418  SELECT * FROM scorm_tracking
419  WHERE user_id = %s
420  AND sco_id = %s
421  AND obj_id = %s
422  ORDER BY lvalue',
423  array('integer','integer','integer'),
424  array($a_user_id,$a_sco_id,$this->getId()));
425 
426  $data = array();
427  while($data_rec = $ilDB->fetchAssoc($data_set)) {
428  $data[] = $data_rec;
429  }
430 
431  return $data;
432  }
433 
434  function getTrackingDataAgg($a_user_id)
435  {
436  global $ilDB;
437 
438  // get all users with any tracking data
439  $sco_set = $ilDB->queryF('
440  SELECT DISTINCT sco_id FROM scorm_tracking
441  WHERE obj_id = %s
442  AND user_id = %s
443  AND sco_id <> %s',
444  array('integer','integer','integer'),
445  array($this->getId(),$a_user_id,0));
446 
447  $data = array();
448  while($sco_rec = $ilDB->fetchAssoc($sco_set))
449  {
450  $data_set = $ilDB->queryF('
451  SELECT * FROM scorm_tracking
452  WHERE obj_id = %s
453  AND sco_id = %s
454  AND user_id = %s
455  AND lvalue <> %s
456  AND (lvalue = %s
457  OR lvalue = %s
458  OR lvalue = %s)',
459  array('integer','integer','integer','text','text','text','text'),
460  array($this->getId(),
461  $sco_rec["sco_id"],
462  $a_user_id,
463  "package_attempts",
464  "cmi.core.lesson_status",
465  "cmi.core.total_time",
466  "cmi.core.score.raw")
467  );
468 
469  $score = $time = $status = "";
470 
471  while($data_rec = $ilDB->fetchAssoc($data_set))
472  {
473  switch($data_rec["lvalue"])
474  {
475  case "cmi.core.lesson_status":
476  $status = $data_rec["rvalue"];
477  break;
478 
479  case "cmi.core.total_time":
480  $time = $data_rec["rvalue"];
481  break;
482 
483  case "cmi.core.score.raw":
484  $score = $data_rec["rvalue"];
485  break;
486  }
487  }
488  //create sco_object
489  include_once './Modules/ScormAicc/classes/SCORM/class.ilSCORMItem.php';
490  $sc_item = new ilSCORMItem($sco_rec["sco_id"]);
491  $data[] = array("sco_id"=>$sco_rec["sco_id"], "title" => $sc_item->getTitle(),
492  "score" => $score, "time" => $time, "status" => $status);
493 
494  }
495  return (array) $data;
496  }
497 
498  function getTrackingDataAggSco($a_sco_id)
499  {
500  global $ilDB;
501 
502  // get all users with any tracking data
503  $user_set = $ilDB->queryF('
504  SELECT DISTINCT user_id FROM scorm_tracking
505  WHERE obj_id = %s
506  AND sco_id = %s',
507  array('integer','integer'),
508  array($this->getId(),$a_sco_id));
509 
510  $data = array();
511  while($user_rec = $ilDB->fetchAssoc($user_set))
512  {
513 
514  $data_set = $ilDB->queryF('
515  SELECT * FROM scorm_tracking
516  WHERE obj_id = %s
517  AND sco_id = %s
518  AND user_id = %s
519  AND (lvalue = %s
520  OR lvalue = %s
521  OR lvalue = %s)',
522  array('integer','integer','integer','text','text','text'),
523  array($this->getId(),
524  $a_sco_id,
525  $user_rec["user_id"],
526  "cmi.core.lesson_status",
527  "cmi.core.total_time",
528  "cmi.core.score.raw")
529  );
530 
531  $score = $time = $status = "";
532 
533  while($data_rec = $ilDB->fetchAssoc($data_set))
534  {
535  switch($data_rec["lvalue"])
536  {
537  case "cmi.core.lesson_status":
538  $status = $data_rec["rvalue"];
539  break;
540 
541  case "cmi.core.total_time":
542  $time = $data_rec["rvalue"];
543  break;
544 
545  case "cmi.core.score.raw":
546  $score = $data_rec["rvalue"];
547  break;
548  }
549  }
550 
551  $data[] = array("user_id" => $user_rec["user_id"],
552  "score" => $score, "time" => $time, "status" => $status);
553  }
554 
555  return $data;
556  }
557 
558 
559 
567  public function exportSelected($a_all, $a_users = array())
568  {
569  global $ilDB, $ilUser;
570  include_once('./Modules/ScormAicc/classes/class.ilSCORMTrackingItems.php');
571  include_once("./Services/Tracking/classes/class.ilLearningProgressBaseGUI.php");
572  include_once('./Services/PrivacySecurity/classes/class.ilPrivacySettings.php');
573  $privacy = ilPrivacySettings::_getInstance();
574  $allowExportPrivacy = $privacy->enabledExportSCORM();
575 
576  $csv = "";
577  $query = 'SELECT * FROM sahs_user WHERE obj_id = %s';
578  if (count($a_users) >0) $query .= ' AND '.$ilDB->in('user_id', $a_users, false, 'integer');
579  $res = $ilDB->queryF(
580  $query,
581  array('integer'),
582  array($this->getId())
583  );
584  while ($data = $ilDB->fetchAssoc($res)) {
585  $csv = $csv. $data["obj_id"]
586  . ";\"" . $this->getTitle() ."\""
587  . ";" . $data["module_version"]
588  . ";\"" . implode("\";\"",ilSCORMTrackingItems::userDataArrayForExport($data["user_id"], $allowExportPrivacy)) ."\""
589  . ";\"" . $data["last_access"] ."\""
590  . ";\"" . ilLearningProgressBaseGUI::__readStatus($data["obj_id"],$data["user_id"]) ."\"" //not $data["status"] because modifications to learning progress could have made before export
591  . ";" . $data["package_attempts"]
592  . ";" . $data["percentage_completed"]
593  . ";" . $data["sco_total_time_sec"]
594 // . ";\"" . $certificateDate ."\""
595  . "\n";
596  }
598  $header = "LearningModuleId;LearningModuleTitle;LearningModuleVersion;".str_replace(',',';',$udh["cols"]).";"
599  . "LastAccess;Status;Attempts;percentageCompletedSCOs;SumTotal_timeSeconds\n";
600 
601  $this->sendExportFile($header, $csv);
602  }
603 
604 
605  function importTrackingData($a_file)
606  {
607  global $ilDB, $ilUser;
608 
609  $error = 0;
610  //echo file_get_contents($a_file);
611  $method = null;
612 
613  //lets import
614  $fhandle = fopen($a_file, "r");
615 
616  //the top line is the field names
617  $fields = fgetcsv($fhandle, pow(2, 16), ';');
618  //lets check the import method
619  fclose($fhandle);
620 
621  switch($fields[0])
622  {
623  case "Scoid":
624  case "SCO-Identifier":
625  $error = $this->importRaw($a_file);
626  break;
627  case "Department":
628  case "LearningModuleId":
629  $error = $this->importSuccess($a_file);
630  break;
631  default:
632  return -1;
633  break;
634  }
635  return $error;
636  }
637 
638  function importSuccess($a_file) {
639 
640  global $ilDB, $ilUser;
641  include_once("./Services/Tracking/classes/class.ilLPStatus.php");
642  $scos = array();
643  //get all SCO's of this object ONLY RELEVANT!
644  include_once './Services/Object/classes/class.ilObjectLP.php';
645  $olp = ilObjectLP::getInstance($this->getId());
646  $collection = $olp->getCollectionInstance();
647  if($collection)
648  {
649  $scos = $collection->getItems();
650  }
651 
652  $fhandle = fopen($a_file, "r");
653 
654  $obj_id = $this->getID();
655  $fields = fgetcsv($fhandle, pow(2, 16), ';');
656  $users = array();
657  $usersToDelete = array();
658  while(($csv_rows = fgetcsv($fhandle, pow(2, 16), ";")) !== FALSE)
659  {
660  $data = array_combine($fields, $csv_rows);
661  //no check the format - sufficient to import users
662  if ($data["Login"]) $user_id = $this->get_user_id($data["Login"]);
663  if ($data["login"]) $user_id = $this->get_user_id($data["login"]);
664  //add mail in future
665  if ($data["user"] && is_numeric($data["user"])) $user_id = (int)$data["user"];
666 
667  if ($user_id>0) {
668 
669  $last_access = ilUtil::now();
670  if ($data['Date']) {
671  $date_ex = explode('.', $data['Date']);
672  $last_access = implode('-', array($date_ex[2], $date_ex[1], $date_ex[0]));
673  }
674  if ($data['LastAccess']) {
675  $last_access = $data['LastAccess'];
676  }
677 
679 
680  if ($data["Status"]) {
681  if (is_int($data["Status"])) $status = $data["Status"];
682  else if ($data["Status"] == "0" || $data["Status"] == "1" || $data["Status"] == "2" || $data["Status"] == "3") $status = (int) $data["Status"];
686  }
687 
688  $attempts = null;
689  if($data["Attempts"]) $attempts = $data["Attempts"];
690 
691  $percentage_completed = 0;
692  if ($status == ilLPStatus::LP_STATUS_COMPLETED_NUM) $percentage_completed = 100;
693  if ($data['percentageCompletedSCOs']) $percentage_completed = $data['percentageCompletedSCOs'];
694 
695  $sco_total_time_sec = null;
696  if ($data['SumTotal_timeSeconds']) $sco_total_time_sec = $data['SumTotal_timeSeconds'];
697 
698  if ($status == ilLPStatus::LP_STATUS_NOT_ATTEMPTED) {
699  $usersToDelete[] = $user_id;
700  } else {
701  $this->importSuccessForSahsUser($user_id, $last_access, $status, $attempts, $percentage_completed, $sco_total_time_sec);
702  $users[] = $user_id;
703  }
704 
705  if ($status == ilLPStatus::LP_STATUS_COMPLETED_NUM) {
706  foreach ($scos as $sco_id)
707  {
708  $statement = $ilDB->queryF('
709  SELECT * FROM scorm_tracking
710  WHERE user_id = %s
711  AND sco_id = %s
712  AND lvalue = %s
713  AND obj_id = %s',
714  array('integer','integer','text','integer'),
715  array($user_id, $sco_id, 'cmi.core.lesson_status',$obj_id)
716  );
717  if($ilDB->numRows($statement) > 0)
718  {
719  $ilDB->update('scorm_tracking',
720  array(
721  'rvalue' => array('clob', 'completed'),
722  'c_timestamp' => array('timestamp', $last_access)
723  ),
724  array(
725  'user_id' => array('integer', $user_id),
726  'sco_id' => array('integer', $sco_id),
727  'lvalue' => array('text', 'cmi.core.lesson_status'),
728  'obj_id' => array('integer', $obj_id)
729  )
730  );
731  }
732  else
733  {
734  $ilDB->insert('scorm_tracking', array(
735  'obj_id' => array('integer', $obj_id),
736  'user_id' => array('integer', $user_id),
737  'sco_id' => array('integer', $sco_id),
738  'lvalue' => array('text', 'cmi.core.lesson_status'),
739  'rvalue' => array('clob', 'completed'),
740  'c_timestamp' => array('timestamp', $last_access)
741  ));
742  }
743  }
744  }
745  } else {
746  //echo "Warning! User $csv_rows[0] does not exist in ILIAS. Data for this user was skipped.\n";
747  }
748  }
749  if (count($usersToDelete)>0) {
750  // include_once("./Services/Tracking/classes/class.ilLPMarks.php");
751  // ilLPMarks::_deleteForUsers($this->getId(), $usersToDelete);
752  $this->deleteTrackingDataOfUsers($usersToDelete);
753  }
754  include_once("./Services/Tracking/classes/class.ilLPStatusWrapper.php");
755  ilLPStatusWrapper::_refreshStatus($this->getId(),$users);
756  return 0;
757  }
758 
759  function importSuccessForSahsUser($user_id, $last_access, $status, $attempts=null, $percentage_completed=null, $sco_total_time_sec=null){
760  global $ilDB;
761  $statement = $ilDB->queryF('SELECT * FROM sahs_user WHERE obj_id = %s AND user_id = %s',
762  array('integer','integer'),
763  array($this->getID(),$user_id)
764  );
765  if($ilDB->numRows($statement) > 0)
766  {
767  $ilDB->update('sahs_user',
768  array(
769  'last_access' => array('timestamp', $last_access),
770  'status' => array('integer', $status),
771  'package_attempts' => array('integer', $attempts),
772  'percentage_completed' => array('integer', $percentage_completed),
773  'sco_total_time_sec' => array('integer', $sco_total_time_sec)
774  ),
775  array(
776  'obj_id' => array('integer', $this->getID()),
777  'user_id' => array('integer', $user_id)
778  )
779  );
780  }
781  else
782  {
783  $ilDB->insert('sahs_user', array(
784  'obj_id' => array('integer', $this->getID()),
785  'user_id' => array('integer', $user_id),
786  'last_access' => array('timestamp', $last_access),
787  'status' => array('integer', $status),
788  'package_attempts' => array('integer', $attempts),
789  'percentage_completed' => array('integer', $percentage_completed),
790  'sco_total_time_sec' => array('integer', $sco_total_time_sec)
791  ));
792  }
793 
794  include_once("./Services/Tracking/classes/class.ilChangeEvent.php");
795  ilChangeEvent::_recordReadEvent("sahs", (int)$_GET["ref_id"], $this->getID(), $user_id, false, $attempts, $sco_total_time_sec);
796  }
797 
803  private function parseUserId($il_id)
804  {
805  global $ilSetting;
806 
807  $parts = explode('_', $il_id);
808 
809  if(!count((array) $parts))
810  {
811  return 0;
812  }
813  if(!isset($parts[2]) or !isset($parts[3]))
814  {
815  return 0;
816  }
817  if($parts[2] != $ilSetting->get('inst_id',$parts[2]))
818  {
819  return 0;
820  }
821  return $parts[3];
822  }
823 
831  private function importRaw($a_file)
832  {
833  global $ilDB, $ilUser,$lng;
834  $lng->loadLanguageModule("scormtrac");
835 
836  $fhandle = fopen($a_file, "r");
837 
838  $fields = fgetcsv($fhandle, pow(2, 16), ';');
839  $users = array();
840  $a_last_access = array();
841  $a_time = array();
842  $a_package_attempts = array();
843  $a_module_version = array();
844  while(($csv_rows = fgetcsv($fhandle, pow(2, 16), ";")) !== FALSE)
845  {
846  $data = array_combine($fields, $csv_rows);
847  if ($data['Userid']) {
848  $user_id = $this->parseUserId($data['Userid']);
849  }
850  else if ($data[$lng->txt("user")])
851  {
852  if (is_int($data[$lng->txt("user")])) $user_id = $data[$lng->txt("user")];
853  }
854  if ($data[$lng->txt("login")])
855  {
856  $user_id = $this->get_user_id($data[$lng->txt("login")]);
857  }
858  if(!$user_id)
859  {
860  continue;
861  }
862 
863  if ($data['Scoid'])
864  {
865  $il_sco_id = $this->lookupSCOId($data['Scoid']);
866  }
867  if ($data[$lng->txt("identifierref")])
868  {
869  $il_sco_id = $this->lookupSCOId($data[$lng->txt("identifierref")]);
870  }
871  if(!$il_sco_id)
872  {
873  continue;
874  }
875 
876  $c_timestamp="";
877  if ($data['Timestamp'])
878  {
879  $c_timestamp = $data['Timestamp'];
880  }
881  if ($data[$lng->txt("c_timestamp")])
882  {
883  $c_timestamp = $data[$lng->txt("c_timestamp")];
884  }
885  if ($c_timestamp == "")
886  {
887  $date = new DateTime();
888  $c_timestamp = $date->getTimestamp();
889  } else {
890  if($a_last_access[$user_id]) {
891  if ($a_last_access[$user_id] < $c_timestamp) $a_last_access[$user_id] = $c_timestamp;
892  } else {
893  $a_last_access[$user_id] = $c_timestamp;
894  }
895  }
896 
897  if(!$data['Key'])
898  {
899  continue;
900  }
901  if(!$data['Value'])
902  {
903  $data['Value'] = "";
904  }
905 
906  if($data['Key'] == "cmi.core.total_time" && $data['Value'] != "") {
907  $tarr = explode(":", $data['Value']);
908  $sec = (int) $tarr[2] + (int) $tarr[1] * 60 +
909  (int) substr($tarr[0], strlen($tarr[0]) - 3) * 60 * 60;
910  if ($a_time[$user_id]) $a_time[$user_id] += $sec;
911  else $a_time[$user_id] = $sec;
912  }
913  //do the actual import
914  if($il_sco_id > 0)
915  {
916  $statement = $ilDB->queryF('
917  SELECT * FROM scorm_tracking
918  WHERE user_id = %s
919  AND sco_id = %s
920  AND lvalue = %s
921  AND obj_id = %s',
922  array('integer', 'integer', 'text', 'integer'),
923  array($user_id, $il_sco_id, $data['Key'], $this->getID())
924  );
925  if($ilDB->numRows($statement) > 0)
926  {
927  $ilDB->update('scorm_tracking',
928  array(
929  'rvalue' => array('clob', $data['Value']),
930  'c_timestamp' => array('timestamp', $c_timestamp)
931  ),
932  array(
933  'user_id' => array('integer', $user_id),
934  'sco_id' => array('integer', $il_sco_id),
935  'lvalue' => array('text', $data['Key']),
936  'obj_id' => array('integer', $this->getId())
937  )
938  );
939  }
940  else
941  {
942  $ilDB->insert('scorm_tracking', array(
943  'obj_id' => array('integer', $this->getId()),
944  'user_id' => array('integer', $user_id),
945  'sco_id' => array('integer', $il_sco_id),
946  'lvalue' => array('text', $data['Key']),
947  'rvalue' => array('clob', $data['Value']),
948  'c_timestamp' => array('timestamp', $data['Timestamp'])
949  ));
950  }
951  }
952  // $package_attempts = 1;
953  if($il_sco_id == 0)
954  {
955  if ($data['Key'] == "package_attempts") $a_package_attempts[$user_id] = $data['Value'];
956  // if ($data['Key'] == "module_version") $a_module_version[$user_id] = $data['Value'];
957  }
958  if (!in_array($user_id,$users)) $users[] = $user_id;
959  }
960  fclose($fhandle);
961 
962  //UK determineStatus, percentage_completed and syncGlobalStatus
963  include_once './Services/Tracking/classes/class.ilLPStatusWrapper.php';
964  ilLPStatusWrapper::_refreshStatus($this->getId(),$users);
965 
966  // include_once './Services/Tracking/classes/status/class.ilLPStatusSCORM.php';
967  include_once './Services/Tracking/classes/class.ilLPStatus.php';
968  foreach ($users as $user_id){
969  $attempts = 1;
970  if ($a_package_attempts[$user_id]) $attempts = $a_package_attempts[$user_id];
971  // $module_version = 1;
972  // if ($a_module_version[$user_id]) $module_version = $a_module_version[$user_id];
973  $sco_total_time_sec = null;
974  if ($a_time[$user_id]) $sco_total_time_sec = $a_time[$user_id];
975  $last_access = null;
976  if ($a_last_access[$user_id]) $last_access = $a_last_access[$user_id];
977  // $status = ilLPStatusWrapper::_determineStatus($this->getId(),$user_id);
978  $status = ilLPStatus::_lookupStatus($this->getId(),$user_id);
979  // $percentage_completed = ilLPStatusSCORM::determinePercentage($this->getId(),$user_id);
980  $percentage_completed = ilLPStatus::_lookupPercentage($this->getId(),$user_id);
981 
982  $this->importSuccessForSahsUser($user_id, $last_access, $status, $attempts, $percentage_completed, $sco_total_time_sec);
983 
984  }
985 
986  return 0;
987  }
988 
989 
995  function decreaseAttemptsForUser($a_user_id) {
996  global $ilDB;
997 
998  foreach ($a_user_id as $user)
999  {
1000  //first check if there is a package_attempts entry
1001  $val_set = $ilDB->queryF('SELECT package_attempts FROM sahs_user WHERE user_id = %s AND obj_id = %s',
1002  array('integer','integer'),
1003  array($user,$this->getID()));
1004 
1005  $val_rec = $ilDB->fetchAssoc($val_set);
1006 
1007  if ($val_rec["package_attempts"] != null && $val_rec["package_attempts"] != 0)
1008  {
1009  $new_rec = 0;
1010  //decrease attempt by 1
1011  if ((int)$val_rec["package_attempts"] > 0) $new_rec = (int)$val_rec["package_attempts"]-1;
1012  $ilDB->manipulateF('UPDATE sahs_user SET package_attempts = %s WHERE user_id = %s AND obj_id = %s',
1013  array('integer','integer','integer'),
1014  array($new_rec,$user,$this->getID()));
1015 
1016  //following 2 lines were before 4.4 only for SCORM 1.2
1017  include_once("./Services/Tracking/classes/class.ilLPStatusWrapper.php");
1018  ilLPStatusWrapper::_updateStatus($this->getId(), $user);
1019  }
1020  }
1021  }
1022 
1023 
1024  //helper function
1025  function get_user_id($a_login) {
1026  global $ilDB, $ilUser;
1027 
1028  $val_set = $ilDB->queryF('SELECT * FROM usr_data WHERE(login=%s)',
1029  array('text'),array($a_login));
1030  $val_rec = $ilDB->fetchAssoc($val_set);
1031 
1032  if (count($val_rec)>0) {
1033  return $val_rec['usr_id'];
1034  } else {
1035  return null;
1036  }
1037  }
1038 
1039 
1043  private function lookupSCOId($a_referrer){
1044  global $ilDB, $ilUser;
1045 
1046  //non specific SCO entries
1047  if ($a_referrer=="0") {
1048  return 0;
1049  }
1050 
1051  $val_set = $ilDB->queryF('
1052  SELECT obj_id FROM sc_item,scorm_tree
1053  WHERE (obj_id = child
1054  AND identifierref = %s
1055  AND slm_id = %s)',
1056  array('text','integer'), array($a_referrer,$this->getID()));
1057  $val_rec = $ilDB->fetchAssoc($val_set);
1058 
1059  return $val_rec["obj_id"];
1060  }
1061 
1065  function getUserIdEmail($a_mail)
1066  {
1067  global $ilDB, $ilUser;
1068 
1069  $val_set = $ilDB->queryF('SELECT usr_id FROM usr_data WHERE(email=%s)',
1070  array('text'),array($a_mail));
1071  $val_rec = $ilDB->fetchAssoc($val_set);
1072 
1073 
1074  return $val_rec["usr_id"];
1075  }
1076 
1077 
1081  function sendExportFile($a_header,$a_content)
1082  {
1083  $timestamp = time();
1084  $refid = $this->getRefId();
1085  $filename = "scorm_tracking_".$refid."_".$timestamp.".csv";
1086  //Header
1087  header("Expires: 0");
1088  header("Cache-control: private");
1089  header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
1090  header("Content-Description: File Transfer");
1091  header("Content-Type: application/octet-stream");
1092  header("Content-disposition: attachment; filename=$filename");
1093  echo $a_header.$a_content;
1094  exit;
1095  }
1096 
1102  public static function _getAllScoIds($a_id)
1103  {
1104  global $ilDB;
1105 
1106  $scos = array();
1107 
1108  $val_set = $ilDB->queryF('
1109  SELECT scorm_object.obj_id,
1110  scorm_object.title,
1111  scorm_object.c_type,
1112  scorm_object.slm_id,
1113  scorm_object.obj_id scoid
1114  FROM scorm_object,sc_item,sc_resource
1115  WHERE(scorm_object.slm_id = %s
1116  AND scorm_object.obj_id = sc_item.obj_id
1117  AND sc_item.identifierref = sc_resource.import_id
1118  AND sc_resource.scormtype = %s)
1119  GROUP BY scorm_object.obj_id,
1120  scorm_object.title,
1121  scorm_object.c_type,
1122  scorm_object.slm_id,
1123  scorm_object.obj_id ',
1124  array('integer', 'text'),
1125  array($a_id,'sco'));
1126 
1127  while ($val_rec = $ilDB->fetchAssoc($val_set))
1128  {
1129  array_push($scos,$val_rec['scoid']);
1130  }
1131  return $scos;
1132  }
1133 
1142  public static function _getStatusForUser($a_id, $a_user,$a_allScoIds,$a_numerical=false)
1143  {
1144  global $ilDB, $lng;
1145 
1146  $scos = $a_allScoIds;
1147  //check if all SCO's are completed
1148  $scos_c = implode(',',$scos);
1149 
1150  $val_set = $ilDB->queryF('
1151  SELECT * FROM scorm_tracking
1152  WHERE (user_id = %s
1153  AND obj_id = %s
1154  AND '.$ilDB->in('sco_id', $scos, false, 'integer').'
1155  AND ((lvalue = %s AND '.$ilDB->like('rvalue', 'clob', 'completed').')
1156  OR (lvalue = %s AND '.$ilDB->like('rvalue', 'clob', 'passed').')))',
1157  array('integer','integer','text','text'),
1158  array($a_user,$a_id,'cmi.core.lesson_status', 'cmi.core.lesson_status'));
1159  while ($val_rec = $ilDB->fetchAssoc($val_set))
1160  {
1161  $key = array_search($val_rec['sco_id'], $scos);
1162  unset ($scos[$key]);
1163  }
1164  //check for completion
1165  if (count($scos) == 0) {
1166  $completion = ($a_numerical===true) ? true: $lng->txt("cont_complete");
1167  }
1168  if (count($scos) > 0) {
1169  $completion = ($a_numerical===true) ? false: $lng->txt("cont_incomplete");
1170  }
1171  return $completion;
1172  }
1173 
1180  public static function _getCourseCompletionForUser($a_id, $a_user)
1181  {
1183  }
1184 
1185  function getAllScoIds(){
1186  global $ilDB;
1187 
1188  $scos = array();
1189  //get all SCO's of this object
1190  $val_set = $ilDB->queryF('
1191  SELECT scorm_object.obj_id,
1192  scorm_object.title,
1193  scorm_object.c_type,
1194  scorm_object.slm_id,
1195  scorm_object.obj_id scoid
1196  FROM scorm_object, sc_item,sc_resource
1197  WHERE(scorm_object.slm_id = %s
1198  AND scorm_object.obj_id = sc_item.obj_id
1199  AND sc_item.identifierref = sc_resource.import_id
1200  AND sc_resource.scormtype = %s )
1201  GROUP BY scorm_object.obj_id,
1202  scorm_object.title,
1203  scorm_object.c_type,
1204  scorm_object.slm_id,
1205  scorm_object.obj_id',
1206  array('integer','text'),
1207  array($this->getId(),'sco'));
1208 
1209  while ($val_rec = $ilDB->fetchAssoc($val_set))
1210  {
1211  array_push($scos,$val_rec['scoid']);
1212  }
1213  return $scos;
1214  }
1215 
1216  function getStatusForUser($a_user,$a_allScoIds,$a_numerical=false){
1217  global $ilDB;
1218  $scos = $a_allScoIds;
1219  //loook up status
1220  //check if all SCO's are completed
1221  $scos_c = implode(',',$scos);
1222 
1223  $val_set = $ilDB->queryF('
1224  SELECT sco_id FROM scorm_tracking
1225  WHERE (user_id = %s
1226  AND obj_id = %s
1227  AND '.$ilDB->in('sco_id', $scos, false, 'integer').'
1228  AND ((lvalue = %s AND '.$ilDB->like('rvalue', 'clob', 'completed').') OR (lvalue = %s AND '.$ilDB->like('rvalue', 'clob', 'passed').') ) )',
1229  array('integer','integer','text','text',),
1230  array($a_user,$this->getID(),'cmi.core.lesson_status','cmi.core.lesson_status'));
1231  while ($val_rec = $ilDB->fetchAssoc($val_set))
1232  {
1233  $key = array_search($val_rec['sco_id'], $scos);
1234  unset ($scos[$key]);
1235  }
1236  //check for completion
1237  if (count($scos) == 0) {
1238  $completion = ($a_numerical===true) ? true: $this->lng->txt("cont_complete");
1239  }
1240  if (count($scos) > 0) {
1241  $completion = ($a_numerical===true) ? false: $this->lng->txt("cont_incomplete");
1242  }
1243  return $completion;
1244  }
1245 
1246  function getCourseCompletionForUser($a_user) {
1247  return $this->getStatusForUser($a_user,$this->getAllScoIds,true);
1248  }
1249 
1250  //to be called from IlObjUser
1251  public static function _removeTrackingDataForUser($user_id) {
1252  global $ilDB;
1253  //gobjective
1254  $ilDB->manipulateF(
1255  'DELETE FROM scorm_tracking WHERE user_id = %s',
1256  array('integer'),
1257  array($user_id)
1258  );
1259  $ilDB->manipulateF(
1260  'DELETE FROM sahs_user WHERE user_id = %s',
1261  array('integer'),
1262  array($user_id)
1263  );
1264  }
1265 
1266  static function _getScoresForUser($a_item_id, $a_user_id)
1267  {
1268  global $ilDB;
1269 
1270  $retAr = array("raw" => null, "max" => null, "scaled" => null);
1271  $val_set = $ilDB->queryF("
1272  SELECT lvalue, rvalue FROM scorm_tracking
1273  WHERE sco_id = %s
1274  AND user_id = %s
1275  AND (lvalue = 'cmi.core.score.raw' OR lvalue = 'cmi.core.score.max')",
1276  array('integer', 'integer'),
1277  array($a_item_id, $a_user_id)
1278  );
1279  while ($val_rec = $ilDB->fetchAssoc($val_set))
1280  {
1281  if ($val_rec['lvalue'] == "cmi.core.score.raw") $retAr["raw"] = $val_rec["rvalue"];
1282  if ($val_rec['lvalue'] == "cmi.core.score.max") $retAr["max"] = $val_rec["rvalue"];
1283  }
1284  if ($retAr["raw"] != null && $retAr["max"] != null) $retAr["scaled"] = ($retAr["raw"] / $retAr["max"]);
1285 
1286  return $retAr;
1287  }
1288 
1289 
1290  public function getLastVisited($user_id)
1291  {
1292  global $ilDB;
1293  $val_set = $ilDB->queryF('SELECT last_visited FROM sahs_user WHERE obj_id = %s AND user_id = %s',
1294  array('integer','integer'),
1295  array($this->getID(),$user_id)
1296  );
1297  while ($val_rec = $ilDB->fetchAssoc($val_set))
1298  {
1299  if ($val_rec["last_visited"] != null) return "".$val_rec["last_visited"];
1300  }
1301  return '0';
1302  }
1303 
1304  function deleteTrackingDataOfUsers($a_users)
1305  {
1306  global $ilDB;
1307  include_once("./Services/Tracking/classes/class.ilChangeEvent.php");
1308  include_once("./Services/Tracking/classes/class.ilLPStatusWrapper.php");
1309 
1310  ilChangeEvent::_deleteReadEventsForUsers($this->getId(), $a_users);
1311 
1312  foreach($a_users as $user)
1313  {
1314  $ilDB->manipulateF('
1315  DELETE FROM scorm_tracking
1316  WHERE user_id = %s
1317  AND obj_id = %s',
1318  array('integer', 'integer'),
1319  array($user, $this->getID()));
1320 
1321  $ilDB->manipulateF('
1322  DELETE FROM sahs_user
1323  WHERE user_id = %s
1324  AND obj_id = %s',
1325  array('integer', 'integer'),
1326  array($user, $this->getID()));
1327 
1328  ilLPStatusWrapper::_updateStatus($this->getId(), $user);
1329  }
1330  }
1331 
1332 }
1333 ?>
const LP_STATUS_COMPLETED_NUM
static _getAllScoIds($a_id)
Get an array of id&#39;s for all Sco&#39;s in the module.
global $ilErr
Definition: raiseError.php:16
$error
Definition: Error.php:17
ILIAS Setting Class.
importRaw($a_file)
Import raw data ilDB $ilDB ilObjUser $ilUser.
parseUserId($il_id)
Parse il_usr_123_6 id.
getAttemptsForUsers()
Get attempts for all users ilDB $ilDB.
$result
static _getCourseCompletionForUser($a_id, $a_user)
Get the completion of a SCORM module for a given user.
$_GET["client_id"]
const LP_STATUS_NOT_ATTEMPTED
static _updateStatus($a_obj_id, $a_usr_id, $a_obj=null, $a_percentage=false, $a_force_raise=false)
Update status.
getTrackedItems()
get all tracked items of current user
static _lookupPercentage($a_obj_id, $a_user_id)
Lookup percentage.
sendExportFile($a_header, $a_content)
send export file to browser
const LP_STATUS_IN_PROGRESS_NUM
decreaseAttemptsForUser($a_user_id)
Decrease attempts for user ilDB $ilDB.
getUserIdEmail($a_mail)
assumes that only one account exists for a mailadress
getAttemptsForUser($a_user_id)
get number of atttempts for a certain user and package
__construct($a_id=0, $a_call_by_reference=true)
Constructor public.
static _refreshStatus($a_obj_id, $a_users=null)
Set dirty.
static now()
Return current timestamp in Y-m-d H:i:s format.
static _lookupLastAccess($a_obj_id, $a_usr_id)
Return the last access timestamp for a given user.
const LP_STATUS_IN_PROGRESS
getTrackingDataPerUser($a_sco_id, $a_user_id)
Get tracking data per user ilDB $ilDB.
validate($directory)
Validate all XML-Files in a SCOM-Directory.
getDataDirectory($mode="filesystem")
get data directory of lm
static _enabledLearningProgress()
check wether learing progress is enabled or not
const LP_STATUS_FAILED
getId()
get object id public
$a_content
Definition: workflow.php:94
$header
getTitle()
get object title public
SCORM Item.
$ilUser
Definition: imgupload.php:18
redirection script todo: (a better solution should control the processing via a xml file) ...
Reload workbook from saved file
static _recordReadEvent($a_type, $a_ref_id, $obj_id, $usr_id, $isCatchupWriteEvents=true, $a_ext_rc=false, $a_ext_time=false)
Records a read event and catches up with write events.
Add a drawing to the header
Definition: 04printing.php:69
getModuleVersionForUser($a_user_id)
get module version that tracking data for a user was recorded on
$n
Definition: RandomTest.php:80
Validation of SCORM-XML Files.
SCORM Object Tree.
Create styles array
The data for the language used.
importSuccessForSahsUser($user_id, $last_access, $status, $attempts=null, $percentage_completed=null, $sco_total_time_sec=null)
exportSelected($a_all, $a_users=array())
Export selected user tracking data ilDB $ilDB ilObjUser $ilUser.
getModuleVersionForUsers()
Get module version for users.
lookupSCOId($a_referrer)
resolves manifest SCOID to internal ILIAS SCO ID
static _getTrackingItems($a_obj_id)
get all tracking items of scorm object static
getStatusForUser($a_user, $a_allScoIds, $a_numerical=false)
foreach($mandatory_scripts as $file) $timestamp
Definition: buildRTE.php:81
const LP_STATUS_NOT_ATTEMPTED_NUM
static __readStatus($a_obj_id, $user_id)
static _getStatusForUser($a_id, $a_user, $a_allScoIds, $a_numerical=false)
Get the status of a SCORM module for a given user.
$lm_set
global $ilSetting
Definition: privfeed.php:17
setLearningProgressSettingsAtUpload()
set settings for learning progress determination per default at upload
static _deleteReadEventsForUsers($a_obj_id, array $a_user_ids)
global $ilDB
readObject()
read manifest file public
getRefId()
get reference id public
static _getInstance()
Get instance of ilPrivacySettings.
Add data(end) time
Method that wraps PHPs time in order to allow simulations with the workflow.
static _getScoresForUser($a_item_id, $a_user_id)
static userDataArrayForExport($user, $b_allowExportPrivacy=false)
static getInstance($a_obj_id)
Class ilObjSCORMLearningModule.
$_POST["username"]
static _lookupStatus($a_obj_id, $a_user_id, $a_create=true)
Lookup status.
Class ilObjSCORMLearningModule.
const LP_STATUS_FAILED_NUM