ILIAS  release_5-1 Revision 5.0.0-5477-g43f3e3fab5f
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 {
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 {
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 ($c_stat == ilLMTracker::FAILED || $c_stat == ilLMTracker::IN_PROGRESS ||
454 ($c_stat == ilLMTracker::NOT_ATTEMPTED && is_array($this->page_questions[$c["child"]]) && count($this->page_questions[$c["child"]]) > 0))
455 {
456 $a_has_pred_incorrect_answers = true;
457 if (!$this->tree_arr["nodes"][$c["child"]]["unlocked"])
458 {
459 $a_has_pred_incorrect_not_unlocked_answers = true;
460 }
461 }
462 }
463 if ($cnt_completed == count($this->tree_arr["childs"][$a_obj_id]))
464 {
465 $status = ilLMTracker::COMPLETED;
466 }
467 }
468 else if ($this->tree_arr["nodes"][$a_obj_id]["type"] == "pg")
469 {
470 // check read event data
471 if (isset($this->re_arr[$a_obj_id]) && $this->re_arr[$a_obj_id]["read_count"] > 0)
472 {
473 $status = ilLMTracker::COMPLETED;
474 }
475 else if ($a_obj_id == $this->getCurrentPage())
476 {
477 $status = ilLMTracker::CURRENT;
478 }
479
480 $unlocked = false;
481 if (is_array($this->page_questions[$a_obj_id]))
482 {
483 // check questions, if one is failed -> failed
484 $unlocked = true;
485 foreach ($this->page_questions[$a_obj_id] as $q_id)
486 {
487 if (is_array($this->answer_status[$q_id])
488 && $this->answer_status[$q_id]["try"] > 0
489 && !$this->answer_status[$q_id]["passed"])
490 {
491 $status = ilLMTracker::FAILED;
492 if (!$this->answer_status[$q_id]["unlocked"])
493 {
494 $unlocked = false;
495 }
496 }
497 }
498
499 // check questions, if one is not answered -> in progress
500 if ($status != ilLMTracker::FAILED)
501 {
502 foreach ($this->page_questions[$a_obj_id] as $q_id)
503 {
504 if (!is_array($this->answer_status[$q_id])
505 || $this->answer_status[$q_id]["try"] == 0)
506 {
507 if ($status != ilLMTracker::NOT_ATTEMPTED)
508 {
509 $status = ilLMTracker::IN_PROGRESS;
510 }
511 }
512 }
513 $unlocked = false;
514 }
515 }
516 $this->tree_arr["nodes"][$a_obj_id]["unlocked"] = $unlocked;
517 $this->tree_arr["nodes"][$a_obj_id]["has_pred_incorrect_answers"] = $a_has_pred_incorrect_answers;
518 $this->tree_arr["nodes"][$a_obj_id]["has_pred_incorrect_not_unlocked_answers"] = $a_has_pred_incorrect_not_unlocked_answers;
519 }
520 }
521 else // free pages (currently not called, since only walking through tree structure)
522 {
523
524 }
525 $this->tree_arr["nodes"][$a_obj_id]["status"] = $status;
526
527 return $status;
528 }
529
530
538 public function getIconForLMObject($a_node, $a_highlighted_node = 0)
539 {
540 $this->loadLMTrackingData();
541 if ($a_node["child"] == $a_highlighted_node)
542 {
543 return ilUtil::getImagePath('scorm/running.svg');
544 }
545 if (isset($this->tree_arr["nodes"][$a_node["child"]]))
546 {
547 switch ($this->tree_arr["nodes"][$a_node["child"]]["status"])
548 {
550 return ilUtil::getImagePath('scorm/incomplete.svg');
551
553 return ilUtil::getImagePath('scorm/failed.svg');
554
556 return ilUtil::getImagePath('scorm/completed.svg');
557 }
558 }
559 return ilUtil::getImagePath('scorm/not_attempted.svg');
560 }
561
568 function hasPredIncorrectAnswers($a_obj_id, $a_ignore_unlock = false)
569 {
570 $this->loadLMTrackingData();
571 $ret = false;
572 if (is_array($this->tree_arr["nodes"][$a_obj_id]))
573 {
574 if ($a_ignore_unlock)
575 {
576 $ret = $this->tree_arr["nodes"][$a_obj_id]["has_pred_incorrect_answers"];
577 }
578 else
579 {
580 $ret = $this->tree_arr["nodes"][$a_obj_id]["has_pred_incorrect_not_unlocked_answers"];
581 }
582 }
583 return $ret;
584 }
585
589
597 {
598 global $ilDB, $lng, $ilPluginAdmin, $ilUser;
599
600 $blocked_users = array();
601
602 // load question/pages information
603 $this->page_questions = array();
604 $this->all_questions = array();
605 $page_for_question = array();
606 include_once("./Modules/LearningModule/classes/class.ilLMPageObject.php");
607 $q = ilLMPageObject::queryQuestionsOfLearningModule($this->lm_obj_id, "", "", 0, 0);
608 foreach ($q["set"] as $quest)
609 {
610 $this->page_questions[$quest["page_id"]][] = $quest["question_id"];
611 $this->all_questions[] = $quest["question_id"];
612 $page_for_question[$quest["question_id"]] = $quest["page_id"];
613 }
614 // get question information
615 include_once("./Modules/TestQuestionPool/classes/class.ilAssQuestionList.php");
616 $qlist = new ilAssQuestionList($ilDB, $lng, $ilPluginAdmin);
617 $qlist->setParentObjId(0);
618 $qlist->setJoinObjectData(false);
619 $qlist->addFieldFilter("question_id", $this->all_questions);
620 $qlist->load();
621 $qdata = $qlist->getQuestionDataArray();
622
623 // load question answer information
624 include_once("./Services/COPage/classes/class.ilPageQuestionProcessor.php");
625 $this->answer_status = ilPageQuestionProcessor::getAnswerStatus($this->all_questions);
626
627 include_once("./Modules/LearningModule/classes/class.ilLMPageObject.php");
628 foreach ($this->answer_status as $as)
629 {
630 if ($as["try"] >= $qdata[$as["qst_id"]]["nr_of_tries"] && $qdata[$as["qst_id"]]["nr_of_tries"] > 0 && !$as["passed"])
631 {
632 //var_dump($qdata[$as["qst_id"]]);
633 $name = ilObjUser::_lookupName($as["user_id"]);
634 $as["user_name"] = $name["lastname"].", ".$name["firstname"]." [".$name["login"]."]";
635 $as["question_text"] = $qdata[$as["qst_id"]]["question_text"];
636 $as["page_id"] = $page_for_question[$as["qst_id"]];
637 $as["page_title"] = ilLMPageObject::_lookupTitle($as["page_id"]);
638 $blocked_users[] = $as;
639 }
640 }
641
642 return $blocked_users;
643 }
644
651 static function _isNodeVisible($a_node)
652 {
653 include_once("./Services/COPage/classes/class.ilPageObject.php");
654
655 if ($a_node["type"] != "pg")
656 {
657 return true;
658 }
659
660 $lm_set = new ilSetting("lm");
661 $active = ilPageObject::_lookupActive($a_node["child"], "lm",
662 $lm_set->get("time_scheduled_page_activation"));
663
664 if(!$active)
665 {
666 $act_data = ilPageObject::_lookupActivationData((int) $a_node["child"], "lm");
667 if ($act_data["show_activation_info"] &&
668 (ilUtil::now() < $act_data["activation_start"]))
669 {
670 return true;
671 }
672 else
673 {
674 return false;
675 }
676 }
677 else
678 {
679 return true;
680 }
681 }
682
683
684}
685
686?>
const IL_CAL_UNIX
const IL_CAL_DATETIME
@classDescription Date and time handling
static _getAllLMObjectsOfLM($a_lm_id, $a_type="")
Get all objects of learning module.
static _lookupTitle($a_obj_id)
Lookup title.
static queryQuestionsOfLearningModule($a_lm_id, $a_order_field, $a_order_dir, $a_offset, $a_limit)
Get questions of learning module.
Track access to ILIAS learning modules.
static _isNodeVisible($a_node)
Is node visible for the learner.
getCurrentPage()
Get current page.
__construct($a_id, $a_by_obj_id=false, $a_user_id)
Constructor.
getAllQuestionsCorrect()
Have all questoins been answered correctly (and questions exist)?
trackLastPageAccess($usr_id, $lm_id, $obj_id)
Track last accessed page for a learning module.
trackAccess($a_page_id)
Track access to lm page.
trackPageAndChapterAccess($a_page_id)
Track page and chapter access.
static getInstance($a_ref_id, $a_user_id=0)
Get instance.
determineProgressStatus($a_obj_id, &$a_has_pred_incorrect_answers, &$a_has_pred_incorrect_not_unlocked_answers)
Determine progress status of nodes.
static $instancesbyobj
getIconForLMObject($a_node, $a_highlighted_node=0)
Get icon for lm object.
static getInstanceByObjId($a_obj_id, $a_user_id=0)
Get instance.
getBlockedUsersInformation()
Get blocked users information.
hasPredIncorrectAnswers($a_obj_id, $a_ignore_unlock=false)
Has predecessing incorrect answers.
setCurrentPage($a_val)
Set current page.
loadLMTrackingData()
Load LM tracking data.
static getInstance($a_tree_id)
Get Instance.
static _updateStatus($a_obj_id, $a_usr_id, $a_obj=null, $a_percentage=false, $a_force_raise=false)
Update status.
static _tracProgress($a_user_id, $a_obj_id, $a_ref_id, $a_obj_type='')
static _lookupName($a_user_id)
lookup user name
static _lookupObjId($a_id)
static _lookupActive($a_id, $a_parent_type, $a_check_scheduled_activation=false, $a_lang="-")
lookup activation status
_lookupActivationData($a_id, $a_parent_type, $a_lang="-")
Lookup activation data.
static getAnswerStatus($a_q_id, $a_user_id=0)
Get statistics for question.
ILIAS Setting Class.
static sortArray($array, $a_array_sortby, $a_array_sortorder=0, $a_numeric=false, $a_keep_keys=false)
sortArray
static now()
Return current timestamp in Y-m-d H:i:s format.
static getImagePath($img, $module_path="", $mode="output", $offline=false)
get image path (for images located in a template directory)
global $lng
Definition: privfeed.php:40
global $ilDB
$lm_set
global $ilUser
Definition: imgupload.php:15