ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
class.ilLMTracker.php
Go to the documentation of this file.
1 <?php
2 
3 /* Copyright (c) 1998-2014 ILIAS open source, Extended GPL, see docs/LICENSE */
4 
13 {
14  const NOT_ATTEMPTED = 0;
15  const IN_PROGRESS = 1;
16  const COMPLETED = 2;
17  const FAILED = 3;
18  const CURRENT = 99;
19 
20  protected $lm_ref_id;
21  protected $lm_obj_id;
22  protected $lm_tree;
23  protected $lm_obj_ids = array();
24  protected $tree_arr = array(); // tree array
25  protected $re_arr = array(); // read event data array
26  protected $loaded_for_node = false; // current node for that the tracking data has been loaded
27  protected $dirty = false;
28  protected $page_questions = array();
29  protected $all_questions = array();
30  protected $answer_status = array();
31  protected $has_incorrect_answers = false;
32  protected $current_page_id = 0;
33 
34  static $instances = array();
35  static $instancesbyobj = array();
36 
40 
46  private function __construct($a_id, $a_by_obj_id = false, $a_user_id)
47  {
48  $this->user_id = $a_user_id;
49 
50  if ($a_by_obj_id)
51  {
52  $this->lm_ref_id = 0;
53  $this->lm_obj_id = $a_id;
54  }
55  else
56  {
57  $this->lm_ref_id = $a_id;
58  $this->lm_obj_id = ilObject::_lookupObjId($a_id);
59  }
60 
61  include_once("./Modules/LearningModule/classes/class.ilLMTree.php");
62  $this->lm_tree = ilLMTree::getInstance($this->lm_obj_id);
63  }
64 
71  static function getInstance($a_ref_id, $a_user_id = 0)
72  {
73  global $ilUser;
74 
75  if ($a_user_id == 0)
76  {
77  $a_user_id = $ilUser->getId();
78  }
79 
80  if (!isset(self::$instances[$a_ref_id][$a_user_id]))
81  {
82  self::$instances[$a_ref_id][$a_user_id] = new ilLMTracker($a_ref_id, false, $a_user_id);
83  }
84  return self::$instances[$a_ref_id][$a_user_id];
85  }
86 
93  static function getInstanceByObjId($a_obj_id, $a_user_id = 0)
94  {
95  global $ilUser;
96 
97  if ($a_user_id == 0)
98  {
99  $a_user_id = $ilUser->getId();
100  }
101 
102  if (!isset(self::$instancesbyobj[$a_obj_id][$a_user_id]))
103  {
104  self::$instancesbyobj[$a_obj_id][$a_user_id] = new ilLMTracker($a_obj_id, true, $a_user_id);
105  }
106  return self::$instancesbyobj[$a_obj_id][$a_user_id];
107  }
108 
112 
118  function trackAccess($a_page_id)
119  {
120  if ($this->lm_ref_id == 0)
121  {
122  die("ilLMTracker: No Ref Id given.");
123  }
124 
125  // track page and chapter access
126  $this->trackPageAndChapterAccess($a_page_id);
127 
128  // track last page access (must be done after calling trackPageAndChapterAccess())
129  $this->trackLastPageAccess($this->user_id, $this->lm_ref_id, $a_page_id);
130 
131  // #9483
132  // general learning module lp tracking
133  include_once("./Services/Tracking/classes/class.ilLearningProgress.php");
134  ilLearningProgress::_tracProgress($this->user_id, $this->lm_obj_id,
135  $this->lm_ref_id, "lm");
136 
137  // obsolete?
138  include_once("./Services/Tracking/classes/class.ilLPStatusWrapper.php");
139  ilLPStatusWrapper::_updateStatus($this->lm_obj_id, $this->user_id);
140 
141  // mark currently loaded data as dirty to force reload if necessary
142  $this->dirty = true;
143  }
144 
152  function trackLastPageAccess($usr_id, $lm_id, $obj_id)
153  {
154  global $ilDB;
155 
156  // first check if an entry for this user and this lm already exist, when so, delete
157  $q = "DELETE FROM lo_access ".
158  "WHERE usr_id = ".$ilDB->quote((int) $usr_id, "integer")." ".
159  "AND lm_id = ".$ilDB->quote((int) $lm_id, "integer");
160  $ilDB->manipulate($q);
161 
162  $title = "";
163 
164  $q = "INSERT INTO lo_access ".
165  "(timestamp,usr_id,lm_id,obj_id,lm_title) ".
166  "VALUES ".
167  "(".$ilDB->now().",".
168  $ilDB->quote((int) $usr_id, "integer").",".
169  $ilDB->quote((int) $lm_id, "integer").",".
170  $ilDB->quote((int) $obj_id, "integer").",".
171  $ilDB->quote($title, "text").")";
172  $ilDB->manipulate($q);
173  }
174 
175 
179  protected function trackPageAndChapterAccess($a_page_id)
180  {
181  global $ilDB;
182 
183  $now = time();
184 
185  //
186  // 1. Page access: current page
187  //
188  $set = $ilDB->query("SELECT obj_id FROM lm_read_event".
189  " WHERE obj_id = ".$ilDB->quote($a_page_id, "integer").
190  " AND usr_id = ".$ilDB->quote($this->user_id, "integer"));
191  if (!$ilDB->fetchAssoc($set))
192  {
193  $fields = array(
194  "obj_id" => array("integer", $a_page_id),
195  "usr_id" => array("integer", $this->user_id)
196  );
197  // $ilDB->insert("lm_read_event", $fields);
198  $ilDB->replace("lm_read_event", $fields, array()); // #15144
199  }
200 
201  // update all parent chapters
202  $ilDB->manipulate("UPDATE lm_read_event SET".
203  " read_count = read_count + 1 ".
204  " , last_access = ".$ilDB->quote($now, "integer").
205  " WHERE obj_id = ".$ilDB->quote($a_page_id, "integer").
206  " AND usr_id = ".$ilDB->quote($this->user_id, "integer"));
207 
208 
209  //
210  // 2. Chapter access: based on last page accessed
211  //
212 
213  // get last accessed page
214  $set = $ilDB->query("SELECT * FROM lo_access WHERE ".
215  "usr_id = ".$ilDB->quote($this->user_id, "integer")." AND ".
216  "lm_id = ".$ilDB->quote($this->lm_ref_id, "integer"));
217  $res = $ilDB->fetchAssoc($set);
218  if($res["obj_id"])
219  {
220  include_once('Services/Tracking/classes/class.ilObjUserTracking.php');
221  $valid_timespan = ilObjUserTracking::_getValidTimeSpan();
222 
223  $pg_ts = new ilDateTime($res["timestamp"], IL_CAL_DATETIME);
224  $pg_ts = $pg_ts->get(IL_CAL_UNIX);
225  $pg_id = $res["obj_id"];
226  if(!$this->lm_tree->isInTree($pg_id))
227  {
228  return;
229  }
230 
231  $time_diff = $read_diff = 0;
232 
233  // spent_seconds or read_count ?
234  if (($now-$pg_ts) <= $valid_timespan)
235  {
236  $time_diff = $now-$pg_ts;
237  }
238  else
239  {
240  $read_diff = 1;
241  }
242 
243  // find parent chapter(s) for that page
244  $parent_st_ids = array();
245  foreach($this->lm_tree->getPathFull($pg_id) as $item)
246  {
247  if($item["type"] == "st")
248  {
249  $parent_st_ids[] = $item["obj_id"];
250  }
251  }
252 
253  if($parent_st_ids && ($time_diff || $read_diff))
254  {
255  // get existing chapter entries
256  $ex_st = array();
257  $set = $ilDB->query("SELECT obj_id FROM lm_read_event".
258  " WHERE ".$ilDB->in("obj_id", $parent_st_ids, "", "integer").
259  " AND usr_id = ".$ilDB->quote($this->user_id, "integer"));
260  while($row = $ilDB->fetchAssoc($set))
261  {
262  $ex_st[] = $row["obj_id"];
263  }
264 
265  // add missing chapter entries
266  $missing_st = array_diff($parent_st_ids, $ex_st);
267  if(sizeof($missing_st))
268  {
269  foreach($missing_st as $st_id)
270  {
271  $fields = array(
272  "obj_id" => array("integer", $st_id),
273  "usr_id" => array("integer", $this->user_id)
274  );
275  // $ilDB->insert("lm_read_event", $fields);
276  $ilDB->replace("lm_read_event", $fields, array()); // #15144
277  }
278  }
279 
280  // update all parent chapters
281  $ilDB->manipulate("UPDATE lm_read_event SET".
282  " read_count = read_count + ".$ilDB->quote($read_diff, "integer").
283  " , spent_seconds = spent_seconds + ".$ilDB->quote($time_diff, "integer").
284  " , last_access = ".$ilDB->quote($now, "integer").
285  " WHERE ".$ilDB->in("obj_id", $parent_st_ids, "", "integer").
286  " AND usr_id = ".$ilDB->quote($this->user_id, "integer"));
287  }
288  }
289  }
290 
291 
295 
301  public function setCurrentPage($a_val)
302  {
303  $this->current_page_id = $a_val;
304  }
305 
311  public function getCurrentPage()
312  {
313  return $this->current_page_id;
314  }
315 
322  protected function loadLMTrackingData()
323  {
324  global $ilDB;
325 
326  // we must prevent loading tracking data multiple times during a request where possible
327  // please note that the dirty flag works only to a certain limit
328  // e.g. if questions are answered the flag is not set (yet)
329  // or if pages/chapter are added/deleted the flag is not set
330  if ($this->loaded_for_node === (int) $this->getCurrentPage() && !$this->dirty)
331  {
332  return;
333  }
334 
335  $this->loaded_for_node = (int) $this->getCurrentPage();
336  $this->dirty = false;
337 
338  // load lm tree in array
339  $this->tree_arr = array();
340  $nodes = $this->lm_tree->getSubTree($this->lm_tree->getNodeData($this->lm_tree->readRootId()));
341  foreach ($nodes as $node)
342  {
343  $this->tree_arr["childs"][$node["parent"]][] = $node;
344  $this->tree_arr["parent"][$node["child"]] = $node["parent"];
345  $this->tree_arr["nodes"][$node["child"]] = $node;
346  }
347 
348  // load all lm obj ids of learning module
349  include_once("./Modules/LearningModule/classes/class.ilLMObject.php");
350  $this->lm_obj_ids = ilLMObject::_getAllLMObjectsOfLM($this->lm_obj_id);
351 
352  // load read event data
353  $this->re_arr = array();
354  $set = $ilDB->query("SELECT * FROM lm_read_event ".
355  " WHERE ".$ilDB->in("obj_id", $this->lm_obj_ids, false, "integer").
356  " AND usr_id = ".$ilDB->quote($this->user_id, "integer"));
357  while ($rec = $ilDB->fetchAssoc($set))
358  {
359  $this->re_arr[$rec["obj_id"]] = $rec;
360  }
361 
362  // load question/pages information
363  $this->page_questions = array();
364  $this->all_questions = array();
365  include_once("./Modules/LearningModule/classes/class.ilLMPageObject.php");
366  $q = ilLMPageObject::queryQuestionsOfLearningModule($this->lm_obj_id, "", "", 0, 0);
367  foreach ($q["set"] as $quest)
368  {
369  $this->page_questions[$quest["page_id"]][] = $quest["question_id"];
370  $this->all_questions[] = $quest["question_id"];
371  }
372 
373  // load question answer information
374  include_once("./Services/COPage/classes/class.ilPageQuestionProcessor.php");
375  $this->answer_status = ilPageQuestionProcessor::getAnswerStatus($this->all_questions, $this->user_id);
376 
377  $this->has_incorrect_answers = false;
378 
379  $has_pred_incorrect_answers = false;
380  $has_pred_incorrect_not_unlocked_answers = false;
381  $this->determineProgressStatus($this->lm_tree->readRootId(), $has_pred_incorrect_answers, $has_pred_incorrect_not_unlocked_answers);
382 
383  $this->has_incorrect_answers = $has_pred_incorrect_answers;
384  }
385 
392  {
393  $this->loadLMTrackingData();
394  if (count($this->all_questions) > 0 && !$this->has_incorrect_answers)
395  {
396  return true;
397  }
398  return false;
399  }
400 
401 
408  protected function determineProgressStatus($a_obj_id, &$a_has_pred_incorrect_answers, &$a_has_pred_incorrect_not_unlocked_answers)
409  {
410  $status = ilLMTracker::NOT_ATTEMPTED;
411 
412  if (isset($this->tree_arr["nodes"][$a_obj_id]))
413  {
414  $this->tree_arr["nodes"][$a_obj_id]["has_pred_incorrect_answers"] = $a_has_pred_incorrect_answers;
415  $this->tree_arr["nodes"][$a_obj_id]["has_pred_incorrect_not_unlocked_answers"] = $a_has_pred_incorrect_not_unlocked_answers;
416 
417  if (is_array($this->tree_arr["childs"][$a_obj_id]))
418  {
419  // sort childs in correct order
420  $this->tree_arr["childs"][$a_obj_id] = ilUtil::sortArray($this->tree_arr["childs"][$a_obj_id], "lft", "asc", true);
421 
422  $cnt_completed = 0;
423  foreach ($this->tree_arr["childs"][$a_obj_id] as $c)
424  {
425  // if child is not activated/displayed count child as implicitly completed
426  // rationale: everything that is visible for the learner determines the status
427  // see also bug #14642
428  if (!self::_isNodeVisible($c))
429  {
430  $cnt_completed++;
431  continue;
432  }
433  $c_stat = $this->determineProgressStatus($c["child"], $a_has_pred_incorrect_answers,
434  $a_has_pred_incorrect_not_unlocked_answers);
435  if ($status != ilLMTracker::FAILED)
436  {
437  if ($c_stat == ilLMTracker::FAILED)
438  {
439  $status = ilLMTracker::IN_PROGRESS;
440  }
441  else if ($c_stat == ilLMTracker::IN_PROGRESS)
442  {
443  $status = ilLMTracker::IN_PROGRESS;
444  }
445  else if ($c_stat == ilLMTracker::COMPLETED || $c_stat == ilLMTracker::CURRENT)
446  {
447  $status = ilLMTracker::IN_PROGRESS;
448  $cnt_completed++;
449  }
450  }
451  // if an item is failed or in progress or (not attempted and contains questions)
452  // the next item has predecessing incorrect answers
453  if ($this->tree_arr["nodes"][$c["child"]]["type"] == "pg")
454  {
455  if ($c_stat == ilLMTracker::FAILED || $c_stat == ilLMTracker::IN_PROGRESS ||
456  ($c_stat == ilLMTracker::NOT_ATTEMPTED && is_array($this->page_questions[$c["child"]]) && count($this->page_questions[$c["child"]]) > 0))
457  {
458  $a_has_pred_incorrect_answers = true;
459  if (!$this->tree_arr["nodes"][$c["child"]]["unlocked"])
460  {
461  $a_has_pred_incorrect_not_unlocked_answers = true;
462  }
463  }
464  }
465  }
466  if ($cnt_completed == count($this->tree_arr["childs"][$a_obj_id]))
467  {
468  $status = ilLMTracker::COMPLETED;
469  }
470  }
471  else if ($this->tree_arr["nodes"][$a_obj_id]["type"] == "pg")
472  {
473  // check read event data
474  if (isset($this->re_arr[$a_obj_id]) && $this->re_arr[$a_obj_id]["read_count"] > 0)
475  {
476  $status = ilLMTracker::COMPLETED;
477  }
478  else if ($a_obj_id == $this->getCurrentPage())
479  {
480  $status = ilLMTracker::CURRENT;
481  }
482 
483  $unlocked = false;
484  if (is_array($this->page_questions[$a_obj_id]))
485  {
486  // check questions, if one is failed -> failed
487  $unlocked = true;
488  foreach ($this->page_questions[$a_obj_id] as $q_id)
489  {
490  if (is_array($this->answer_status[$q_id])
491  && $this->answer_status[$q_id]["try"] > 0
492  && !$this->answer_status[$q_id]["passed"])
493  {
494  $status = ilLMTracker::FAILED;
495  if (!$this->answer_status[$q_id]["unlocked"])
496  {
497  $unlocked = false;
498  }
499  }
500  }
501 
502  // check questions, if one is not answered -> in progress
503  if ($status != ilLMTracker::FAILED)
504  {
505  foreach ($this->page_questions[$a_obj_id] as $q_id)
506  {
507  if (!is_array($this->answer_status[$q_id])
508  || $this->answer_status[$q_id]["try"] == 0)
509  {
510  if ($status != ilLMTracker::NOT_ATTEMPTED)
511  {
512  $status = ilLMTracker::IN_PROGRESS;
513  }
514  }
515  }
516  $unlocked = false;
517  }
518  }
519  $this->tree_arr["nodes"][$a_obj_id]["unlocked"] = $unlocked;
520  $this->tree_arr["nodes"][$a_obj_id]["has_pred_incorrect_answers"] = $a_has_pred_incorrect_answers;
521  $this->tree_arr["nodes"][$a_obj_id]["has_pred_incorrect_not_unlocked_answers"] = $a_has_pred_incorrect_not_unlocked_answers;
522  }
523  }
524  else // free pages (currently not called, since only walking through tree structure)
525  {
526 
527  }
528  $this->tree_arr["nodes"][$a_obj_id]["status"] = $status;
529 
530  return $status;
531  }
532 
533 
541  public function getIconForLMObject($a_node, $a_highlighted_node = 0)
542  {
543  $this->loadLMTrackingData();
544  if ($a_node["child"] == $a_highlighted_node)
545  {
546  return ilUtil::getImagePath('scorm/running.svg');
547  }
548  if (isset($this->tree_arr["nodes"][$a_node["child"]]))
549  {
550  switch ($this->tree_arr["nodes"][$a_node["child"]]["status"])
551  {
553  return ilUtil::getImagePath('scorm/incomplete.svg');
554 
555  case ilLMTracker::FAILED:
556  return ilUtil::getImagePath('scorm/failed.svg');
557 
559  return ilUtil::getImagePath('scorm/completed.svg');
560  }
561  }
562  return ilUtil::getImagePath('scorm/not_attempted.svg');
563  }
564 
571  function hasPredIncorrectAnswers($a_obj_id, $a_ignore_unlock = false)
572  {
573  $this->loadLMTrackingData();
574  $ret = false;
575  if (is_array($this->tree_arr["nodes"][$a_obj_id]))
576  {
577  if ($a_ignore_unlock)
578  {
579  $ret = $this->tree_arr["nodes"][$a_obj_id]["has_pred_incorrect_answers"];
580  }
581  else
582  {
583  $ret = $this->tree_arr["nodes"][$a_obj_id]["has_pred_incorrect_not_unlocked_answers"];
584  }
585  }
586  return $ret;
587  }
588 
592 
600  {
601  global $ilDB, $lng, $ilPluginAdmin, $ilUser;
602 
603  $blocked_users = array();
604 
605  // load question/pages information
606  $this->page_questions = array();
607  $this->all_questions = array();
608  $page_for_question = array();
609  include_once("./Modules/LearningModule/classes/class.ilLMPageObject.php");
610  $q = ilLMPageObject::queryQuestionsOfLearningModule($this->lm_obj_id, "", "", 0, 0);
611  foreach ($q["set"] as $quest)
612  {
613  $this->page_questions[$quest["page_id"]][] = $quest["question_id"];
614  $this->all_questions[] = $quest["question_id"];
615  $page_for_question[$quest["question_id"]] = $quest["page_id"];
616  }
617  // get question information
618  include_once("./Modules/TestQuestionPool/classes/class.ilAssQuestionList.php");
619  $qlist = new ilAssQuestionList($ilDB, $lng, $ilPluginAdmin);
620  $qlist->setParentObjId(0);
621  $qlist->setJoinObjectData(false);
622  $qlist->addFieldFilter("question_id", $this->all_questions);
623  $qlist->load();
624  $qdata = $qlist->getQuestionDataArray();
625 
626  // load question answer information
627  include_once("./Services/COPage/classes/class.ilPageQuestionProcessor.php");
628  $this->answer_status = ilPageQuestionProcessor::getAnswerStatus($this->all_questions);
629  include_once("./Modules/LearningModule/classes/class.ilLMPageObject.php");
630  foreach ($this->answer_status as $as)
631  {
632  if ($as["try"] >= $qdata[$as["qst_id"]]["nr_of_tries"] && $qdata[$as["qst_id"]]["nr_of_tries"] > 0 && !$as["passed"])
633  {
634  //var_dump($qdata[$as["qst_id"]]);
635  $name = ilObjUser::_lookupName($as["user_id"]);
636  $as["user_name"] = $name["lastname"].", ".$name["firstname"]." [".$name["login"]."]";
637  $as["question_text"] = $qdata[$as["qst_id"]]["question_text"];
638  $as["page_id"] = $page_for_question[$as["qst_id"]];
639  $as["page_title"] = ilLMPageObject::_lookupTitle($as["page_id"]);
640  $blocked_users[] = $as;
641  }
642  }
643 
644  return $blocked_users;
645  }
646 
653  static function _isNodeVisible($a_node)
654  {
655  include_once("./Services/COPage/classes/class.ilPageObject.php");
656 
657  if ($a_node["type"] != "pg")
658  {
659  return true;
660  }
661 
662  $lm_set = new ilSetting("lm");
663  $active = ilPageObject::_lookupActive($a_node["child"], "lm",
664  $lm_set->get("time_scheduled_page_activation"));
665 
666  if(!$active)
667  {
668  $act_data = ilPageObject::_lookupActivationData((int) $a_node["child"], "lm");
669  if ($act_data["show_activation_info"] &&
670  (ilUtil::now() < $act_data["activation_start"]))
671  {
672  return true;
673  }
674  else
675  {
676  return false;
677  }
678  }
679  else
680  {
681  return true;
682  }
683  }
684 
685 
686 }
687 
688 ?>
static $instancesbyobj
static _lookupName($a_user_id)
lookup user name
ILIAS Setting Class.
Track access to ILIAS learning modules.
const IL_CAL_DATETIME
hasPredIncorrectAnswers($a_obj_id, $a_ignore_unlock=false)
Has predecessing incorrect answers.
trackLastPageAccess($usr_id, $lm_id, $obj_id)
Track last accessed page for a learning module.
static _updateStatus($a_obj_id, $a_usr_id, $a_obj=null, $a_percentage=false, $a_force_raise=false)
Update status.
getBlockedUsersInformation()
Get blocked users information.
setCurrentPage($a_val)
Set current page.
static _getAllLMObjectsOfLM($a_lm_id, $a_type="")
Get all objects of learning module.
static _tracProgress($a_user_id, $a_obj_id, $a_ref_id, $a_obj_type='')
static queryQuestionsOfLearningModule($a_lm_id, $a_order_field, $a_order_dir, $a_offset, $a_limit)
Get questions of learning module.
getIconForLMObject($a_node, $a_highlighted_node=0)
Get icon for lm object.
const IL_CAL_UNIX
static _lookupTitle($a_obj_id)
Lookup title.
static sortArray($array, $a_array_sortby, $a_array_sortorder=0, $a_numeric=false, $a_keep_keys=false)
sortArray
static getInstanceByObjId($a_obj_id, $a_user_id=0)
Get instance.
static now()
Return current timestamp in Y-m-d H:i:s format.
getCurrentPage()
Get current page.
trackPageAndChapterAccess($a_page_id)
Track page and chapter access.
$time_diff
Definition: langcheck.php:760
static getAnswerStatus($a_q_id, $a_user_id=0)
Get statistics for question.
getAllQuestionsCorrect()
Have all questoins been answered correctly (and questions exist)?
static _lookupActive($a_id, $a_parent_type, $a_check_scheduled_activation=false, $a_lang="-")
lookup activation status
static getImagePath($img, $module_path="", $mode="output", $offline=false)
get image path (for images located in a template directory)
static _lookupObjId($a_id)
static getInstance($a_tree_id)
Get Instance.
Date and time handling
$ilUser
Definition: imgupload.php:18
determineProgressStatus($a_obj_id, &$a_has_pred_incorrect_answers, &$a_has_pred_incorrect_not_unlocked_answers)
Determine progress status of nodes.
Create styles array
The data for the language used.
trackAccess($a_page_id)
Track access to lm page.
static getInstance($a_ref_id, $a_user_id=0)
Get instance.
static _lookupActivationData($a_id, $a_parent_type, $a_lang="-")
Lookup activation data.
$lm_set
global $lng
Definition: privfeed.php:17
global $ilDB
$ret
Definition: parser.php:6
__construct($a_id, $a_by_obj_id=false, $a_user_id)
Constructor.
Add data(end) time
Method that wraps PHPs time in order to allow simulations with the workflow.
static _isNodeVisible($a_node)
Is node visible for the learner.
loadLMTrackingData()
Load LM tracking data.