19 declare(strict_types=0);
66 ?
int $parent_obj_id = null
79 if ($parent_obj_id == null) {
81 'SELECT r2.obj_id par_obj_id FROM object_reference r1 ' .
82 'JOIN tree t ON t.child = r1.ref_id ' .
83 'JOIN object_reference r2 ON r2.ref_id = t.parent ' .
84 'WHERE r1.obj_id = ' .
$ilDB->quote($obj_id,
'integer')
87 while ($prec =
$ilDB->fetchAssoc($pset)) {
88 $nid =
$ilDB->nextId(
"write_event");
90 'INSERT INTO write_event ' .
91 '(write_id, obj_id, parent_obj_id, usr_id, action, ts) VALUES ' .
92 '(%s, %s, %s, %s, %s, ' .
$ilDB->now() .
')',
93 $ilDB->quote($nid,
'integer'),
94 $ilDB->quote($obj_id,
'integer'),
95 $ilDB->quote($prec[
"par_obj_id"],
'integer'),
96 $ilDB->quote($usr_id,
'integer'),
97 $ilDB->quote($action,
'text')
100 $aff =
$ilDB->manipulate($query);
103 $nid =
$ilDB->nextId(
"write_event");
105 'INSERT INTO write_event ' .
106 '(write_id, obj_id, parent_obj_id, usr_id, action, ts) ' .
107 'VALUES (%s,%s,%s,%s,%s,' .
$ilDB->now() .
')',
108 $ilDB->quote($nid,
'integer'),
109 $ilDB->quote($obj_id,
'integer'),
110 $ilDB->quote($parent_obj_id,
'integer'),
111 $ilDB->quote($usr_id,
'integer'),
112 $ilDB->quote($action,
'text')
114 $aff =
$ilDB->manipulate($query);
123 bool $isCatchupWriteEvents =
true,
129 $ilDB = $DIC[
'ilDB'];
130 $tree = $DIC[
'tree'];
135 'SELECT * FROM read_event ' .
136 'WHERE obj_id = %s ' .
138 $ilDB->quote($obj_id,
'integer'),
139 $ilDB->quote($usr_id,
'integer')
142 $row =
$ilDB->fetchObject($res);
145 if ($a_ext_rc !== null) {
146 $read_count =
'read_count = ' .
$ilDB->quote(
150 $read_count_init = max(1, (
int) $a_ext_rc);
151 $read_count_diff = max(1, (
int) $a_ext_rc) - (
int) ($row?->read_count ?? 0);
153 $read_count =
'read_count = read_count + 1, ';
154 $read_count_init = 1;
155 $read_count_diff = 1;
159 if ($a_ext_time !== null) {
160 $time = (
int) $a_ext_time;
162 $time =
$ilDB->quote(
163 (time() - $row->last_access) <= $validTimeSpan
164 ? $row->spent_seconds + time() - $row->last_access
165 : $row->spent_seconds,
172 if ((time() - $row->last_access) <= $validTimeSpan) {
174 $read_count_init = 1;
175 $read_count_diff = 0;
178 $time_diff = $time - (
int) ($row->spent_seconds ?? 0);
182 'UPDATE read_event SET ' .
184 'spent_seconds = %s, ' .
185 'last_access = %s ' .
186 'WHERE obj_id = %s ' .
189 $ilDB->quote(time(),
'integer'),
190 $ilDB->quote($obj_id,
'integer'),
191 $ilDB->quote($usr_id,
'integer')
193 $aff =
$ilDB->manipulate($query);
195 self::_recordObjStats($obj_id, $time_diff, $read_count_diff);
197 if ($a_ext_time !==
false) {
198 $time = (
int) $a_ext_time;
203 $time_diff = $time - (
int) ($row->spent_seconds ?? 0);
209 'obj_id' => array(
'integer', $obj_id),
210 'usr_id' => array(
'integer', $usr_id)
213 'read_count' => array(
'integer', $read_count_init),
214 'spent_seconds' => array(
'integer', $time),
215 'first_access' => array(
'timestamp', date(
"Y-m-d H:i:s")),
217 'last_access' => array(
'integer', time())
221 self::$has_accessed[$obj_id][$usr_id] =
true;
223 self::_recordObjStats($obj_id, $time_diff, $read_count_diff);
226 if ($isCatchupWriteEvents) {
231 if (!in_array($a_type, array(
"cat",
"root",
"crs"))) {
232 if ($tree->isInTree($a_ref_id)) {
233 $path = $tree->getPathId($a_ref_id);
235 foreach (
$path as $p) {
239 if (($p != $a_ref_id) && (in_array(
248 'SELECT * FROM read_event ' .
249 'WHERE obj_id = %s ' .
251 $ilDB->quote($obj2_id,
'integer'),
252 $ilDB->quote($usr_id,
'integer')
254 $res2 =
$ilDB->query($query);
255 if ($row2 =
$ilDB->fetchAssoc($res2)) {
259 'UPDATE read_event SET ' .
260 'childs_read_count = childs_read_count + %s ,' .
261 'childs_spent_seconds = childs_spent_seconds + %s ' .
262 'WHERE obj_id = %s ' .
264 $ilDB->quote((
int) $read_count_diff,
'integer'),
265 $ilDB->quote((
int) $time_diff,
'integer'),
266 $ilDB->quote($obj2_id,
'integer'),
267 $ilDB->quote($usr_id,
'integer')
269 $aff =
$ilDB->manipulate($query);
271 self::_recordObjStats(
276 (
int) $read_count_diff
283 'obj_id' => array(
'integer', $obj2_id),
284 'usr_id' => array(
'integer', $usr_id)
287 'read_count' => array(
'integer', 1),
288 'spent_seconds' => array(
'integer', $time),
289 'first_access' => array(
'timestamp',
292 'last_access' => array(
'integer', time()),
293 'childs_read_count' => array(
'integer',
294 (
int) $read_count_diff
296 'childs_spent_seconds' => array(
'integer',
302 self::$has_accessed[$obj2_id][$usr_id] =
true;
304 self::_recordObjStats(
309 (
int) $read_count_diff
325 ?
int $a_spent_seconds,
327 ?
int $a_childs_spent_seconds = null,
328 ?
int $a_child_read_count = null
332 $ilDB = $DIC[
'ilDB'];
342 $fields[
'log_id'] = array(
"integer",
$ilDB->nextId(
'obj_stat_log'));
343 $fields[
"obj_id"] = array(
"integer", $a_obj_id);
345 $fields[
"tstamp"] = array(
"timestamp", $now);
346 $fields[
"yyyy"] = array(
"integer", date(
"Y"));
347 $fields[
"mm"] = array(
"integer", date(
"m"));
348 $fields[
"dd"] = array(
"integer", date(
"d"));
349 $fields[
"hh"] = array(
"integer", date(
"H"));
350 if ($a_spent_seconds > 0) {
351 $fields[
"spent_seconds"] = array(
"integer", $a_spent_seconds);
353 if ($a_read_count > 0) {
354 $fields[
"read_count"] = array(
"integer", $a_read_count);
356 if ($a_childs_spent_seconds > 0) {
357 $fields[
"childs_spent_seconds"] = array(
"integer",
358 $a_childs_spent_seconds
361 if ($a_child_read_count > 0) {
362 $fields[
"childs_read_count"] = array(
"integer",
366 $ilDB->insert(
"obj_stat_log", $fields);
369 if (mt_rand(1, 100) == 1) {
370 self::_syncObjectStats($now);
376 int $a_minimum = 20000
380 $ilDB = $DIC[
'ilDB'];
389 $set =
$ilDB->query(
"SELECT COUNT(*) AS counter FROM obj_stat_log");
390 $row =
$ilDB->fetchAssoc($set);
391 if ($row[
"counter"] >= $a_minimum) {
392 $ilAtomQuery =
$ilDB->buildAtomQuery();
393 $ilAtomQuery->addTableLock(
'obj_stat_log');
394 $ilAtomQuery->addTableLock(
'obj_stat_tmp');
396 $ilAtomQuery->addQueryCallable(
401 "SELECT COUNT(*) AS counter FROM obj_stat_log" 404 if ($row[
"counter"] >= $a_minimum) {
407 "INSERT INTO obj_stat_tmp" .
408 " SELECT * FROM obj_stat_log" .
409 " WHERE tstamp < " . $ilDB->
quote(
417 "DELETE FROM obj_stat_log" .
418 " WHERE tstamp < " . $ilDB->
quote(
436 $ilAtomQuery->addTableLock(
'obj_stat_tmp');
437 $ilAtomQuery->addTableLock(
'obj_stat');
439 $ilAtomQuery->addQueryCallable(
442 $sql =
"SELECT obj_id, obj_type, yyyy, mm, dd, hh, SUM(read_count) AS read_count," .
443 " SUM(childs_read_count) AS childs_read_count, SUM(spent_seconds) AS spent_seconds," .
444 " SUM(childs_spent_seconds) AS childs_spent_seconds" .
445 " FROM obj_stat_tmp" .
446 " GROUP BY obj_id, obj_type, yyyy, mm, dd, hh";
447 $set = $ilDB->
query($sql);
450 $where = array(
"obj_id" => array(
"integer",
453 "obj_type" => array(
"text",
456 "yyyy" => array(
"integer",
459 "mm" => array(
"integer", $row[
"mm"]),
460 "dd" => array(
"integer", $row[
"dd"]),
461 "hh" => array(
"integer", $row[
"hh"])
464 $where_sql = array();
465 foreach ($where as $field => $def) {
466 $where_sql[] = $field .
" = " . $ilDB->
quote(
471 $where_sql = implode(
" AND ", $where_sql);
475 "SELECT read_count, childs_read_count, spent_seconds," .
476 "childs_spent_seconds" .
478 " WHERE " . $where_sql
484 $fields = array(
"read_count" => array(
"integer",
485 $old[
"read_count"] + $row[
"read_count"]
487 "childs_read_count" => array(
"integer",
488 $old[
"childs_read_count"] + $row[
"childs_read_count"]
490 "spent_seconds" => array(
"integer",
491 $old[
"spent_seconds"] + $row[
"spent_seconds"]
493 "childs_spent_seconds" => array(
"integer",
494 $old[
"childs_spent_seconds"] + $row[
"childs_spent_seconds"]
498 $ilDB->
update(
"obj_stat", $fields, $where);
502 $fields[
"read_count"] = array(
"integer",
505 $fields[
"childs_read_count"] = array(
"integer",
506 $row[
"childs_read_count"]
508 $fields[
"spent_seconds"] = array(
"integer",
509 $row[
"spent_seconds"]
511 $fields[
"childs_spent_seconds"] = array(
"integer",
512 $row[
"childs_spent_seconds"]
515 $ilDB->
insert(
"obj_stat", $fields);
520 $ilDB->
query(
"DELETE FROM obj_stat_tmp");
593 $ilDB = $DIC[
'ilDB'];
595 if ($usr_id == null) {
597 'SELECT * FROM read_event ' .
598 'WHERE obj_id = %s ' .
599 'ORDER BY last_access DESC',
600 $ilDB->quote($obj_id,
'integer')
605 'SELECT * FROM read_event ' .
606 'WHERE obj_id = %s ' .
608 'ORDER BY last_access DESC',
609 $ilDB->quote($obj_id,
'integer'),
610 $ilDB->quote($usr_id,
'integer')
618 $events[$counter][
'obj_id'] = $row[
'obj_id'];
619 $events[$counter][
'usr_id'] = $row[
'usr_id'];
620 $events[$counter][
'last_access'] = $row[
'last_access'];
621 $events[$counter][
'read_count'] = $row[
'read_count'];
622 $events[$counter][
'spent_seconds'] = $row[
'spent_seconds'];
623 $events[$counter][
'first_access'] = $row[
'first_access'];
634 $ilDB = $DIC[
'ilDB'];
637 'SELECT DISTINCT(usr_id) usr FROM read_event ' .
638 'WHERE obj_id = %s ',
639 $ilDB->quote($a_obj_id,
'integer')
643 while ($row =
$ilDB->fetchObject($res)) {
644 $users[] = (
int) $row->usr;
652 public static function hasAccessed(
int $a_obj_id,
int $a_usr_id): bool
656 $ilDB = $DIC[
'ilDB'];
658 if (isset(self::$has_accessed[$a_obj_id][$a_usr_id])) {
659 return self::$has_accessed[$a_obj_id][$a_usr_id];
663 "SELECT usr_id FROM read_event WHERE " .
664 "obj_id = " .
$ilDB->quote($a_obj_id,
"integer") .
" AND " .
665 "usr_id = " .
$ilDB->quote($a_usr_id,
"integer")
667 if ($rec =
$ilDB->fetchAssoc($set)) {
668 return self::$has_accessed[$a_obj_id][$a_usr_id] =
true;
670 return self::$has_accessed[$a_obj_id][$a_usr_id] =
false;
683 $ilDB = $DIC[
'ilDB'];
693 'SELECT r1.obj_id,r2.obj_id p,d.owner,%s,d.create_date ' .
694 'FROM object_data d ' .
695 'LEFT JOIN write_event w ON d.obj_id = w.obj_id ' .
696 'JOIN object_reference r1 ON d.obj_id=r1.obj_id ' .
697 'JOIN tree t ON t.child=r1.ref_id ' .
698 'JOIN object_reference r2 on r2.ref_id=t.parent ' .
699 'WHERE w.obj_id IS NULL',
700 $ilDB->quote(
'create',
'text')
704 while ($rec =
$ilDB->fetchAssoc($set)) {
705 $nid =
$ilDB->nextId(
"write_event");
706 $query =
'INSERT INTO write_event ' .
707 '(write_id, obj_id,parent_obj_id,usr_id,action,ts) VALUES (' .
708 $ilDB->quote($nid,
"integer") .
"," .
709 $ilDB->quote($rec[
"obj_id"],
"integer") .
"," .
710 $ilDB->quote($rec[
"p"],
"integer") .
"," .
711 $ilDB->quote($rec[
"owner"],
"integer") .
"," .
712 $ilDB->quote(
"create",
"text") .
"," .
713 $ilDB->quote($rec[
"create_date"],
"timestamp") .
721 $ilSetting->set(
'enable_change_event_tracking',
'1');
723 return $res !== null;
735 $ilSetting->set(
'enable_change_event_tracking',
'0');
747 return $ilSetting->get(
'enable_change_event_tracking',
'0') ==
'1';
753 public static function _delete(
int $a_obj_id): bool
757 $ilDB = $DIC[
'ilDB'];
759 'DELETE FROM write_event WHERE obj_id = %s ',
760 $ilDB->quote($a_obj_id,
'integer')
762 $aff =
$ilDB->manipulate($query);
765 'DELETE FROM read_event WHERE obj_id = %s ',
766 $ilDB->quote($a_obj_id,
'integer')
768 $aff =
$ilDB->manipulate($query);
776 $ilDB = $DIC[
'ilDB'];
779 "DELETE FROM read_event" .
780 " WHERE obj_id = " .
$ilDB->quote($a_obj_id,
"integer")
790 $ilDB = $DIC[
'ilDB'];
793 "DELETE FROM read_event" .
794 " WHERE obj_id = " .
$ilDB->quote($a_obj_id,
"integer") .
795 " AND " .
$ilDB->in(
"usr_id", $a_user_ids,
"",
"integer")
803 $ilDB = $DIC[
'ilDB'];
806 "SELECT usr_id FROM read_event" .
807 " WHERE obj_id = " .
$ilDB->quote($a_obj_id,
"integer")
809 while ($row =
$ilDB->fetchAssoc($set)) {
824 string $t_first_access
828 $ilDB = $DIC->database();
830 'UPDATE read_event SET first_access=%s, last_access = %s WHERE obj_id=%s AND usr_id=%s',
831 array(
'timestamp',
'integer',
'integer',
'integer'),
832 array($t_first_access, $i_last_access, $obj_id, $usr_id)
static _delete(int $a_obj_id)
Delete object entries.
static _activate()
Activates change event tracking.
numRows(ilDBStatement $statement)
insert(string $table_name, array $values)
static _enabledObjectStatistics()
static _lookupUncaughtWriteEvents(int $obj_id, int $usr_id)
Reads all write events which occured on the object which happened after the last time the user caught...
fetchAssoc(ilDBStatement $statement)
update(string $table_name, array $values, array $where)
$where MUST contain existing columns only.
static hasAccessed(int $a_obj_id, int $a_usr_id)
Has accessed.
quote($value, string $type)
static _updateAccessForScormOfflinePlayer(int $obj_id, int $usr_id, int $i_last_access, string $t_first_access)
_updateAccessForScormOfflinePlayer needed to synchronize last_access and first_access when learning m...
static _syncObjectStats(?int $a_now=null, int $a_minimum=20000)
static _lookupObjId(int $ref_id)
static lookupUsersInProgress(int $a_obj_id)
static _recordReadEvent(string $a_type, int $a_ref_id, int $obj_id, int $usr_id, bool $isCatchupWriteEvents=true, $a_ext_rc=null, $a_ext_time=null)
static _lookupChangeState(int $obj_id, int $usr_id)
Returns the change state of the object for the specified user.
static _recordWriteEvent(int $obj_id, int $usr_id, string $action, ?int $parent_obj_id=null)
Records a write event.
query(string $query)
Run a (read-only) Query on the database.
static _deleteReadEvents(int $a_obj_id)
static _lookupReadEvents($obj_id, $usr_id=null)
Reads all read events which occured on the object.
foreach($mandatory_scripts as $file) $timestamp
Class ilChangeEvent tracks change events on repository objects.
static _recordObjStats(int $a_obj_id, ?int $a_spent_seconds, ?int $a_read_count, ?int $a_childs_spent_seconds=null, ?int $a_child_read_count=null)
static _isActive()
Returns true, if change event tracking is active.
static _deactivate()
Deactivates change event tracking.
static _deleteReadEventsForUsers(int $a_obj_id, array $a_user_ids)
static array $has_accessed
static _lookupType(int $id, bool $reference=false)
static _catchupWriteEvents(int $obj_id, int $usr_id, ?string $timestamp=null)
Catches up with all write events which occured before the specified timestamp.
static _getAllUserIds(int $a_obj_id)
static _getValidTimeSpan()