1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8//this script may only be included - so its better to die if called directly.
9if (strpos($_SERVER["SCRIPT_NAME"], basename(__FILE__)) !== false) {
10	header("location: index.php");
11	exit;
12}
13
14class LogsLib extends TikiLib
15{
16
17	function add_log($type, $message, $who = '', $ip = '', $client = '', $time = '')
18	{
19		global $user;
20		if (empty($who)) {
21			if (! empty($user)) {
22				$who = $user;
23			} else {
24				$who = 'Anonymous';
25			}
26		}
27		if (empty($ip)) {
28			$ip = $this->get_ip_address();
29		}
30		if (empty($client)) {
31			if (empty($_SERVER['HTTP_USER_AGENT'])) {
32				$client = 'NO USER AGENT';
33			} else {
34				$client = $_SERVER['HTTP_USER_AGENT'];
35			}
36		}
37		if (empty($time)) {
38			$time = $this->now;
39		}
40		$this->add_action($type, 'system', 'system', $message, $who, $ip, $client, $time);
41	}
42
43	function list_logs($type = '', $user = '', $offset = 0, $maxRecords = -1, $sort_mode = 'lastModif_desc', $find = '', $min = 0, $max = 0)
44	{
45		$actions = $this->list_actions($type, 'system', $user, $offset, $maxRecords, $sort_mode, $find, $min, $max, '', true);
46		return $actions;
47	}
48
49	function old_list_logs($type = '', $user = '', $offset = 0, $maxRecords = -1, $sort_mode = 'logtime_desc', $find = '', $min = 0, $max = 0)
50	{
51		$bindvars = [];
52		$amid = [];
53		$mid = '';
54
55		if ($find) {
56			$findesc = '%' . $find . '%';
57			$amid[] = "`logmessage` like ? or `loguser` like ? or 'logip' like ?";
58			$bindvars[] = $findesc;
59			$bindvars[] = $findesc;
60			$bindvars[] = $findesc;
61		}
62
63		if ($type) {
64			$amid[] = "`logtype` = ?";
65			$bindvars[] = $type;
66		}
67
68		if ($user) {
69			if (is_array($user)) {
70				$amid[] = '`loguser` in (' . implode(',', array_fill(0, count($user), '?')) . ')';
71				foreach ($user as $u) {
72					$bindvars[] = $u;
73				}
74			} else {
75				$amid[] = "`loguser` = ?";
76				$bindvars[] = $user ;
77			}
78		}
79
80		if ($min) {
81			$amid[] = "`logtime` > ?";
82			$bindvars[] = $min;
83		}
84
85		if ($max) {
86			$amid[] = "`logtime` < ?";
87			$bindvars[] = $max;
88		}
89
90		if (count($amid)) {
91			$mid = " where " . implode(" and ", $amid) . " ";
92		}
93
94		$query = "select `logId`,`loguser`,`logtype`,`logmessage`,`logtime`,`logip`,`logclient` ";
95		$query .= " from `tiki_logs` $mid order by " . $this->convertSortMode($sort_mode);
96		$query_cant = "select count(*) from `tiki_logs` $mid";
97		$ret = $this->fetchAll($query, $bindvars, $maxRecords, $offset);
98		$cant = $this->getOne($query_cant, $bindvars);
99		$retval = [];
100		$retval["data"] = $ret;
101		$retval["cant"] = $cant;
102		return $retval;
103	}
104
105	function clean_logs($date)
106	{
107		$query = "delete from `tiki_actionlog` where `objectType`='system' and `lastModif`<=?";
108		$this->query($query, [(int)$date]);
109	}
110
111	/**
112	 *  action = "Updated", "Created", "Removed", "Viewed", "Removed version $version", "Changed actual version to $version"
113	 * type = 'wiki page', 'category', 'article', 'image gallery', 'tracker', 'forum thread'
114	 * TODO: merge $param and $contributions together into a hash and but everything in actionlog_params
115	 */
116	function object_must_be_logged($action, $object, $objectType)
117	{
118		global $prefs;
119
120		if ($objectType == 'wiki page' && $action != 'Viewed') {
121			$logObject = true; // to have the tiki_my_edit, history and mod-last_modif_pages
122		} else {
123			$logObject = $this->action_must_be_logged($action, $objectType);
124		}
125
126		$logCateg = $prefs['feature_categories'] == 'y' ? $this->action_must_be_logged('*', 'category') : false;
127
128		if (! $logObject && ! $logCateg) {
129			return 0;
130		}
131
132		if ($logCateg) {
133			$categlib = TikiLib::lib('categ');
134			if ($objectType == 'comment') {
135				preg_match('/type=([^&]*)/', $param, $matches);
136				$categs = $categlib->get_object_categories($matches[1], $object);
137			} else {
138				$categs = $categlib->get_object_categories($objectType, $object);
139			}
140		}
141	}
142	function add_action($action, $object, $objectType = 'wiki page', $param = '', $who = '', $ip = '', $client = '', $date = '', $contributions = '', $hash = '', $log = '')
143	{
144		global $user, $prefs;
145
146		if (is_array($param)) {
147			$param = http_build_query($param, '', '&');
148		}
149
150		if ($objectType == 'wiki page' && $action != 'Viewed') {
151			$logObject = true; // to have the tiki_my_edit, history and mod-last_modif_pages
152		} else {
153			$logObject = $this->action_must_be_logged($action, $objectType);
154		}
155
156		$logCateg = false;
157		if (isset($prefs['feature_categories'])) {
158			$logCateg = $prefs['feature_categories'] == 'y' ? $this->action_must_be_logged('*', 'category') : false;
159		}
160
161		if (! $logObject && ! $logCateg) {
162			return 0;
163		}
164
165		if ($date == '') {
166			$date = $this->now;
167		}
168
169		if ($who == '') {
170			global $tokenlib;
171			if ($prefs['auth_token_access'] == 'y' && empty($user) && ! empty($tokenlib) && $tokenlib->ok) {
172				$user = '§TOKEN§';
173			} else {
174				$who = $user;
175			}
176		}
177
178		if ($ip == '') {
179			$ip = $this->get_ip_address();
180		}
181
182		if ($client == '') {
183			if (! empty($_SERVER['HTTP_USER_AGENT'])) {
184				$client = substr($_SERVER['HTTP_USER_AGENT'], 0, 200);
185			} else {
186				$client = null;
187			}
188		} else {
189			$client = substr($client, 0, 200);
190		}
191
192		if ($logCateg) {
193			$categlib = TikiLib::lib('categ');
194			if ($objectType == 'comment') {
195				preg_match('/type=([^&]*)/', $param, $matches);
196				$categs = $categlib->get_object_categories($matches[1], $object);
197			} else {
198				$categs = $categlib->get_object_categories($objectType, $object);
199			}
200		}
201
202		if (! empty($log)) {
203			$log = serialize($log);
204		}
205
206		$actions = [];
207		$nottostore = [
208			'auth_ldap_adminpass',
209			'auth_ldap_group_adminpass',
210			'shipping_fedex_password',
211			'shipping_ups_password',
212			'auth_phpbb_dbpasswd',
213			'zend_mail_smtp_pass',
214			'unified_elastic_url',
215			'proxy_pass'
216		];
217		if ($logObject) {
218			if (function_exists('mb_strcut')) {
219				$param = mb_strcut($param, 0, 200);
220			} else {
221				$param = substr($param, 0, 200);
222			}
223			if ($logCateg && count($categs) > 0) {
224				foreach ($categs as $categ) {
225					$query = "insert into `tiki_actionlog` " .
226									" (`action`, `object`, `lastModif`, `user`, `ip`, `comment`, `objectType`, `categId`, `client`, `log`) " .
227									" values(?,?,?,?,?,?,?,?,?,?)"
228									;
229
230					$this->query($query, [$action, $object, (int)$date, $who, $ip, $param, $objectType, $categ, $client, $log]);
231					$actions[] = $this->lastInsertId();
232				}
233			} elseif (! in_array($object, $nottostore)) {
234				// It's possible that this action is being added during upgrade to 18.x before the `log` field has been added
235				// to the database. To avoid error on doc/devtools/svnup.php, do not use the field here if $log is null
236				if ($log != null) {
237					$query = "insert into `tiki_actionlog`" .
238								" (`action`, `object`, `lastModif`, `user`, `ip`, `comment`, `objectType`, `client`, `log`)" .
239								" values(?,?,?,?,?,?,?,?,?)"
240								;
241
242					$this->query($query, [$action, $object, (int)$date, $who, $ip, $param, $objectType, $client, $log]);
243				} else {
244					$query = "insert into `tiki_actionlog`" .
245								" (`action`, `object`, `lastModif`, `user`, `ip`, `comment`, `objectType`, `client`)" .
246								" values(?,?,?,?,?,?,?,?)"
247								;
248
249					$this->query($query, [$action, $object, (int)$date, $who, $ip, $param, $objectType, $client]);
250				}
251				$actions[] = $this->lastInsertId();
252			}
253		}
254
255		if (! empty($contributions)) {
256			foreach ($actions as $a) {
257				$query = "insert into `tiki_actionlog_params` (`actionId`, `name`, `value`) values(?,?,?)";
258				foreach ($contributions as $contribution) {
259					$this->query($query, [$a, 'contribution', $contribution]);
260				}
261			}
262		}
263
264		if (! empty($hash)) {
265			$query = "insert into `tiki_actionlog_params` (`actionId`, `name`, `value`) values(?,?,?)";
266			foreach ($actions as $a) {
267				foreach ($hash as $h) {
268					foreach ($h as $param => $val) {
269						$this->query($query, [$a, $param, $val]);
270					}
271				}
272			}
273		}
274
275		return  isset($actions[0]) ? $actions[0] : 0;
276	}
277
278	function action_must_be_logged($action, $objectType)
279	{
280		global $prefs;
281
282		return $this->action_is_viewed($action, $objectType, true);
283	}
284
285	function action_is_viewed($action, $objectType, $logged = false)
286	{
287		global $prefs;
288		static $is_viewed;
289
290		// for previous compatibility
291		// the new action are added with a if ($feature..)
292		if (isset($prefs['feature_actionlog'])) {
293			if ($prefs['feature_actionlog'] != 'y') {
294				return true;
295			}
296		}
297
298
299		if (! isset($is_viewed)) {
300			$logActions = $this->get_all_actionlog_conf();
301			$is_viewed = [];
302			foreach ($logActions as $conf) {
303				if ($logged) {
304					$is_viewed[$conf['objectType']][$conf['action']] = $conf['status'] == 'v' || $conf['status'] == 'y';
305				} else {
306					$is_viewed[$conf['objectType']][$conf['action']] = $conf['status'] == 'v';
307				}
308			}
309		}
310
311		if (isset($is_viewed[$objectType][$action])) {
312			return $is_viewed[$objectType][$action];
313		} elseif (isset($is_viewed[$objectType]['*'])) {
314			return $is_viewed[$objectType]['*'];
315		} else {
316			return false;
317		}
318	}
319
320	function set_actionlog_conf($action, $objectType, $status)
321	{
322		global $actionlogConf;
323		$this->delete_actionlog_conf($action, $objectType);
324		$action = str_replace('*', '%', $action);
325		$query = "insert into `tiki_actionlog_conf` (`action`, `objectType`, `status`) values(?, ?, ?)";
326		$this->query($query, [$action, $objectType, $status]);
327		unset($actionlogConf);
328	}
329
330	function delete_actionlog_conf($action, $objectType)
331	{
332		if ($action === '*') {
333			$action = '%';
334		}
335		$query = "delete from `tiki_actionlog_conf` where `action`=? and `objectType`= ?";
336		$this->query($query, [$action, $objectType]);
337	}
338
339	function get_all_actionlog_conf()
340	{
341		global $actionlogConf;
342
343		if (! isset($actionlogConf)) {
344			$actionlogConf = self::get_actionlog_conf();
345		}
346
347		return $actionlogConf;
348	}
349
350	function get_actionlog_conf($type = '%', $action = '%')
351	{
352		$actionlogconf = [];
353		$query = "select * from `tiki_actionlog_conf`" .
354						" where `objectType` like ? and `action` like ?" .
355						" order by `objectType` desc, `action` asc"
356						;
357		$result = $this->query($query, [$type, $action]);
358
359		while ($res = $result->fetchRow()) {
360			if ($res['action'] == '%') {
361				$res['action'] = '*';
362			}
363			$res['code'] = self::encode_actionlog_conf($res['action'], $res['objectType']);
364			$actionlogconf[] = $res;
365		}
366
367		return $actionlogconf;
368	}
369
370	function get_actionlog_types()
371	{
372		$actionlogtype = [];
373		$query = "select distinct `objectType` from `tiki_actionlog_conf` order by `objectType`";
374		$result = $this->query($query, []);
375		while ($res = $result->fetchRow()) {
376			$actionlogtypes[] = $res['objectType'];
377		}
378		return $actionlogtypes;
379	}
380
381	function get_actionlog_actions()
382	{
383		$actionlogactions = [];
384		$query = "select distinct `action` from `tiki_actionlog_conf` order by `action`";
385		$result = $this->query($query, []);
386		while ($res = $result->fetchRow()) {
387			if ($res['action'] != '%') {
388				$actionlogactions[] = $res['action'];
389			}
390		}
391		return $actionlogactions;
392	}
393
394	function encode_actionlog_conf($action, $objectType)
395	{
396		return str_replace(' ', '0', $action . '_' . $objectType);
397	}
398
399	function decode_actionlog_conf($string)
400	{
401		return explode('_', str_replace('0', ' ', $conf));
402	}
403
404	function list_actions($action = '', $objectType = '', $user = '', $offset = 0, $maxRecords = -1, $sort_mode = 'lastModif_desc', $find = '', $start = 0, $end = 0, $categId = '', $all = false
405			)
406	{
407		global $prefs, $section;
408		$tikilib = TikiLib::lib('tiki');
409		$contributionlib = TikiLib::lib('contribution');
410
411		$bindvars = [];
412		$bindvarsU = [];
413		$amid = [];
414		$mid1 = '';
415		$mid2 = '';
416		if ($find) {
417			$findesc = '%' . $find . '%';
418			$amid[] = "(`comment` like ? or a.`action` like ? or `object` like ?)";
419			$bindvars[] = $findesc;
420			$bindvars[] = $findesc;
421			$bindvars[] = $findesc;
422		}
423		if ($action) {
424			$amid[] = "a.`action` = ?";
425			$bindvars[] = $action;
426		}
427		if ($objectType) {
428			$amid[] = "c.`objectType` = ?";
429			$bindvars[] = $objectType;
430		}
431		if ($user == 'Anonymous') {
432			$amid[] = "`user` = ?";
433			$bindvars[] = '' ;
434		} elseif ($user == 'Registered') {
435			$amid[] = "`user` != ?";
436			$bindvars[] = '' ;
437		} elseif ($user) {
438			if (is_array($user)) {
439				$mid1 = '`user` in (' . implode(',', array_fill(0, count($user), '?')) . ')';
440				$mid2 = 'ap.`value` in (' . implode(',', array_fill(0, count($user), '?')) . ') and ap.`name`=? and ap.`actionId`=a.`actionId`';
441				foreach ($user as $u) {
442					$bindvarsU[] = $u;
443				}
444
445				foreach ($user as $u) {
446					$bindvarsU[] = $tikilib->get_user_id($u);
447				}
448
449				$bindvarsU[] = 'contributor';
450			} else {
451				$mid1 = '`user` = ?';
452				$mid2 = 'ap.`value`=? and ap.`name`=? and ap.`actionId`=a.`actionId`';
453				$bindvarsU[] = $user ;
454				$bindvarsU[] = $tikilib->get_user_id($user);
455				$bindvarsU[] = 'contributor';
456			}
457		}
458
459		if ($start) {
460			$amid[] = "`lastModif` > ?";
461			$bindvars[] = $start;
462		}
463
464		if ($end) {
465			$amid[] = "`lastModif` < ?";
466			$bindvars[] = $end;
467		}
468
469		if ($categId && $categId != 0) {
470			if (is_array($categId)) {
471				$amid[] = "`categId`in (?)";
472				$bindvars[] = implode(",", $categId);
473			} else {
474				$amid[] = "`categId` = ?";
475				$bindvars[] = $categId;
476			}
477		}
478
479		$amid[] = " a.`action` like c.`action` and a.`objectType` = c.`objectType`" . ($all ? "" : " and (c.`status` = 'v')");
480
481		if (count($amid)) {
482			$mid = implode(" and ", $amid);
483		}
484
485		if (! empty($bindvarsU)) {
486			$bindvars = array_merge($bindvars, $bindvarsU, $bindvars);
487			$query = "(select distinct a.* from `tiki_actionlog` a ,`tiki_actionlog_conf` c where $mid and $mid1)";
488			$query .= "union (select distinct a.* from `tiki_actionlog` a ,`tiki_actionlog_conf` c,`tiki_actionlog_params` ap where $mid2 and $mid)";
489
490			$query_cant = "select count(distinct `actionId`) from `tiki_actionlog`" .
491										" where `actionId` in" .
492										" (select distinct a.`actionId` from `tiki_actionlog` a ,`tiki_actionlog_conf` c" .
493										" where $mid and $mid1 union select distinct a.`actionId`" .
494										" from `tiki_actionlog` a ,`tiki_actionlog_conf` c,`tiki_actionlog_params` ap where $mid2 and $mid)"
495										;
496		} else {
497			$query = "select distinct a.* from `tiki_actionlog` a ,`tiki_actionlog_conf` c where $mid";
498			$query_cant = "select count(distinct actionId) from `tiki_actionlog` a ,`tiki_actionlog_conf` c where $mid";
499		}
500
501		$query .= " order by " . $this->convertSortMode($sort_mode);
502		$result = $this->query($query, $bindvars, $maxRecords, $offset);
503		$cant = $this->getOne($query_cant, $bindvars);
504		$ret = [];
505
506		while ($res = $result->fetchRow()) {
507			if ($prefs['feature_contribution'] == 'y' &&
508					($res['action'] == 'Created' || $res['action'] == 'Updated' || $res['action'] == 'Posted' || $res['action'] == 'Replied')) {
509				if ($res['objectType'] == 'wiki page') {
510					$res['contributions'] = $this->get_action_contributions($res['actionId']);
511				} elseif ($id = $this->get_comment_action($res)) {
512					$res['contributions'] = $this->get_action_contributions($res['actionId']);
513				} else {
514					$res['contributions'] = $contributionlib->get_assigned_contributions($res['object'], $res['objectType']); // todo: do a left join
515				}
516			}
517
518			if ($prefs['feature_contributor_wiki'] == 'y' && $res['objectType'] == 'wiki page') {
519				$res['contributors'] = $this->get_contributors($res['actionId']);
520				$res['nbContributors'] = 1 + count($res['contributors']);
521			}
522
523			if ($res['objectType'] == 'comment' && empty($res['categId'])) {
524				$categlib = TikiLib::lib('categ');
525				preg_match('/type=([^&]*)/', $res['comment'], $matches);
526				$categs = $categlib->get_object_categories($matches[1], $res['object']);
527				$i = 0;
528
529				foreach ($categs as $categId) {
530					$res['categId'] = $categId;
531					if ($i++ > 0) {
532						$ret[] = $res;
533					}
534				}
535			}
536
537			// For tiki logs
538			if ($res['objectType'] === 'system') {
539				$what = $res['object'] === 'system' ? '' : $res['object'] . ' : ';
540				$res['object'] = $res['action'];
541				$res['action'] = $what . $res['comment'];
542			}
543			$ret[] = $res;
544		}
545
546		return ['data' => $ret, 'cant' => $cant];
547	}
548
549	function sort_by_date($action1, $action2)
550	{
551		return ($action1['lastModif'] - $action2['lastModif']);
552	}
553
554	function get_login_time($logins, $startDate, $endDate, $actions)
555	{
556		//FIXME
557		if ($endDate > $this->now) {
558			$endDate = $this->now;
559		}
560
561		$logTimes = [];
562
563		foreach ($logins as $login) {
564			if (! array_key_exists($login['user'], $logTimes)) {
565				if ($login['action'] == 'timeout' || $login['action'] == 'logged out') {
566					$logTimes[$login['user']]['last'] = $startDate;
567				} else {
568					$logTimes[$login['user']]['last'] = 0;
569				}
570				$logTimes[$login['user']]['time'] = 0;
571				$logTimes[$login['user']]['nbLogins'] = 0;
572			}
573
574			if (strstr($login['action'], 'logged from') || $login['action'] == 'back') {
575				if (strstr($login['action'], 'logged from')) {
576					++$logTimes[$login['user']]['nbLogins'];
577				}
578				// can be already log in
579				if ($logTimes[$login['user']]['last'] == 0) {
580					$logTimes[$login['user']]['last'] = $login['lastModif'];
581				}
582			} elseif (($login['action'] == 'timeout' || $login['action'] == 'logged out') && $logTimes[$login['user']]['last'] > 0) {
583				$logTimes[$login['user']]['time'] += $login['lastModif'] - $logTimes[$login['user']]['last'];
584				$logTimes[$login['user']]['last'] = 0;
585			}
586		}
587
588		// update time for those still logged in
589		foreach ($logTimes as $user => $logTime) {
590			if ($logTime['last']) {
591				$logTimes[$user]['time'] += $endDate - $logTime['last'];
592			}
593		}
594
595		// update time for those who were always logged in
596		foreach ($actions as $action) {
597			if ($action['user'] && ! array_key_exists($action['user'], $logTimes)) {
598				$logTimes[$action['user']]['time'] = $endDate - $startDate;
599			}
600		}
601
602		foreach ($logTimes as $user => $login) {
603			$nbMin = floor($login['time'] / 60);
604			$nbHour = floor($nbMin / 60);
605			$nbDay = floor($nbHour / 24);
606			$logTimes[$user]['secs'] = $login['time'] - $nbMin * 60;
607			$logTimes[$user]['mins'] = $nbMin - $nbHour * 60;
608			$logTimes[$user]['hours'] = $nbHour - $nbDay * 24;
609			$logTimes[$user]['days'] = $nbDay;
610		}
611
612		return $logTimes;
613	}
614
615	function get_volume_action($action)
616	{
617		$bytes = [];
618
619		if (preg_match('/bytes=([0-9\-+]+)/', $action['comment'], $matches)) {//old syntax
620			if (preg_match('/\+([0-9]+)/', $matches[1], $m)) {
621				$bytes['add'] = $m[1];
622			}
623			if (preg_match('/\-([0-9]+)/', $matches[1], $m)) {
624				$bytes['del'] = $m[1];
625			}
626		} else {
627			if (preg_match('/add=([0-9\-+]+)/', $action['comment'], $matches)) {
628				$bytes['add'] = $matches[1];
629			}
630			if (preg_match('/del=([0-9\-+]+)/', $action['comment'], $matches)) {
631				$bytes['del'] = $matches[1];
632			}
633		}
634
635		return $bytes;
636	}
637
638	function get_comment_action($action)
639	{
640		if (preg_match('/comments_parentId=([0-9\-+]+)/', $action['comment'], $matches)) {
641			return $matches[1];
642		} elseif (preg_match('/#threadId=?([0-9\-+]+)/', $action['comment'], $matches)) {
643			return $matches[1];
644		} elseif (preg_match('/sheetId=([0-9]+)/', $action['comment'], $matches)) {
645			return $matches[1];
646		} elseif (preg_match('/postId=([0-9]+)/', $action['comment'], $matches)) {
647			return $matches[1];
648		} else {
649			return '';
650		}
651	}
652
653	function get_stat_actions_per_user($actions)
654	{
655		$stats = $this->get_stat_actions_per_field($actions, 'user');
656
657		return $stats;
658	}
659
660	function get_stat_actions_per_field($actions, $field = 'user')
661	{
662		$stats = [];
663		$actions_name = [];
664
665		$actionlogConf = $this->get_all_actionlog_conf();
666
667		foreach ($actions as $action) {
668			if (strpos($action['action'], 'logged from') === 0) {
669				$action['action'] = 'login';
670			}
671			if (strpos($action['action'], 'logged out') === 0) {
672				$action['action'] = 'login';
673			}
674			$name = $action['action'] . '/' . $action['objectType'];
675			$sort = $action['objectType'] . '/' . $action['action'];
676			if ($this->action_is_viewed($action['action'], $action['objectType']) and ! in_array($name, $actions_name)) {
677				$actions_name[$sort] = $name;
678			}
679		}
680
681		ksort($actions_name);
682
683		foreach ($actions as $action) {
684			$key = $action[$field];
685			if (! isset($stats[$key])) {
686				$stats[$key] = array_fill_keys($actions_name, 0);
687				$stats[$key][$field] = $action[$field];
688			}
689			$name = $action['action'] . '/' . $action['objectType'];
690			if (($index = array_search($name, $actions_name)) !== false) {
691				if ($field == 'object') {
692					$stats[$key]['link'] = isset($action['link']) ? $action['link'] : null;
693				}
694				++$stats[$key][$name];
695			}
696		}
697
698		sort($stats, SORT_STRING); // will sort on the first field
699
700		return $stats;
701	}
702
703	function get_stat_contributions_per_group($actions, $selectedGroups)
704	{
705		$tikilib = TikiLib::lib('tiki');
706		$statGroups = [];
707		foreach ($actions as $action) {
708			if (! empty($previousAction) &&
709						$action['lastModif'] == $previousAction['lastModif'] &&
710						$action['user'] == $previousAction['user'] &&
711						$action['object'] == $previousAction['object'] &&
712						$action['objectType'] == $previousAction['objectType']) {
713				// differ only by the categories
714				continue;
715			}
716
717			if (strpos($action['action'], 'logged from') === 0) {
718				$action['action'] = 'login';
719			}
720
721			if (strpos($action['action'], 'logged out') === 0) {
722				$action['action'] = 'login';
723			}
724
725			$previousAction = $action;
726
727			if (empty($action['user'])) {
728				$groups = ['Anonymous'];
729			} else {
730				$groups = $tikilib->get_user_groups($action['user']);
731				$groups = array_diff($groups, ['Anonymous']);
732			}
733
734			foreach ($groups as $key => $group) {
735				if (isset($selectedGroups) && $selectedGroups[$group] != 'y') {
736					continue;
737				}
738
739				if (empty($action['contributions'])) {
740					continue;
741				}
742
743				foreach ($action['contributions'] as $contribution) {
744					if (! isset($statGroups[$group])) {
745						$statGroups[$group][$contribution['name']]['add'] = 0;
746						$statGroups[$group][$contribution['name']]['del'] = 0;
747						$statGroups[$group][$contribution['name']]['dif'] = 0;
748					}
749					$statGroups[$group][$contribution['name']]['add'] += $action['contributorAdd'];
750					$statGroups[$group][$contribution['name']]['del'] += $action['contributorDel'];
751					$statGroups[$group][$contribution['name']]['dif'] += $action['contributorAdd'] - $action['contributorDel'];
752				}
753			}
754		}
755		ksort($statGroups);
756
757		return $statGroups;
758	}
759
760	function get_action_stat_categ($actions, $categNames)
761	{
762		$stats = [];
763		$actionlogConf = $this->get_all_actionlog_conf();
764
765		foreach ($actions as $action) {
766			//if ($action['categId'] == 0) print also stat for non categ object
767			//	continue;
768
769			if (strpos($action['action'], 'logged from') === 0) {
770				$action['action'] = 'login';
771			}
772
773			if (strpos($action['action'], 'logged out') === 0) {
774				$action['action'] = 'login';
775			}
776			$key = $action['categId'];
777
778			if (! array_key_exists($key, $stats)) {
779				$stats[$key]['category'] = $key ? $categNames[$key] : '';
780				foreach ($actionlogConf as $conf) {
781					// don't take category
782					if ($conf['status'] == 'v' && $conf['action'] != '*') {
783						$stats[$key][$conf['action'] . '/' . $conf['objectType']] = 0;
784					}
785				}
786			}
787			++$stats[$key][$action['action'] . '/' . $action['objectType']];
788		}
789		sort($stats); //sort on the first field category
790
791		return $stats;
792	}
793
794	function get_action_vol_categ($actions, $categNames)
795	{
796		$stats = [];
797		$actionlogConf = $this->get_all_actionlog_conf();
798
799		foreach ($actions as $action) {
800			//if ($action['categId'] == 0) print also stat for non categ object
801			//	continue;
802
803			if (strpos($action['action'], 'logged from') === 0) {
804				$action['action'] = 'login';
805			}
806
807			if (strpos($action['action'], 'logged out') === 0) {
808				$action['action'] = 'login';
809			}
810
811			if (! ($bytes = $this->get_volume_action($action))) {
812				continue;
813			}
814
815			$key = $action['categId'];
816			if (! array_key_exists($key, $stats)) {
817				$stats[$key]['category'] = $key ? $categNames[$key] : '';
818			}
819
820			if (! isset($stats[$key][$action['objectType']]['add'])) {
821				$stats[$key][$action['objectType']]['add'] = 0;
822				$stats[$key][$action['objectType']]['del'] = 0;
823				$stats[$key][$action['objectType']]['dif'] = 0;
824			}
825			$dif = 0;
826
827			if (isset($bytes['add'])) {
828				$stats[$key][$action['objectType']]['add'] += $bytes['add'];
829				$dif = $bytes['add'];
830			}
831
832			if (isset($bytes['del'])) {
833				$stats[$key][$action['objectType']]['del'] += $bytes['del'];
834				$dif -= $bytes['del'];
835			}
836
837			$stats[$key][$action['objectType']]['dif'] += $dif;
838		}
839		sort($stats); //sort on the first field category
840
841		return $stats;
842	}
843
844	function get_action_vol_user_categ($actions, $categNames)
845	{
846		$stats = [];
847		$actionlogConf = $this->get_all_actionlog_conf();
848
849		foreach ($actions as $action) {
850			//if ($action['categId'] == 0) print also stat for non categ object
851			//	continue;
852
853			if (strpos($action['action'], 'logged from') === 0) {
854				$action['action'] = 'login';
855			}
856
857			if (strpos($action['action'], 'logged out') === 0) {
858				$action['action'] = 'login';
859			}
860
861			if ($action['user'] == ''
862					|| ! ($bytes = $this->get_volume_action($action))) {
863				continue;
864			}
865
866			$key = $action['categId'] . '/' . $action['user'];
867			if (! array_key_exists($key, $stats)) {
868				$stats[$key]['category'] = $action['categId'] ? $categNames[$action['categId']] : '';
869				$stats[$key]['user'] = $action['user'];
870			}
871
872			if (! isset($stats[$key][$action['objectType']]['add'])) {
873				$stats[$key][$action['objectType']]['add'] = 0;
874				$stats[$key][$action['objectType']]['del'] = 0;
875				$stats[$key][$action['objectType']]['dif'] = 0;
876			}
877
878			$dif = 0;
879			if (isset($bytes['add'])) {
880				$stats[$key][$action['objectType']]['add'] += $bytes['add'];
881				$dif = $bytes['add'];
882			}
883
884			if (isset($bytes['del'])) {
885				$stats[$key][$action['objectType']]['del'] += $bytes['del'];
886				$dif -= $bytes['del'];
887			}
888			$stats[$key][$action['objectType']]['dif'] += $dif;
889		}
890		sort($stats); //sort on the first field category
891
892		return $stats;
893	}
894
895	function get_action_vol_type($vols)
896	{
897		$types = [];
898		foreach ($vols as $vol) {
899			foreach ($vol as $key => $value) {
900				if ($key != 'category' && $key != 'user' && ! in_array($key, $types)) {
901					$types[] = $key;
902				}
903			}
904		}
905
906		return $types;
907	}
908
909	function get_actions_per_user_categ($actions, $categNames)
910	{
911		$stats = [];
912		$actionlogConf = $this->get_all_actionlog_conf();
913		foreach ($actions as $action) {
914			if (empty($action['categId'])) {
915				continue;
916			}
917
918			if (strpos($action['action'], 'logged from') === 0) {
919				$action['action'] = 'login';
920			}
921
922			if (strpos($action['action'], 'logged out') === 0) {
923				$action['action'] = 'login';
924			}
925
926			$key = $action['categId'] . '/' . $action['user'];
927			;
928
929			if (! array_key_exists($key, $stats)) {
930				$stats[$key]['category'] = $categNames[$action['categId']];
931				$stats[$key]['user'] = $action['user'];
932				foreach ($actionlogConf as $conf) {
933					// don't take category
934					if ($conf['status'] == 'v' && $conf['action'] != '*') {
935						$stats[$key][$conf['action'] . '/' . $conf['objectType']] = 0;
936					}
937				}
938			}
939			++$stats[$key][$action['action'] . '/' . $action['objectType']];
940		}
941		sort($stats); // sort on the first fields categ , then user
942
943		return $stats;
944	}
945
946	function in_kb($vol)
947	{
948		for ($i = count($vol) - 1; $i >= 0; --$i) {
949			foreach ($vol[$i] as $k => $v) {
950				if ($k != 'category' && $k != 'user') {
951					$vol[$i][$k]['add'] = round($vol[$i][$k]['add'] / 1024);
952					$vol[$i][$k]['del'] = round($vol[$i][$k]['del'] / 1024);
953					$vol[$i][$k]['dif'] = round($vol[$i][$k]['dif'] / 1024);
954				}
955			}
956		}
957		return $vol;
958	}
959
960	function export($actionlogs, $unit = 'b')
961	{
962		foreach ($actionlogs as $action) {
963			if (! isset($action['object'])) {
964				$action['object'] = '';
965			}
966
967			if (! isset($action['categName'])) {
968				$action['categName'] = '';
969				$action['categId'] = '';
970			}
971
972			if (! isset($action['add'])) {
973				$action['add'] = '';
974			}
975
976			if (! isset($action['del'])) {
977				$action['del'] = '';
978			}
979
980			if (! isset($action['ip'])) {
981				$action['ip'] = '';
982			}
983
984			$csv .= '"' . $action['user']
985				. '","' . $this->date_format("%y%m%d", $action['lastModif'])
986				. '","' . $this->date_format("%H:%M", $action['lastModif'])
987				. '","' . $action['action']
988				. '","' . $action['objectType']
989				. '","' . $action['object']
990				. '","' . $action['categName']
991				. '","' . $action['categId']
992				. '","' . $action['ip']
993				. '","' . $unit
994				. '","' . $action['add']
995				. '","' . $action['del']
996				. '","'
997				;
998
999			if (isset($action['contributions'])) {
1000				$i = 0;
1001				foreach ($action['contributions'] as $contribution) {
1002					if ($i++) {
1003						$csv .= ',';
1004					}
1005					$csv .= $contribution['name'];
1006				}
1007			}
1008			$csv .= "\"\n";
1009		}
1010
1011		return $csv;
1012	}
1013
1014	function get_action_params($actionId, $name = '')
1015	{
1016		if (empty($name)) {
1017			$query = "select * from `tiki_actionlog_params` where `actionId`=?";
1018			$ret = $this->fetchAll($query, [$actionId]);
1019		} else {
1020			$query = "select `value` from `tiki_actionlog_params` where `actionId`=? and `name`=?";
1021			$result = $this->query($query, [$actionId, $name]);
1022			$ret = [];
1023			while ($res = $result->fetchRow()) {
1024				$ret[] = $res['value'];
1025			}
1026		}
1027
1028		return $ret;
1029	}
1030
1031	function get_action_contributions($actionId)
1032	{
1033		$query = "select tc.* from `tiki_contributions` tc, `tiki_actionlog_params` tp where tp.`actionId`=? and tp.`name`=? and tp.`value`=tc.`contributionId`";
1034
1035		return $this->fetchAll($query, [$actionId, 'contribution']);
1036	}
1037
1038	function rename($objectType, $oldName, $newName)
1039	{
1040		$query = "update `tiki_actionlog`set `comment`= concat(?, `comment`) where `object`=? and (`objectType`=? or `objectType`= ?) and `comment` not like ?";
1041		$this->query($query, ["old=$oldName&amp;", $oldName, $objectType, 'comment' , '%old=%']);
1042		$query = "update `tiki_actionlog`set `object`=? where `object`=? and (`objectType`=? or `objectType`= ?)";
1043		$this->query($query, [$newName, $oldName, $objectType, 'comment']);
1044	}
1045
1046	function update_category($actionId, $categId)
1047	{
1048		$query = "update `tiki_actionlog` set `categId`=? where `actionId`=?";
1049		$this->query($query, [$categId, $actionId]);
1050	}
1051
1052	function get_info_action($actionId)
1053	{
1054		$query = "select * from `tiki_actionlog`where `actionId`= ?";
1055		$result = $this->query($query, [$actionId]);
1056		if ($res = $result->fetchRow()) {
1057			return $res;
1058		} else {
1059			return null;
1060		}
1061	}
1062
1063	function get_user_registration_action($user)
1064	{
1065		$actions = $this->list_actions('register', 'system', '', 0, -1, 'lastModif_desc', 'created account', 0, 0, '', true);
1066		$n_actions = count($actions['data']);
1067		for ($al = 0; $al <= $n_actions - 1; $al++) { // $al = action listed, from the list of actions found matching these previous criteria
1068			$string = $actions['data'][$al]['action'];
1069			if (strpos($string, $user)) {
1070				return $actions['data'][$al];
1071			}
1072		}
1073		return '';
1074	}
1075
1076	function delete_params($actionId, $name = '')
1077	{
1078		$bindvars = [$actionId];
1079		if (! empty($name)) {
1080			$mid = 'and `name`= ?';
1081			$bindvars[] = $name;
1082		}
1083		$query = "delete from `tiki_actionlog_params` where `actionId`=? $mid";
1084		$this->query($query, $bindvars);
1085	}
1086
1087	function insert_params($actionId, $param, $values)
1088	{
1089		$query = "insert into `tiki_actionlog_params` (`actionId`, `name`, `value`) values(?,?,?)";
1090
1091		foreach ($values as $val) {
1092			$this->query($query, [$actionId, $param, $val]);
1093		}
1094	}
1095
1096	function get_stat_contribution($actions, $startDate, $endDate, $unit = 'w')
1097	{
1098		$contributions = [];
1099		$nbCols = ceil(($endDate - $startDate) / (60 * 60 * 24));
1100		if ($unit != 'd') {
1101			$nbCols = ceil($nbCols / 7);
1102		}
1103		foreach ($actions as $action) {
1104			if (isset($action['contributions'])) {
1105				if (! empty($previousAction)
1106						&& $action['lastModif'] == $previousAction['lastModif']
1107						&& $action['user'] == $previousAction['user']
1108						&& $action['object'] == $previousAction['object']
1109						&& $action['objectType'] == $previousAction['objectType']
1110					 ) {
1111					// differ only by the categories
1112					continue;
1113				}
1114
1115				$previousAction = $action;
1116
1117				foreach ($action['contributions'] as $contrib) {
1118					$i = floor(($action['lastModif'] - $startDate) / (60 * 60 * 24));
1119
1120					if ($unit != 'd') {
1121						$i = floor($i / 7);
1122					}
1123
1124					if (empty($contributions[$contrib['contributionId']])) {
1125						$contributions[$contrib['contributionId']]['name'] = $contrib['name'];
1126						for ($j = 0; $j < $nbCols; ++$j) {
1127							$contributions[$contrib['contributionId']]['stat'][$j]['add'] = 0;
1128							$contributions[$contrib['contributionId']]['stat'][$j]['del'] = 0;
1129							$contributions[$contrib['contributionId']]['stat'][$j]['nbAdd'] = 0;
1130							$contributions[$contrib['contributionId']]['stat'][$j]['nbDel'] = 0;
1131							$contributions[$contrib['contributionId']]['stat'][$j]['nbUpdate'] = 0;
1132						}
1133					}
1134
1135					if (! empty($action['add'])) {
1136						$contributions[$contrib['contributionId']]['stat'][$i]['add'] += $action['add'];
1137						if (empty($action['del'])) {
1138							++$contributions[$contrib['contributionId']]['stat'][$i]['nbAdd'];
1139						}
1140					}
1141
1142					if (! empty($action['del'])) {
1143						$contributions[$contrib['contributionId']]['stat'][$i]['del'] += $action['del'];
1144						if (empty($action['add'])) {
1145							++$contributions[$contrib['contributionId']]['stat'][$i]['nbDel'];
1146						}
1147					}
1148
1149					if (! empty($action['add']) && ! empty($action['del'])) {
1150						++$contributions[$contrib['contributionId']]['stat'][$i]['nbUpdate'];
1151					}
1152				}
1153			}
1154		}
1155
1156		return (['nbCols' => $nbCols, 'data' => $contributions]);
1157	}
1158
1159	function get_stat_contributions_per_user($actions)
1160	{
1161		$tab = [];
1162
1163		foreach ($actions as $action) {
1164			if (strpos($action['action'], 'logged from') === 0) {
1165				$action['action'] = 'login';
1166			}
1167
1168			if (strpos($action['action'], 'logged out') === 0) {
1169				$action['action'] = 'login';
1170			}
1171
1172			if (isset($action['contributions'])) {
1173				if (! empty($previousAction)
1174						&& $action['lastModif'] == $previousAction['lastModif']
1175						&& $action['object'] == $previousAction['object']
1176						&& $action['objectType'] == $previousAction['objectType']
1177						&& $action['categId'] != $previousAction['categId']
1178					 ) {
1179					// differ only by the categories
1180					continue;
1181				}
1182
1183				$previousAction = $action;
1184
1185				foreach ($action['contributions'] as $contrib) {
1186					if (empty($tab[$action['user']]) or empty($tab[$action['user']]['stat'][$contrib['contributionId']])) {
1187						$tab[$action['user']][$contrib['contributionId']]['name'] = $contrib['name'];
1188						$tab[$action['user']][$contrib['contributionId']]['stat']['add'] = 0;
1189						$tab[$action['user']][$contrib['contributionId']]['stat']['del'] = 0;
1190						$tab[$action['user']][$contrib['contributionId']]['stat']['nbAdd'] = 0;
1191						$tab[$action['user']][$contrib['contributionId']]['stat']['nbDel'] = 0;
1192						$tab[$action['user']][$contrib['contributionId']]['stat']['nbUpdate'] = 0;
1193					}
1194
1195					if ($action['contributorAdd']) {
1196						$tab[$action['user']][$contrib['contributionId']]['stat']['add'] += $action['contributorAdd'];
1197						if (! $action['contributorDel']) {
1198							++$tab[$action['user']][$contrib['contributionId']]['stat']['nbAdd'];
1199						}
1200					}
1201
1202					if ($action['contributorDel']) {
1203						$tab[$action['user']][$contrib['contributionId']]['stat']['del'] += $action['contributorDel'];
1204						if (! $action['contributorAdd']) {
1205							++$tab[$action['user']][$contrib['contributionId']]['stat']['nbDel'];
1206						}
1207					}
1208
1209					if ($action['contributorAdd'] && $action['contributorDel']) {
1210						++$tab[$action['user']][$contrib['contributionId']]['stat']['nbUpdate'];
1211					}
1212				}
1213			}
1214		}
1215		ksort($tab);
1216
1217		return ['data' => $tab, 'nbCols' => count($tab)];
1218		;
1219	}
1220
1221	function get_colors($nb)
1222	{
1223		$colors[] = 'red';
1224		if (! --$nb) {
1225			return $colors;
1226		}
1227		$colors[] = 'yellow';
1228		if (! --$nb) {
1229			return $colors;
1230		}
1231		$colors[] = 'blue';
1232		if (! --$nb) {
1233			return $colors;
1234		}
1235		$colors[] = 'gray';
1236		if (! --$nb) {
1237			return $colors;
1238		}
1239		$colors[] = 'green';
1240		if (! --$nb) {
1241			return $colors;
1242		}
1243		$colors[] = 'aqua';
1244		if (! --$nb) {
1245			return $colors;
1246		}
1247		$colors[] = 'lime';
1248		if (! --$nb) {
1249			return $colors;
1250		}
1251		$colors[] = 'maroon';
1252		if (! --$nb) {
1253			return $colors;
1254		}
1255		$colors[] = 'navy';
1256		if (! --$nb) {
1257			return $colors;
1258		}
1259		$colors[] = 'black';
1260		if (! --$nb) {
1261			return $colors;
1262		}
1263		$colors[] = 'purple';
1264		if (! --$nb) {
1265			return $colors;
1266		}
1267		$colors[] = 'silver';
1268		if (! --$nb) {
1269			return $colors;
1270		}
1271		$colors[] = 'teal';
1272		if (! --$nb) {
1273			return $colors;
1274		}
1275
1276		if ($nb > 0) {
1277			while (--$nb) {
1278				$colors[] = rand(1, 999999);
1279			}
1280		}
1281
1282		return $colors;
1283	}
1284
1285	function draw_contribution_vol($contributionStat, $type = 'add', $contributions)
1286	{
1287		$ret = [];
1288		$ret['totalVol'] = 0;
1289		$ret['x'][] = tra('Contributions');
1290		$ret['color'] = $this->get_colors($contributions['cant']);
1291		$iy = 0;
1292
1293		foreach ($contributions['data'] as $contribution) {
1294			$ret['label'][] = utf8_decode($contribution['name']);
1295			$vol = 0;
1296			for ($ix = 0; $ix < $contributionStat['nbCols']; ++$ix) {
1297				if (! empty($contributionStat['data'][$contribution['contributionId']]['stat'][$ix])) {
1298					$vol += $contributionStat['data'][$contribution['contributionId']]['stat'][$ix][$type];
1299				}
1300			}
1301
1302			$ret["y$iy"][] = $vol;
1303			$ret['totalVol'] += $vol;
1304			++$iy;
1305		}
1306
1307		return $ret;
1308	}
1309
1310	function draw_week_contribution_vol($contributionStat, $type = 'add', $contributions)
1311	{
1312		$ret = [];
1313		$ret['totalVol'] = 0;
1314
1315		for ($i = 1, $nb = $contributionStat['nbCols']; $nb; --$nb) {
1316			$ret['x'][] = $i++;
1317		}
1318
1319		$ret['color'] = $this->get_colors($contributions['cant']);
1320		$iy = 0;
1321
1322		foreach ($contributions['data'] as $contribution) {
1323			$ret['label'][] = utf8_decode($contribution['name']);
1324			for ($ix = 0; $ix < $contributionStat['nbCols']; ++$ix) {
1325				if (empty($contributionStat['data'][$contribution['contributionId']]) ||
1326						empty($contributionStat['data'][$contribution['contributionId']]['stat'][$ix])) {
1327					$ret["y$iy"][] = 0;
1328				} else {
1329					$ret["y$iy"][] = $contributionStat['data'][$contribution['contributionId']]['stat'][$ix][$type];
1330					$ret['totalVol'] += $contributionStat['data'][$contribution['contributionId']]['stat'][$ix][$type];
1331				}
1332			}
1333			++$iy;
1334		}
1335
1336		return $ret;
1337	}
1338
1339	function draw_contribution_user($userStat, $type = 'add', $contributions)
1340	{
1341		$ret = [];
1342		$ret['totalVol'] = 0;
1343
1344		foreach ($userStat['data'] as $user => $stats) {
1345			$ret['x'][] = utf8_decode($user);
1346		}
1347
1348		$ret['color'] = $this->get_colors($contributions['cant']);
1349		$iy = 0;
1350
1351		foreach ($contributions['data'] as $contribution) {
1352			$ret['label'][] = utf8_decode($contribution['name']);
1353			foreach ($userStat['data'] as $user => $stats) {
1354				if (empty($stats[$contribution['contributionId']])) {
1355					$ret["y$iy"][] = 0;
1356				} else {
1357					$ret["y$iy"][] = $stats[$contribution['contributionId']]['stat']["$type"];
1358					$ret['totalVol'] += $stats[$contribution['contributionId']]['stat']["$type"];
1359				}
1360			}
1361			++$iy;
1362		}
1363
1364		return $ret;
1365	}
1366
1367	function draw_contribution_group($groupContributions, $type = 'add', $contributions)
1368	{
1369		$ret = [];
1370		$ret['totalVol'] = 0;
1371
1372		foreach ($groupContributions as $group => $stats) {
1373			$ret['x'][] = utf8_decode($group);
1374		}
1375
1376		$ret['color'] = $this->get_colors($contributions['cant']);
1377		$iy = 0;
1378
1379		foreach ($contributions['data'] as $contribution) {
1380			$ret['label'][] = utf8_decode($contribution['name']);
1381			foreach ($groupContributions as $group => $stats) {
1382				if (empty($stats[$contribution['name']])) {
1383					$ret["y$iy"][] = 0;
1384				} else {
1385					$ret["y$iy"][] = $stats[$contribution['name']][$type];
1386					$ret['totalVol'] += $stats[$contribution['name']][$type];
1387				}
1388			}
1389			++$iy;
1390		}
1391
1392		return $ret;
1393	}
1394
1395	function get_contributors($actionId)
1396	{
1397		$query = 'select uu.`login` from `tiki_actionlog_params` tap, `users_users` uu where tap.`actionId`=? and tap.`name`=? and uu.`userId`=tap.`value`';
1398		return $this->fetchAll($query, [$actionId, 'contributor']);
1399	}
1400
1401	/*
1402	 * get the contributors of the last update of a wiki page
1403	 *
1404	 */
1405	function get_wiki_contributors($page_info)
1406	{
1407		$query = 'select distinct(uu.`login`), uu.`userId` ' .
1408						' from `tiki_actionlog_params` tap, `users_users` uu , `tiki_actionlog` ta' .
1409						' where tap.`actionId`= ta.`actionId` ' .
1410						' and tap.`name`=? ' .
1411						' and uu.`userId`=tap.`value` ' .
1412						' and ta.`object`=? ' .
1413						' and ta.`objectType`=? ' .
1414						' and ta.`lastModif`=? ' .
1415						' order by `login` asc'
1416						;
1417
1418		return $this->fetchAll($query, ['contributor', $page_info['pageName'], 'wiki page', $page_info['lastModif']]);
1419	}
1420
1421	function split_actions_per_contributors($actions, $users)
1422	{
1423		$contributorActions = [];
1424
1425		foreach ($actions as $action) {
1426			$bytes = $this->get_volume_action($action);
1427
1428			if (strpos($action['action'], 'logged from') === 0) {
1429				$action['action'] = 'login';
1430			}
1431
1432			if (strpos($action['action'], 'logged out') === 0) {
1433				$action['action'] = 'login';
1434			}
1435
1436			$nbC = isset($action['nbContributors']) ? $action['nbContributors'] : 1;
1437
1438			if (isset($bytes['add'])) {
1439				$action['add'] = $bytes['add'];
1440				$action['contributorAdd'] = round($bytes['add'] / $nbC);
1441				$action['comment'] = 'add=' . $action['contributorAdd'];
1442			}
1443
1444			if (isset($bytes['del'])) {
1445				$action['del'] = $bytes['del'];
1446				$action['contributorDel'] = round($bytes['del'] / $nbC);
1447				if (! empty($action['comment'])) {
1448					$action['comment'] .= '&del=' . $action['contributorDel'];
1449				} else {
1450					$action['comment'] = 'del=' . $action['contributorDel'];
1451				}
1452			}
1453
1454			if (empty($users) || in_array($action['user'], $users)) {
1455				$contributorActions[] = $action;
1456			}
1457
1458			if (isset($action['contributors'])) {
1459				foreach ($action['contributors'] as $contributor) {
1460					if (empty($users) || in_array($contributor['login'], $users)) {
1461						$action['user'] = $contributor['login'];
1462						$contributorActions[] = $action;
1463					}
1464				}
1465			}
1466		}
1467		return $contributorActions;
1468	}
1469
1470	function list_logsql($sort_mode = 'created_desc', $offset = 0, $maxRecords = -1, $find = '')
1471	{
1472		global $prefs;
1473		$bindvars = [];
1474
1475		if (! empty($find)) {
1476			$findesc = '%' . $find . '%';
1477			$amid = '`sql1` like ? or `params` like ? or `tracer` like ?';
1478			$bindvars[] = $findesc;
1479			$bindvars[] = $findesc;
1480			$bindvars[] = $findesc;
1481		}
1482
1483		$query = 'select * from `adodb_logsql`' . ($find ? " where $amid" : '') . ' order by ' . $this->convertSortMode($sort_mode);
1484		$ret = $this->fetchAll($query, $bindvars, $maxRecords, $offset);
1485		$query_cant = 'select count(*) from `adodb_logsql`' . ($find ? " where $amid" : '');
1486		$cant = $this->getOne($query_cant, $bindvars);
1487		$retval = [];
1488		$retval['data'] = $ret;
1489		$retval['cant'] = $cant;
1490
1491		return $retval;
1492	}
1493
1494	function clean_logsql()
1495	{
1496		$query = 'delete from  `adodb_logsql`';
1497		$this->query($query, []);
1498	}
1499
1500	function graph_to_jpgraph(&$jpgraph, $series, $accumulated = false, $color = 'whitesmoke', $colorLegend = 'white')
1501	{
1502		$jpgraph->SetScale('textlin');
1503		$jpgraph->setMarginColor($color);
1504		$jpgraph->xaxis->SetTickLabels($series['x']);
1505		$plot = [];
1506
1507		for ($i = 0; isset($series["y$i"]); ++$i) {
1508			$plot[$i] = new BarPlot($series["y$i"]);
1509			$plot[$i]->SetFillColor($series['color'][$i]);
1510			$plot[$i]->SetLegend($series['label'][$i]);
1511		}
1512
1513		if ($accumulated) {
1514			$gbplot = new AccBarPlot($plot);
1515		} else {
1516			$gbplot = new GroupBarPlot($plot);
1517		}
1518
1519		$jpgraph->legend->SetFillColor($colorLegend);
1520		$jpgraph->Add($gbplot);
1521	}
1522
1523	function insert_image($galleryId, $graph, $ext, $title, $period)
1524	{
1525		global $prefs, $user;
1526		$imagegallib = TikiLib::lib('imagegal');
1527
1528		$filename = $prefs['tmpDir'] . '/' . md5(rand() . time()) . '.' . $ext;
1529		$graph->Stroke($filename);
1530		$info = getimagesize($filename);
1531		$size = filesize($filename);
1532		$fp = fopen($filename, "rb");
1533		$data = fread($fp, $size);
1534		fclose($fp);
1535		$imagegallib->insert_image(
1536			$_REQUEST['galleryId'],
1537			$title . $period,
1538			'',
1539			$title . $period . '.' . $ext,
1540			'image/' . $ext,
1541			$data,
1542			$size,
1543			$info[0],
1544			$info[1],
1545			$user,
1546			'',
1547			''
1548		);
1549	}
1550
1551	function get_more_info($actions, $categNames = [])
1552	{
1553		global $tikilib, $prefs;
1554
1555		foreach ($actions as &$action) {
1556			if (empty($action['user'])) {
1557				$action['user'] = 'Anonymous';
1558			}
1559
1560			if ($action['categId'] && $categNames) {
1561				$action['categName'] = $categNames[$action['categId']];
1562			}
1563
1564			if ($bytes = $this->get_volume_action($action)) {
1565				if (isset($bytes['add'])) {
1566					$action['add'] = $bytes['add'];
1567				}
1568				if (isset($bytes['del'])) {
1569					$action['del'] = $bytes['del'];
1570				}
1571			}
1572
1573			switch ($action['objectType']) {
1574				case 'wiki page':
1575					if (preg_match("/old=(.*)/", $action['comment'], $matches)) {
1576						$action['link'] = 'tiki-index.php?page=' . $action['object'] . '&amp;old=' . $matches[1];
1577					} else {
1578						$action['link'] = 'tiki-index.php?page=' . $action['object'];
1579					}
1580					break;
1581
1582				case 'article':
1583					$action['link'] = 'tiki-read_article.php?articleId=' . $action['object'];
1584
1585					if (! isset($articleNames)) {
1586						$artlib = TikiLib::lib('art');
1587						$objects = $artlib->list_articles(0, -1, 'title_asc', '', 0, 0, '');
1588						$articleNames = [];
1589						foreach ($objects['data'] as $object) {
1590							$articleNames[$object['articleId']] = $object['title'];
1591						}
1592					}
1593
1594					if (! empty($articleNames[$action['object']])) {
1595						$action['object'] = $articleNames[$action['object']];
1596					}
1597					break;
1598
1599				case 'category':
1600					$action['link'] = 'tiki-browse_categories.php?parentId=' . $action['object'];
1601					if ($categNames && ! empty($categNames[$action['object']])) {
1602						$action['object'] = $categNames[$action['object']];
1603					}
1604					break;
1605
1606				case 'forum':
1607					if ($action['action'] == 'Removed') {
1608						$action['link'] = 'tiki-view_forum.php?forumId=' . $action['object'] . '&' . $action['comment'];// threadId dded for debug info
1609					} else {
1610						$action['link'] = 'tiki-view_forum_thread.php?' . $action['comment'];
1611					}
1612
1613					if (! isset($forumNames)) {
1614						$objects = TikiLib::lib('comments')->list_forums(0, -1, 'name_asc', '');
1615						$forumNames = [];
1616						foreach ($objects['data'] as $object) {
1617							$forumNames[$object['forumId']] = $object['name'];
1618						}
1619					}
1620
1621					if (! empty($forumNames[$action['object']])) {
1622						$action['object'] = $forumNames[$action['object']];
1623					}
1624					break;
1625
1626				case 'image gallery':
1627					if ($action['action'] == 'Uploaded') {
1628						$action['link'] = 'tiki-browse_image.php?galleryId=' . $action['object'] . '&' . $action['comment'];
1629					} else {
1630						$action['link'] = 'tiki-browse_gallery.php?galleryId=' . $action['object'];
1631					}
1632
1633					if (! isset($imageGalleryNames)) {
1634						$imagegallib = TikiLib::lib('imagegal');
1635						$objects = $imagegallib->list_galleries(0, -1, 'name_asc', 'admin');
1636						foreach ($objects['data'] as $object) {
1637							$imageGalleryNames[$object['galleryId']] = $object['name'];
1638						}
1639					}
1640
1641					if (! empty($imageGalleryNames[$action['object']])) {
1642						$action['object'] = $imageGalleryNames[$action['object']];
1643					}
1644					break;
1645
1646				case 'file gallery':
1647					if ($action['action'] == 'Uploaded' || $action['action'] == 'Downloaded') {
1648						$action['link'] = 'tiki-upload_file.php?galleryId=' . $action['object'] . '&' . $action['comment'];
1649					} else {
1650						$action['link'] = 'tiki-list_file_gallery.php?galleryId=' . $action['object'];
1651					}
1652
1653					if (! isset($fileGalleryNames)) {
1654						$filegallib = TikiLib::lib('filegal');
1655						$objects = $filegallib->list_file_galleries(0, -1, 'name_asc', 'admin', '', $prefs['fgal_root_id']);
1656						foreach ($objects['data'] as $object) {
1657							$fileGalleryNames[$object['id']] = $object['name'];
1658						}
1659					}
1660
1661					if (! empty($fileGalleryNames[$action['object']])) {
1662						$action['object'] = $fileGalleryNames[$action['object']];
1663					}
1664					break;
1665
1666				case 'comment':
1667					preg_match('/type=([^&]*)(&.*)/', $action['comment'], $matches);
1668
1669					switch ($matches[1]) {
1670						case 'wiki page':
1671						case 'wiki+page':
1672						case 'wiki%20page':
1673								$action['link'] = 'tiki-index.php?page=' . $action['object'];
1674							if (preg_match("/old=(.*)&amp;/", $action['comment'], $ms)) {
1675								$action['link'] .= '&amp;old=' . $ms[1];
1676							}
1677								$action['link'] .= $matches[2];
1678							break;
1679
1680						case 'file gallery':
1681							$action['link'] = 'tiki-list_file_gallery.php?galleryId=' . $action['object'] . $matches[2];
1682							break;
1683
1684						case 'image gallery':
1685							$action['link'] = 'tiki-browse_gallery.php?galleryId=' . $action['object'] . $matches[2];
1686							break;
1687					}
1688
1689					break;
1690
1691				case 'sheet':
1692					if (! isset($sheetNames)) {
1693						$sheetlib = TikiLib::lib('sheet');
1694						$objects = $sheetlib->list_sheets();
1695						foreach ($objects['data'] as $object) {
1696							$sheetNames[$object['sheetId']] = $object['title'];
1697						}
1698					}
1699
1700					if (! empty($sheetNames[$action['object']])) {
1701						$action['object'] = $sheetNames[$action['object']];
1702					}
1703
1704					$action['link'] = 'tiki-view_sheets.php?sheetId=' . $action['object'];
1705					break;
1706
1707				case 'blog':
1708					if (! isset($blogNames)) {
1709						$bloglib = TikiLib::lib('blog');
1710						$objects = $bloglib->list_blogs();
1711						foreach ($objects['data'] as $object) {
1712							$blogNames[$object['blogId']] = $object['title'];
1713						}
1714					}
1715
1716					$action['link'] = 'tiki-view_blog.php?' . $action['comment'];
1717
1718					if (! empty($blogNames[$action['object']])) {
1719						$action['object'] = $blogNames[$action['object']];
1720					}
1721					break;
1722			}
1723		}
1724
1725		return $actions;
1726	}
1727
1728	function remove_action($actionId)
1729	{
1730		$query = 'delete from `tiki_actionlog_params` where `actionId`=?';
1731		$this->query($query, [$actionId]);
1732		$query = 'delete from `tiki_actionlog` where `actionId`=?';
1733		return $this->query($query, [$actionId]);
1734	}
1735
1736	function get_who_viewed($mystuff, $anonymous = true)
1737	{
1738		if (! $mystuff) {
1739			return false;
1740		}
1741
1742		global $prefs;
1743		$bindvars = [];
1744		$mid = '';
1745		foreach ($mystuff as $obj) {
1746			// If changing type, compose rest of partial filter immediately
1747			if (isset($objectType) && $obj["objectType"] != $objectType) {
1748				$mid .= ' and `object` in (' . implode(',', array_fill(0, $thistype, '?')) . ')';
1749				// add comments filter if needed
1750				if ($comments) {
1751					$bindvars = array_merge($bindvars, $comments);
1752					$mid .= ' and `comment` in (' . implode(',', array_fill(0, count($comments), '?')) . ')';
1753				}
1754			}
1755
1756			// If starting out, or changing type, to start new sub filter
1757			if (! isset($objectType) || $obj["objectType"] != $objectType) {
1758				$objectType = $obj["objectType"];
1759				if (! $mid) {
1760					$mid .= ' (`objectType` = ?';
1761				} else {
1762					$mid .= ' or `objectType` = ?';
1763				}
1764
1765				$bindvars[] = $objectType;
1766				// reset comment detection and counter
1767				$comments = [];
1768				$thistype = 0;
1769			}
1770
1771			// Just keep adding while objectType remain unchanged
1772			$bindvars[] = $obj["object"];
1773			if ($obj["comment"]) {
1774				// i.e. this objectType filters by comments also, not just on object (id)
1775				$comments[] = $obj["comment"];
1776			}
1777			$thistype++;
1778		}
1779
1780		// compose rest of filter for last type
1781		$mid .= ' and `object` in (' . implode(',', array_fill(0, $thistype, '?')) . ')';
1782		// add comments filter if needed
1783		if ($comments) {
1784			$bindvars = array_merge($bindvars, $comments);
1785			$mid .= ' and `comment` in (' . implode(',', array_fill(0, count($comments), '?')) . ')';
1786		}
1787		// add date filter
1788		if ($prefs['user_who_viewed_my_stuff_days']) {
1789			$firsttime = $this->now - 3600 * 24 * $prefs['user_who_viewed_my_stuff_days'];
1790			$mid .= ") and `lastModif` > $firsttime";
1791		}
1792
1793		if (! $anonymous) {
1794			$mid .= " and `user` != 'Anonymous'";
1795		}
1796
1797		$mid .= " and `action` = 'Viewed'";
1798		$mid .= " and `user` IS NOT NULL"; // just to avoid those strange null entries
1799		$query = "select *, max(`lastModif`) as `lastViewed` " .
1800						" from `tiki_actionlog` where $mid " .
1801						" group by `user`, `object`, `objectType`, `comment`, `actionId`, `action`, `ip`, `categId`, `client`, `lastModif` order by `lastViewed` desc"
1802						;
1803
1804		$ret = $this->fetchAll($query, $bindvars);
1805		$ret = $this->get_more_info($ret);
1806
1807		return $ret;
1808	}
1809
1810	function get_log_count($objectType, $action)
1811	{
1812		$query = "SELECT m.user,m.object,m.action
1813			FROM tiki_actionlog AS m
1814			INNER JOIN (
1815			  SELECT MAX(i.lastModif) lastModif, i.user
1816			  FROM tiki_actionlog i
1817			  where objectType = ?
1818			  GROUP BY i.user, i.object
1819			) AS j ON (j.lastModif = m.lastModif AND j.user = m.user)";
1820		return $this->fetchAll($query, [$objectType]);
1821	}
1822
1823	function get_bigblue_login_time($logins, $startDate, $endDate, $actions)
1824	{
1825		if ($endDate > $this->now) {
1826			$endDate = $this->now;
1827		}
1828			$logTimes = [];
1829
1830		foreach ($logins as $login) {
1831			if ($login['objectType'] == 'bigbluebutton') {
1832				if ($login['action'] == 'Joined Room') {
1833					if (! isset($logTimes[$login['user']][$login['object']]['starttime'])) {
1834						$logTimes[$login['user']][$login['object']]['starttime'] = $login['lastModif'];
1835					}
1836				}
1837
1838				if ($login['action'] == 'Left Room') {
1839					if (isset($logTimes[$login['user']][$login['object']]['starttime'])) {
1840						$logTimes[$login['user']][$login['object']]['total'][] = $login['lastModif'] - $logTimes[$login['user']][$login['object']]['starttime'];
1841						unset($logTimes[$login['user']][$login['object']]['starttime']);
1842					}
1843				}
1844			}
1845		}
1846
1847		foreach ($logTimes as $user => $object) {
1848			foreach ($object as $room => $times) {
1849				foreach ($times['total'] as $key => $time) {
1850					$nbMin = floor($time / 60);
1851					$nbHour = floor($nbMin / 60);
1852					$nbDay = floor($nbHour / 24);
1853					$log[$user][$room][$key] = floor($time / 60);
1854				}
1855			}
1856		}
1857		return $log;
1858	}
1859
1860	function export_bbb($actionlogs)
1861	{
1862		foreach ($actionlogs as $user => $room) {
1863			foreach ($room as $room_name => $values) {
1864				foreach ($values as $value) {
1865					$csv .= '"' . $user
1866					. '","' . $room_name
1867					. '","' . $value
1868					. '","'
1869					;
1870					$csv .= "\"\n";
1871				}
1872			}
1873		}
1874		return $csv;
1875	}
1876
1877	function delete_action($action, $object, $objectType, $comment)
1878	{
1879		$query = "delete from `tiki_actionlog` where `action` = ? and `object` = ? and `objectType` = ? and `comment` = ?";
1880		$this->query($query, [$action, $object, $objectType, $comment]);
1881	}
1882
1883	/**
1884	 * Log a revert action from another log
1885	 *
1886	 * @param int $actionId
1887	 * @param string $object
1888	 * @param string $page
1889	 * @param array $logInfo
1890	 * return null
1891	 */
1892	function revert_action($actionId, $object, $page, $logInfo)
1893	{
1894		if (! isset($logInfo['reverted'])) {
1895			$logInfoReverted = array_merge(['reverted' => true], $logInfo);
1896			$logInfoReverted = serialize($logInfoReverted);
1897			$query = "update `tiki_actionlog` set `log`= ? where `actionId`=?";
1898			$this->query($query, [$logInfoReverted, $actionId]);
1899			$type = $page . ' apply reverted';
1900			$message = $page . ' ' . tra('reverted');
1901			$this->add_action($type, $object, 'system', $message, '', '', '', '', '', '', $logInfo);
1902		}
1903	}
1904}
1905