ILIAS  release_4-3 Revision
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.ilObjSCORMTracking.php
Go to the documentation of this file.
1 <?php
2 
3 /* Copyright (c) 1998-2011 ILIAS open source, Extended GPL, see docs/LICENSE */
4 
13 {
18  function ilObjSCORMTracking()
19  {
20  global $ilias;
21 
22  }
23 
24  function extractData()
25  {
26  $this->insert = array();
27  if (is_array($_GET["iL"]))
28  {
29  foreach($_GET["iL"] as $key => $value)
30  {
31  $this->insert[] = array("left" => $value, "right" => $_GET["iR"][$key]);
32  }
33  }
34  if (is_array($_POST["iL"]))
35  {
36  foreach($_POST["iL"] as $key => $value)
37  {
38  $this->insert[] = array("left" => $value, "right" => $_POST["iR"][$key]);
39  }
40  }
41 
42  $this->update = array();
43  if (is_array($_GET["uL"]))
44  {
45  foreach($_GET["uL"] as $key => $value)
46  {
47  $this->update[] = array("left" => $value, "right" => $_GET["uR"][$key]);
48  }
49  }
50  if (is_array($_POST["uL"]))
51  {
52  foreach($_POST["uL"] as $key => $value)
53  {
54  $this->update[] = array("left" => $value, "right" => $_POST["uR"][$key]);
55  }
56  }
57  }
58 
59  function store($obj_id=0, $sahs_id=0, $extractData=1)
60  {
61  global $ilDB, $ilUser;
62 
63  $ref_id = $_GET["ref_id"];
64  if (empty($obj_id))
65  {
66  $obj_id = ilObject::_lookupObjId($_GET["ref_id"]);
67  }
68 
69  // writing to scorm test log
70  $f = fopen("./Modules/ScormAicc/log/scorm.log", "a");
71  fwrite($f, "\nCALLING SCORM store()\n");
72  fwrite($f,'POST: '.print_r($_POST,true));
73 
74 
75  if (empty($sahs_id))
76  $sahs_id = ($_GET["sahs_id"] != "") ? $_GET["sahs_id"] : $_POST["sahs_id"];
77 
78  if ($extractData==1)
79  $this->extractData();
80 
81  if (is_object($ilUser))
82  {
83  $user_id = $ilUser->getId();
84  }
85 
86 
87 
88  if ($obj_id <= 1)
89  {
90  fwrite($f, "Error: No obj_id given.\n");
91  }
92  else
93  {
94  foreach($this->insert as $insert)
95  {
96  $set = $ilDB->queryF('
97  SELECT * FROM scorm_tracking
98  WHERE user_id = %s
99  AND sco_id = %s
100  AND lvalue = %s
101  AND obj_id = %s',
102  array('integer','integer','text','integer'),
103  array($user_id,$sahs_id,$insert["left"],$obj_id));
104  if ($rec = $ilDB->fetchAssoc($set))
105  {
106  fwrite($f, "Error Insert, left value already exists. L:".$insert["left"].",R:".
107  $insert["right"].",sahs_id:".$sahs_id.",user_id:".$user_id."\n");
108  }
109  else
110  {
111  $ilDB->insert('scorm_tracking', array(
112  'obj_id' => array('integer', $obj_id),
113  'user_id' => array('integer', $user_id),
114  'sco_id' => array('integer', $sahs_id),
115  'lvalue' => array('text', $insert["left"]),
116  'rvalue' => array('clob', $insert["right"]),
117  'c_timestamp' => array('timestamp', ilUtil::now())
118  ));
119 
120  fwrite($f, "Insert - L:".$insert["left"].",R:".
121  $insert["right"].",sahs_id:".$sahs_id.",user_id:".$user_id."\n");
122  }
123  }
124  foreach($this->update as $update)
125  {
126  $set = $ilDB->queryF('
127  SELECT * FROM scorm_tracking
128  WHERE user_id = %s
129  AND sco_id = %s
130  AND lvalue = %s
131  AND obj_id = %s',
132  array('integer','integer','text','integer'),
133  array($user_id,$sahs_id,$update["left"],$obj_id));
134 
135  if ($rec = $ilDB->fetchAssoc($set))
136  {
137  $ilDB->update('scorm_tracking',
138  array(
139  'rvalue' => array('clob', $update["right"]),
140  'c_timestamp' => array('timestamp', ilUtil::now())
141  ),
142  array(
143  'user_id' => array('integer', $user_id),
144  'sco_id' => array('integer', $sahs_id),
145  'lvalue' => array('text', $update["left"]),
146  'obj_id' => array('integer', $obj_id)
147  )
148  );
149  }
150  else
151  {
152  fwrite($f, "ERROR Update, left value does not exist. L:".$update["left"].",R:".
153  $update["right"].",sahs_id:".$sahs_id.",user_id:".$user_id."\n");
154  }
155 
156  }
157  }
158  fclose($f);
159 
160  // update status
161  include_once("./Services/Tracking/classes/class.ilLPStatusWrapper.php");
162  ilLPStatusWrapper::_updateStatus($obj_id, $user_id);
163 
164  // update time and numbers of attempts in change event
165  //NOTE: is possibly not correct (it is count of commit with changed values); be careful to performance issues
166  ilObjSCORMTracking::_syncReadEvent($obj_id, $user_id, "sahs", $ref_id);
167  }
168 
169  function storeJsApi($obj_id=0) {
170  global $ilLog, $ilDB, $ilUser;
171 
172  $b_updateStatus=false;
173 
174  $b_messageLog=false;
175  if ($ilLog->current_log_level == 30)
176  $b_messageLog=true;
177 
178  $ref_id = $_GET["ref_id"];
179 
180  if (empty($obj_id))
181  $obj_id = ilObject::_lookupObjId($_GET["ref_id"]);
182 
183  if ($b_messageLog)
184  $ilLog->write("ScormAicc: CALLING SCORM storeJsApi() ".$_POST);
185 
186  if (is_object($ilUser))
187  $user_id = $ilUser->getId();
188 
189  $aa_data = array();
190  if (is_array($_POST["S"])) {
191  foreach($_POST["S"] as $key => $value) {
192  $aa_data[] = array("sco_id" => $value, "left" => $_POST["L"][$key], "right" => $_POST["R"][$key]);
193  }
194  }
195 
196  if ($obj_id <= 1) {
197  $ilLog->write("ScormAicc: storeJsApi: Error: No valid obj_id given.");
198  }
199  else {
200  foreach($aa_data as $a_data) {
201  $set = $ilDB->queryF('
202  SELECT rvalue FROM scorm_tracking
203  WHERE user_id = %s
204  AND sco_id = %s
205  AND lvalue = %s
206  AND obj_id = %s',
207  array('integer','integer','text','integer'),
208  array($user_id,$a_data["sco_id"],$a_data["left"],$obj_id));
209  if ($rec = $ilDB->fetchAssoc($set)) {
210  if ($a_data["left"] == 'cmi.core.lesson_status' && $a_data["right"] != $rec["rvalue"]) {
211  $b_updateStatus = true;
212  }
213  $ilDB->update('scorm_tracking',
214  array(
215  'rvalue' => array('clob', $a_data["right"]),
216  'c_timestamp' => array('timestamp', ilUtil::now())
217  ),
218  array(
219  'user_id' => array('integer', $user_id),
220  'sco_id' => array('integer', $a_data["sco_id"]),
221  'lvalue' => array('text', $a_data["left"]),
222  'obj_id' => array('integer', $obj_id)
223  )
224  );
225  if ($b_messageLog) {
226  $ilLog->write("ScormAicc: storeJsApi Updated - L:".$a_data["left"].",R:".
227  $a_data["right"]." for obj_id:".$obj_id.",sco_id:".$a_data["sco_id"].",user_id:".$user_id);
228  }
229  }
230  else {
231  if ($a_data["left"] == 'cmi.core.lesson_status') {
232  $b_updateStatus = true;
233  }
234  $ilDB->insert('scorm_tracking', array(
235  'obj_id' => array('integer', $obj_id),
236  'user_id' => array('integer', $user_id),
237  'sco_id' => array('integer', $a_data["sco_id"]),
238  'lvalue' => array('text', $a_data["left"]),
239  'rvalue' => array('clob', $a_data["right"]),
240  'c_timestamp' => array('timestamp', ilUtil::now())
241  ));
242  if ($b_messageLog) {
243  $ilLog->write("ScormAicc: storeJsApi Inserted - L:".$a_data["left"].",R:".
244  $a_data["right"]." for obj_id:".$obj_id.",sco_id:".$$a_data["sco_id"].",user_id:".$user_id);
245  }
246  }
247  }
248  }
249 
250  // update status
251  if ($b_updateStatus == true) {
252  include_once("./Services/Tracking/classes/class.ilLPStatusWrapper.php");
253  ilLPStatusWrapper::_updateStatus($obj_id, $user_id);
254  }
255 
256  header('Content-Type: text/plain; charset=UTF-8');
257  print("ok");
258  }
259 
266  function _syncReadEvent($a_obj_id, $a_user_id, $a_type, $a_ref_id)
267  {
268  global $ilDB, $ilLog;
269 
270  // get attempts
271  $val_set = $ilDB->queryF('
272  SELECT rvalue FROM scorm_tracking
273  WHERE user_id = %s
274  AND sco_id = %s
275  AND lvalue = %s
276  AND obj_id = %s',
277  array('integer','integer','text','integer'),
278  array($a_user_id,0,'package_attempts',$a_obj_id));
279 
280  $val_rec = $ilDB->fetchAssoc($val_set);
281 
282  $val_rec["rvalue"] = str_replace("\r\n", "\n", $val_rec["rvalue"]);
283  if ($val_rec["rvalue"] == null) {
284  $val_rec["rvalue"]="";
285  }
286  $attempts = $val_rec["rvalue"];
287 
288  // get learning time
289  $sco_set = $ilDB->queryF('
290  SELECT sco_id, rvalue FROM scorm_tracking
291  WHERE obj_id = %s
292  AND user_id = %s
293  AND lvalue = %s
294  AND sco_id <> %s',
295  array('integer','integer','text','integer'),
296  array($a_obj_id,$a_user_id, 'cmi.core.total_time',0));
297 
298  $time = 0;
299  while($sco_rec = $ilDB->fetchAssoc($sco_set))
300  {
301  $tarr = explode(":", $sco_rec["rvalue"]);
302  $sec = (int) $tarr[2] + (int) $tarr[1] * 60 +
303  (int) substr($tarr[0], strlen($tarr[0]) - 3) * 60 * 60;
304  $time += $sec;
305  }
306 
307  include_once("./Services/Tracking/classes/class.ilChangeEvent.php");
308  ilChangeEvent::_recordReadEvent($a_type, $a_ref_id, $a_obj_id, $a_user_id, false, $attempts, $time);
309  }
310 
311  function _insertTrackData($a_sahs_id, $a_lval, $a_rval, $a_obj_id)
312  {
313  global $ilDB, $ilUser;
314 
315  $ilDB->insert('scorm_tracking', array(
316  'obj_id' => array('integer', $a_obj_id),
317  'user_id' => array('integer', $ilUser->getId()),
318  'sco_id' => array('integer', $a_sahs_id),
319  'lvalue' => array('text', $a_lval),
320  'rvalue' => array('clob', $a_rval),
321  'c_timestamp' => array('timestamp', ilUtil::now())
322  ));
323 
324  if ($a_lval == "cmi.core.lesson_status")
325  {
326  include_once("./Services/Tracking/classes/class.ilLPStatusWrapper.php");
327  ilLPStatusWrapper::_updateStatus($a_obj_id, $ilUser->getId());
328  }
329  }
330 
331 
338  public static function _getInProgress($scorm_item_id,$a_obj_id,$a_blocked_user_ids = null)
339  {
340  global $ilDB;
341 
342  if(is_array($scorm_item_id))
343  {
344  $in = $ilDB->in('sco_id', $scorm_item_id, false, 'integer');
345 
346  $res = $ilDB->queryF('SELECT user_id,sco_id FROM scorm_tracking
347  WHERE '.$in.'
348  AND obj_id = %s
349  GROUP BY user_id, sco_id',
350  array('integer'),array($a_obj_id));
351 
352  }
353  else
354  {
355  $res = $ilDB->queryF('SELECT user_id,sco_id FROM scorm_tracking
356  WHERE sco_id = %s
357  AND obj_id = %s',
358  array('integer','integer'),array($scorm_item_id,$a_obj_id)
359  );
360  }
361 
362  while($row = $ilDB->fetchObject($res))
363  {
364  // see _getProgressInfo()
365  if(!($a_blocked_user_ids && in_array($row->user_id, $a_blocked_user_ids)))
366  {
367  $in_progress[$row->sco_id][] = $row->user_id;
368  }
369  }
370  return is_array($in_progress) ? $in_progress : array();
371  }
372 
379  public static function _getCompleted($scorm_item_id,$a_obj_id)
380  {
381  global $ilDB;
382 
383  if(is_array($scorm_item_id))
384  {
385  $in = $ilDB->in('sco_id', $scorm_item_id, false, 'integer');
386 
387  $res = $ilDB->queryF('SELECT DISTINCT(user_id) FROM scorm_tracking
388  WHERE '.$in.'
389  AND obj_id = %s
390  AND lvalue = %s
391  AND ('.$ilDB->like('rvalue', 'clob', 'completed').' OR '.$ilDB->like('rvalue', 'clob', 'passed').')',
392  array('integer','text'),
393  array($a_obj_id,'cmi.core.lesson_status'));
394  }
395  else
396  {
397  $res = $ilDB->queryF('SELECT DISTINCT(user_id) FROM scorm_tracking
398  WHERE sco_id = %s
399  AND obj_id = %s
400  AND lvalue = %s
401  AND ('.$ilDB->like('rvalue', 'clob', 'completed').' OR '.$ilDB->like('rvalue', 'clob', 'passed').')',
402  array('integer','integer','text'),
403  array($scorm_item_id,$a_obj_id,'cmi.core.lesson_status'));
404  }
405 
406  while($row = $ilDB->fetchObject($res))
407  {
408  $user_ids[] = $row->user_id;
409  }
410  return $user_ids ? $user_ids : array();
411  }
412 
413  public static function _getCollectionStatus($a_scos, $a_obj_id, $a_user_id)
414  {
415  global $ilDB;
416 
417 
418  $status = "not_attempted";
419 
420  if (is_array($a_scos))
421  {
422  $in = $ilDB->in('sco_id', $a_scos, false, 'integer');
423 
424  $res = $ilDB->queryF('SELECT sco_id, rvalue FROM scorm_tracking
425  WHERE '.$in.'
426  AND obj_id = %s
427  AND lvalue = %s
428  AND user_id = %s',
429  array('integer','text', 'integer'),
430  array($a_obj_id,'cmi.core.lesson_status', $a_user_id));
431 
432  $cnt = 0;
433  $completed = true;
434  $failed = false;
435  while ($rec = $ilDB->fetchAssoc($res))
436  {
437  if ($rec["rvalue"] == "failed")
438  {
439  $failed = true;
440  }
441  if ($rec["rvalue"] != "completed" && $rec["rvalue"] != "passed")
442  {
443  $completed = false;
444  }
445  $cnt++;
446  }
447  if ($cnt > 0)
448  {
449  $status = "in_progress";
450  }
451  if ($completed && $cnt == count($a_scos))
452  {
453  $status = "completed";
454  }
455  if ($failed)
456  {
457  $status = "failed";
458  }
459 
460  // check max attempts
461  if ($status == "in_progress" && self::_hasMaxAttempts($a_obj_id, $a_user_id))
462  {
463  $status = "failed";
464  }
465  }
466  return $status;
467  }
468 
469  public static function _hasMaxAttempts($a_obj_id, $a_user_id)
470  {
471  global $ilDB;
472 
473  // see ilSCORMPresentationGUI
474  $val_set = $ilDB->queryF('SELECT * FROM sahs_lm WHERE id = %s',
475  array('integer'), array($a_obj_id));
476  $val_rec = $ilDB->fetchAssoc($val_set);
477  $max_attempts = $val_rec["max_attempt"];
478 
479  if ($max_attempts)
480  {
481  $val_set = $ilDB->queryF('
482  SELECT * FROM scorm_tracking
483  WHERE user_id = %s
484  AND sco_id = %s
485  AND lvalue= %s
486  AND obj_id = %s',
487  array('integer','integer','text','integer'),
488  array($a_user_id,0,'package_attempts',$a_obj_id)
489  );
490  $val_rec = $ilDB->fetchAssoc($val_set);
491 
492  $val_rec["rvalue"] = str_replace("\r\n", "\n", $val_rec["rvalue"]);
493  if ($val_rec["rvalue"] == null)
494  {
495  $val_rec["rvalue"] = 0;
496  }
497  $act_attempts = $val_rec["rvalue"];
498 
499  if ($act_attempts >= $max_attempts)
500  {
501  return true;
502  }
503  }
504 
505  return false;
506  }
507 
508  public static function _countCompleted($a_scos, $a_obj_id, $a_user_id)
509  {
510  global $ilDB;
511 
512  if (is_array($a_scos))
513  {
514  $in = $ilDB->in('sco_id', $a_scos, false, 'integer');
515 
516  $res = $ilDB->queryF('SELECT sco_id, rvalue FROM scorm_tracking
517  WHERE '.$in.'
518  AND obj_id = %s
519  AND lvalue = %s
520  AND user_id = %s',
521  array('integer','text', 'integer'),
522  array($a_obj_id,'cmi.core.lesson_status', $a_user_id));
523 
524  $cnt = 0;
525  while ($rec = $ilDB->fetchAssoc($res))
526  {
527  if ($rec["rvalue"] == "completed" || $rec["rvalue"] == "passed")
528  {
529  $cnt++;
530  }
531  }
532  }
533  return $cnt;
534  }
535 
542  public static function lookupLastAccessTimes($a_obj_id)
543  {
544  global $ilDB;
545 
546  $query = 'SELECT user_id, MAX(c_timestamp) tst '.
547  'FROM scorm_tracking '.
548  'WHERE obj_id = '.$ilDB->quote($a_obj_id,'integer').' '.
549  'GROUP BY user_id';
550  $res = $ilDB->query($query);
551 
552  $users = array();
553  while($row = $res->fetchRow(DB_FETCHMODE_OBJECT))
554  {
555  $users[$row->user_id] = $row->tst;
556  }
557  return $users;
558  }
559 
560 
566  function _getTrackedUsers($a_obj_id)
567  {
568  global $ilDB, $ilLog;
569 
570  $res = $ilDB->queryF('SELECT DISTINCT user_id FROM scorm_tracking
571  WHERE obj_id = %s
572  AND lvalue = %s',
573  array('integer','text'),
574  array($a_obj_id,'cmi.core.lesson_status'));
575 
576  $users = array();
577  while ($row = $ilDB->fetchAssoc($res))
578  {
579  $users[] = $row["user_id"];
580  }
581  return $users;
582  }
583 
590  function _getFailed($scorm_item_id,$a_obj_id)
591  {
592  global $ilDB;
593 
594  if(is_array($scorm_item_id))
595  {
596  $in = $ilDB->in('sco_id', $scorm_item_id, false, 'integer');
597 
598  $res = $ilDB->queryF('
599  SELECT DISTINCT(user_id) FROM scorm_tracking
600  WHERE '.$in.'
601  AND obj_id = %s
602  AND lvalue = %s
603  AND '.$ilDB->like('rvalue', 'clob', 'failed').' ',
604  array('integer','text'),
605  array($a_obj_id,'cmi.core.lesson_status'));
606  }
607  else
608  {
609 
610  $res = $ilDB->queryF('
611  SELECT DISTINCT(user_id) FROM scorm_tracking
612  WHERE sco_id = %s
613  AND obj_id = %s
614  AND lvalue = %s
615  AND '.$ilDB->like('rvalue', 'clob', 'failed').' ',
616  array('integer','integer','text'),
617  array($scorm_item_id,$a_obj_id,'cmi.core.lesson_status'));
618  }
619 
620  while($row = $ilDB->fetchObject($res))
621  {
622  $user_ids[] = $row->user_id;
623  }
624  return $user_ids ? $user_ids : array();
625  }
626 
633  public static function _getCountCompletedPerUser($a_scorm_item_ids,$a_obj_id)
634  {
635  global $ilDB;
636 
637  $in = $ilDB->in('sco_id', $a_scorm_item_ids, false, 'integer');
638 
639  // Why does this query use a like search against "passed" and "failed"
640  //because it's clob and we support Oracle
641  $res = $ilDB->queryF('
642  SELECT user_id, COUNT(user_id) completed FROM scorm_tracking
643  WHERE '.$in.'
644  AND obj_id = %s
645  AND lvalue = %s
646  AND ('.$ilDB->like('rvalue', 'clob', 'completed').' OR '.$ilDB->like('rvalue', 'clob', 'passed').')
647  GROUP BY user_id',
648  array('integer', 'text'),
649  array($a_obj_id, 'cmi.core.lesson_status')
650  );
651  while($row = $ilDB->fetchObject($res))
652  {
653  $users[$row->user_id] = $row->completed;
654  }
655  /*
656  // Avoid searches against field rvalue.
657  // This gives the possibility to reuse the obj_id,sco_id,lvalue index.
658  $query = "SELECT user_id,rvalue FROM scorm_tracking ".
659  "WHERE ".$in." ".
660  "AND obj_id = ".$ilDB->quote($a_obj_id,'integer')." ".
661  "AND lvalue = ".$ilDB->quote('cmi.core.lesson_status','text');
662 
663  $res = $ilDB->query($query);
664  while($row = $ilDB->fetchObject($res))
665  {
666  if($row->rvalue == 'passed' or $row->rvalue == 'completed')
667  {
668  ++$users[$row->user_id];
669  }
670  }*/
671  return $users ? $users : array();
672  }
673 
680  public static function _getProgressInfo($sco_item_ids,$a_obj_id)
681  {
682  global $ilDB;
683 
684  $in = $ilDB->in('sco_id', $sco_item_ids, false, 'integer');
685 
686  $res = $ilDB->queryF('
687  SELECT * FROM scorm_tracking
688  WHERE '.$in.'
689  AND obj_id = %s
690  AND lvalue = %s ',
691  array('integer','text'),
692  array($a_obj_id,'cmi.core.lesson_status'));
693 
694  $info['completed'] = array();
695  $info['failed'] = array();
696 
697  $user_ids = array();
698  while($row = $ilDB->fetchObject($res))
699  {
700  switch($row->rvalue)
701  {
702  case 'completed':
703  case 'passed':
704  $info['completed'][$row->sco_id][] = $row->user_id;
705  $user_ids[] = $row->user_id;
706  break;
707 
708  case 'failed':
709  $info['failed'][$row->sco_id][] = $row->user_id;
710  $user_ids[] = $row->user_id;
711  break;
712  }
713  }
714  $info['in_progress'] = ilObjSCORMTracking::_getInProgress($sco_item_ids,$a_obj_id,$user_ids);
715 
716  return $info;
717  }
718 
720  {
721  global $ilUser, $ilDB;
722 
723  $user_id = $ilUser->getID();
724  $ref_id = $_GET["ref_id"];
725  $obj_id = ilObject::_lookupObjId($ref_id);
726  if ($obj_id <= 1){
727  $GLOBALS['ilLog']->write(__METHOD__.' no valid obj_id');
728  } else {
729  $data=$_POST['last_visited'];
730 // $GLOBALS['ilLog']->write(__METHOD__.' last_visited: '.$data);
731  if($data) {
732  $set = $ilDB->queryF('
733  SELECT rvalue FROM scorm_tracking
734  WHERE user_id = %s
735  AND sco_id = %s
736  AND lvalue = %s
737  AND obj_id = %s',
738  array('integer','integer','text','integer'),
739  array($user_id,0,'last_visited',$obj_id));
740  if ($rec = $ilDB->fetchAssoc($set)) {
741  $ilDB->update('scorm_tracking',
742  array(
743  'rvalue' => array('clob', $data),
744  'c_timestamp' => array('timestamp', ilUtil::now())
745  ),
746  array(
747  'user_id' => array('integer', $user_id),
748  'sco_id' => array('integer', 0),
749  'lvalue' => array('text', 'last_visited'),
750  'obj_id' => array('integer', $obj_id)
751  )
752  );
753  }
754  else {
755  $ilDB->insert('scorm_tracking', array(
756  'obj_id' => array('integer', $obj_id),
757  'user_id' => array('integer', $user_id),
758  'sco_id' => array('integer', 0),
759  'lvalue' => array('text', 'last_visited'),
760  'rvalue' => array('clob', $data),
761  'c_timestamp' => array('timestamp', ilUtil::now())
762  ));
763  }
764  }
765  // update time and numbers of attempts in change event
766  //NOTE: here it is correct (not count of commit with changed values); be careful to performance issues
767  ilObjSCORMTracking::_syncReadEvent($obj_id, $user_id, "sahs", $ref_id);
768  }
769  header('Content-Type: text/plain; charset=UTF-8');
770  print("");
771  }
772 
773 
774 } // END class.ilObjSCORMTracking
775 ?>