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
14/**
15 * Lib for user administration, groups and permissions.
16 */
17
18// some definitions for helping with authentication
19define('USER_VALID', 2);
20
21define('SERVER_ERROR', -1);
22define('PASSWORD_INCORRECT', -3);
23define('USER_NOT_FOUND', -5);
24define('ACCOUNT_DISABLED', -6);
25define('ACCOUNT_WAITING_USER', -9);
26define('USER_AMBIGOUS', -7);
27define('USER_NOT_VALIDATED', -8);
28define('USER_PREVIOUSLY_VALIDATED', -10);
29define('USER_ALREADY_LOGGED', -11);
30define('EMAIL_AMBIGUOUS', -12);
31define('TWO_FA_INCORRECT', -13);
32
33//added for Auth v1.3 support
34define('AUTH_LOGIN_OK', 0);
35
36use PragmaRX\Google2FA\Google2FA;
37use Symfony\Component\Yaml\Yaml;
38use Symfony\Component\Yaml\Exception\ParseException;
39use Zend\Ldap\Exception\LdapException;
40use OneLogin\Saml2;
41
42class UsersLib extends TikiLib
43{
44	// change this to an email address to receive debug emails from the LDAP code
45	public $debug = false;
46
47	public $usergroups_cache;
48	public $groupperm_cache;
49	public $groupinclude_cache;
50	public $userobjectperm_cache; // used to cache queries in object_has_one_permission()
51	public $get_object_permissions_for_user_cache;
52	static $cas_initialized = false;
53	static $userexists_cache = [];
54
55
56
57
58	function __construct()
59	{
60		parent::__construct();
61
62		// Initialize caches
63		$this->usergroups_cache = [];
64		$this->groupperm_cache = [[]];
65		$this->groupinclude_cache = [];
66		$this->get_object_permissions_for_user_cache = [];
67	}
68
69
70	function assign_object_permission($groupName, $objectId, $objectType, $permName)
71	{
72		$objectId = md5($objectType . TikiLib::strtolower($objectId));
73
74		$query = 'delete from `users_objectpermissions`	where `objectId` = ? and `objectType`=?';
75		$bindvars = [$objectId, $objectType];
76		if (! empty($groupName)) {
77			$query .= ' and `groupName` = ?';
78			$bindvars[] = $groupName;
79		}
80		if (! empty($permName)) {
81			$query .= ' and `permName` = ?';
82			$bindvars[] = $permName;
83		}
84		$result = $this->query($query, $bindvars);
85
86		if (! empty($permName) && ! empty($groupName)) {
87			$query = 'insert into `users_objectpermissions`' .
88				' (`groupName`, `objectId`, `objectType`, `permName`)' .
89				' values(?, ?, ?, ?)';
90
91			$result = $this->query($query, [$groupName, $objectId, $objectType, $permName]);
92		}
93
94		if ($objectType == 'file gallery') {
95			$cachelib = TikiLib::lib('cache');
96			$cachelib->empty_type_cache('fgals_perms_' . $objectId . '_');
97		}
98		return true;
99	}
100
101	function object_has_permission($user, $objectId, $objectType, $permName)
102	{
103		$groups = $this->get_user_groups($user);
104		$objectId = md5($objectType . TikiLib::strtolower($objectId));
105		$mid = implode(',', array_fill(0, count($groups), '?'));
106		$query = "select count(*) from `users_objectpermissions` where `groupName` in ($mid) and `objectId` = ? and `objectType` = ? and `permName` = ?";
107		$bindvars = array_merge($groups, [$objectId, $objectType, $permName]);
108		$result = $this->getOne($query, $bindvars);
109		if ($result > 0) {
110			return true;
111		} else {
112			return false;
113		}
114	}
115
116	function remove_object_permission($groupName, $objectId, $objectType, $permName)
117	{
118		$objectId = md5($objectType . TikiLib::strtolower($objectId));
119
120		$query = 'delete from `users_objectpermissions`' .
121			' where`objectId` = ? and `objectType` = ?';
122		$bindvars = [$objectId, $objectType];
123		if (! empty($groupName)) {
124			$query .= ' and `groupName` = ? ';
125			$bindvars[] = $groupName;
126		}
127		if (! empty($permName)) {
128			$query .= ' and `permName` = ? ';
129			$bindvars[] = $permName;
130		}
131
132		$result = $this->query($query, $bindvars);
133
134		if ($objectType == 'file gallery') {
135			$cachelib = TikiLib::lib('cache');
136			$cachelib->empty_type_cache('fgals_perms_' . $objectId . '_');
137		}
138
139		return true;
140	}
141
142	function copy_object_permissions($objectId, $destinationObjectId, $objectType)
143	{
144		$objectId = md5($objectType . TikiLib::strtolower($objectId));
145
146		$query = "select `permName`, `groupName`
147			from `users_objectpermissions`
148			where `objectId` =? and
149			`objectType` = ?";
150		$bindvars = [$objectId, $objectType];
151		$result = $this->query($query, $bindvars);
152		while ($res = $result->fetchRow()) {
153			$this->assign_object_permission($res["groupName"], $destinationObjectId, $objectType, $res["permName"]);
154		}
155		return true;
156	}
157
158	function get_object_permissions($objectId, $objectType, $group = '', $perm = '')
159	{
160		$objectId = md5($objectType . TikiLib::strtolower($objectId));
161
162		$query = "select `groupName`, `permName`
163			from `users_objectpermissions`
164			where `objectId` = ? and
165			`objectType` = ?";
166		$bindvars = [$objectId, $objectType];
167		if (! empty($group)) {
168			$query .= ' and `groupName`=?';
169			$bindvars[] = $group;
170		}
171		if (! empty($perm)) {
172			$query .= ' and `permName`=?';
173			$bindvars[] = $perm;
174		}
175		return $this->fetchAll($query, $bindvars);
176	}
177
178	function get_object_permissions_for_user($objectId, $objectType, $user)
179	{
180		$params = md5($objectId . $objectType . $user);
181		//Check the cache for these parameters
182		if (array_key_exists($params, $this->get_object_permissions_for_user_cache)) {
183			return $this->get_object_permissions_for_user_cache[$params];
184		}
185		$objectId = md5($objectType . TikiLib::strtolower($objectId));
186		$bindvars = [$objectId, $objectType];
187		$groups = $this->get_user_groups($user);
188		$bindvars = array_merge($bindvars, $groups);
189
190		$query = 'select `permName` ' .
191			' from `users_objectpermissions`' .
192			' where `objectId` = ? and `objectType` = ?' .
193			' and `groupName` in (' . implode(',', array_fill(0, count($groups), '?')) . ')';
194
195		$result = $this->query($query, $bindvars);
196		$ret = [];
197
198		while ($res = $result->fetchRow()) {
199			$ret[] = $res['permName'];
200		}
201
202		//Cache the result for this set of parameters
203		$this->get_object_permissions_for_user_cache[$params] = $ret;
204		return $ret;
205	}
206
207	function object_has_one_permission($objectId, $objectType)
208	{
209		$objectId = md5($objectType . TikiLib::strtolower($objectId));
210
211		if (! isset($this->userobjectperm_cache) || ! is_array($this->userobjectperm_cache)
212			|| ! isset($this->userobjectperm_cache[$objectId])) {
213			// i think, we really dont need the "and `objectType`=?" because the objectId should be unique due to the md5()
214			$query = 'select count(*) from `users_objectpermissions` where `objectId`=? and `objectType`=?';
215			$this->userobjectperm_cache[$objectId] = $this->getOne($query, [$objectId, $objectType]);
216		}
217
218		return $this->userobjectperm_cache[$objectId];
219	}
220
221	function user_exists($user)
222	{
223		if (! isset($userexists_cache[$user])) {
224			$query = 'select count(*) from `users_users` where upper(`login`) = ?';
225			$result = $this->getOne($query, [TikiLib::strtoupper($user)]);
226			$userexists_cache[$user] = $result;
227		}
228		return $userexists_cache[$user];
229	}
230	function user_exists_by_email($email)
231	{
232		if (! isset($userexists_cache[$email])) {
233			$query = 'select count(*) from `users_users` where upper(`email`) = ?';
234			$result = $this->getOne($query, [TikiLib::strtoupper($email)]);
235			$userexists_cache[$email] = $result;
236		}
237		return $userexists_cache[$email];
238	}
239	function get_user_real_case($user)
240	{
241		$query = 'select `login` from `users_users` where upper(`login`) = ?';
242		return $this->getOne($query, [TikiLib::strtoupper($user)]);
243	}
244
245	function group_exists($group)
246	{
247		return in_array($group, $this->list_all_groups());
248	}
249
250	/**
251	 * @param string $user : username
252	 * @param bool $remote_logout : logged out remotely (so do not redirect)
253	 * @param string $redir : url to redirect to. Uses home page according to prefs if empty
254	 * @return void : redirects to suitable homepage or redir param if not remote
255	 */
256	function user_logout($user, $remote_logout = false, $redir = '')
257	{
258		global $prefs, $user_cookie_site;
259
260		$logslib = TikiLib::lib('logs');
261		$logslib->add_log('login', 'logged out');
262
263		$userInfo = $this->get_user_info($user);
264		if ($prefs['login_multiple_forbidden'] === 'y') {
265			$this->delete_user_cookie($userInfo['userId']);
266		} else {
267			$secret = explode('.', $_COOKIE[$user_cookie_site]);
268			$this->delete_user_cookie($userInfo['userId'], $secret[0]);
269		}
270
271		if ($prefs['feature_intertiki'] == 'y' and $prefs['feature_intertiki_sharedcookie'] == 'y' and ! empty($prefs['feature_intertiki_mymaster'])) {
272			$remote = $prefs['interlist'][$prefs['feature_intertiki_mymaster']];
273			$remote['path'] = preg_replace('/^\/?/', '/', $remote['path']);
274			$client = new XML_RPC_Client($remote['path'], $remote['host'], $remote['port']);
275			$client->setDebug(0);
276			$msg = new XML_RPC_Message(
277				'intertiki.logout',
278				[
279					 new XML_RPC_Value($prefs['tiki_key'], 'string'),
280					 new XML_RPC_Value($user, 'string')
281				]
282			);
283			$client->send($msg);
284		}
285
286		// more local cleanup originally from tiki-logout.php
287
288		// go offline in Live Support
289		if ($prefs['feature_live_support'] == 'y') {
290			$access = TikiLib::lib('access');
291			global $lslib;
292			include_once('lib/live_support/lslib.php');
293			if ($lslib->get_operator_status($user) != 'offline') {
294				$lslib->set_operator_status($user, 'offline');
295			}
296		}
297
298		if ($prefs['auth_method'] === 'saml' && $prefs['saml_options_slo'] == 'y') {
299			$saml_instance = $this->get_saml_auth();
300			if (isset($saml_instance)) {
301				$nameId = null;
302				$sessionIndex = null;
303				if (isset($_SESSION['saml_nameid'])) {
304					$nameId = $_SESSION['saml_nameid'];
305				}
306				if (isset($_SESSION['saml_sessionindex'])) {
307					$sessionIndex = $_SESSION['saml_sessionindex'];
308				}
309				$saml_instance->logout(null, [], $nameId, $sessionIndex);
310			}
311		}
312
313		setcookie($user_cookie_site, '', -3600, $prefs['feature_intertiki_sharedcookie'] == 'y' ? '/' : $prefs['cookie_path'], $prefs['cookie_domain']);
314
315		/* change group home page or deactivate if no page is set */
316		if (! empty($redir)) {
317			$url = $redir;
318		} elseif (($groupHome = $this->get_group_home('Anonymous')) != '') {
319			$url = (preg_match('/^(\/|https?:)/', $groupHome)) ? $groupHome : 'tiki-index.php?page=' . $groupHome;
320		} else {
321			$url = $prefs['site_tikiIndex'];
322		}
323		// RFC 2616 defines that the 'Location' HTTP headerconsists of an absolute URI
324		if (! preg_match('/^https?\:/i', $url)) {
325			global $url_scheme, $url_host, $url_port, $base_url;
326			$url = (preg_match('#^/#', $url) ? $url_scheme . '://' . $url_host . (($url_port != '') ? ":$url_port" : '') : $base_url) . $url;
327		}
328		if (SID) {
329			$url .= '?' . SID;
330		}
331
332		if ($prefs['auth_method'] === 'cas' && $user !== 'admin' && $user !== '' && $prefs['cas_force_logout'] === 'y') {
333			phpCAS::logoutWithRedirectService($url);
334		}
335		unset($_SESSION['cas_validation_time']);
336		unset($_SESSION[$user_cookie_site]);
337		session_unset();
338		session_destroy();
339
340		if ($remote_logout) {
341			return;
342		}
343
344		if ($prefs['auth_method'] === 'ws') {
345			header('Location: ' . str_replace('//', '//admin:@', $url)); // simulate a fake login to logout the user
346		} else {
347			header('Location: ' . $url);
348		}
349
350		return;
351	}
352
353	/**
354	 * @see TikiLib::genPass()
355	 * TODO: Merge with the above
356	 */
357	static function genPass()
358	{
359		// AWC: enable mixed case and digits, don't return too short password
360		global $prefs;
361
362		$vocales = 'AaEeIiOoUu13580';
363		$consonantes = 'BbCcDdFfGgHhJjKkLlMmNnPpQqRrSsTtVvWwXxYyZz24679';
364		$r = '';
365		$passlen = ($prefs['min_pass_length'] > 5) ? $prefs['min_pass_length'] : 5;
366
367		for ($i = 0; $i < $passlen; $i++) {
368			if ($i % 2) {
369				$r .= $vocales{rand(0, strlen($vocales) - 1)};
370			} else {
371				$r .= $consonantes{rand(0, strlen($consonantes) - 1)};
372			}
373		}
374
375		return $r;
376	}
377
378	/**
379	 * Force a logout for the specified user
380	 * @param $user
381	 */
382	function force_logout($user)
383	{
384		if (! empty($user)) {
385			// Clear the timestamp for the existing session,
386			//	which will force a logout next time the user accesses the session
387			$this->query('delete from `tiki_sessions` where `user`=?', [$user]);
388
389			// Add a log entry
390			$logslib = TikiLib::lib('logs');
391			$logslib->add_log("login", "logged out", $user, '', '', $this->now);
392		}
393	}
394
395	// For each auth method, validate user in auth, if valid, verify tiki user exists and create if necessary (as configured)
396	// Once complete, update_lastlogin and return result, username and login message.
397	function validate_user($user, $pass, $validate_phase = false, $twoFactorCode = null)
398	{
399		global $prefs;
400
401		$user = str_replace(chr(0), '', $user);
402		$pass = str_replace(chr(0), '', $pass);
403
404		if ($user != 'admin' && $prefs['feature_intertiki'] == 'y' && ! empty($prefs['feature_intertiki_mymaster'])) {
405			// slave intertiki sites should never check passwords locally, just for admin
406			return false;
407		}
408
409		// these will help us keep tabs of what is going on
410		$userTiki = false;
411		$userTikiPresent = false;
412		$userLdap = false;
413		$userLdapPresent = false;
414
415		// read basic pam options
416		$auth_pam = ($prefs['auth_method'] == 'pam');
417		$pam_create_tiki = ($prefs['pam_create_user_tiki'] == 'y');
418		$pam_skip_admin = ($prefs['pam_skip_admin'] == 'y');
419
420		// read basic LDAP options
421		$auth_ldap = ($prefs['auth_method'] == 'ldap');
422		$ldap_create_tiki = ($prefs['ldap_create_user_tiki'] == 'y');
423		$create_auth = ($prefs['ldap_create_user_ldap'] == 'y');
424		$skip_admin = ($prefs['ldap_skip_admin'] == 'y');
425
426		// read basic cas options
427		$auth_cas = ($prefs['auth_method'] == 'cas');
428		$cas_create_tiki = ($prefs['cas_create_user_tiki'] == 'y');
429		$cas_skip_admin = ($prefs['cas_skip_admin'] == 'y');
430
431		// read basic phpbb options
432		$auth_phpbb = ($prefs['auth_method'] == 'phpbb');
433		$phpbb_create_tiki = ($prefs['auth_phpbb_create_tiki'] == 'y');
434		$phpbb_skip_admin = ($prefs['auth_phpbb_skip_admin'] == 'y');
435		$phpbb_disable_tikionly = ($prefs['auth_phpbb_disable_tikionly'] == 'y');
436
437		// see if we are to use Shibboleth
438		$auth_shib = ($prefs['auth_method'] == 'shib');
439		$shib_create_tiki = ($prefs['shib_create_user_tiki'] == 'y');
440		$shib_skip_admin = ($prefs['shib_skip_admin'] == 'y');
441
442		// see if we are to use SAML
443		$auth_saml = ($prefs['auth_method'] == 'saml');
444		 $saml_create_tiki = (isset($prefs['saml_options_autocreates']) && $prefs['saml_options_autocreates'] == 'y');
445		 $saml_skip_admin = (isset($prefs['saml_options_skip_admin']) && $prefs['saml_options_skip_admin'] == 'y');
446
447		// first attempt a login via the standard Tiki system
448		//
449		if (! ($auth_shib || $auth_cas || $auth_saml) || $user == 'admin') { //redflo: does this mean, that users in cas and shib are not replicated to tiki tables? Does this work well?
450			list($result, $user) = $this->validate_user_tiki($user, $pass, $validate_phase);
451		} else {
452			$result = null;
453		}
454
455		// If preference login_multiple_forbidden is set, don't let user login if already logged in
456		if ($result == USER_VALID && $prefs['login_multiple_forbidden'] == 'y' && $user != 'admin') {
457			$tikilib = TikiLib::lib('tiki');
458			$grabSessionOnAlreadyLoggedIn = ! empty($prefs['login_grab_session']) ? $prefs['login_grab_session'] : 'n';
459			if ($grabSessionOnAlreadyLoggedIn === 'y') {
460				// Log out first, then proceed to log in again
461				$this->force_logout($user);
462			} else {
463				$tikilib->update_session();
464				if ($tikilib->is_user_online($user)) {
465					$result = USER_ALREADY_LOGGED;
466				}
467			}
468		}
469
470		switch ($result) {
471			case USER_VALID:
472				$userTiki = true;
473				$userTikiPresent = true;
474				break;
475
476			case USER_ALREADY_LOGGED:
477				$userTikiPresent = true;
478				break;
479
480			case PASSWORD_INCORRECT:
481				$userTikiPresent = true;
482				break;
483		}
484
485		// if we aren't using LDAP this will be quick
486		// if we are using tiki auth or if we're using an alternative auth except for admin
487
488		// todo: bad hack. better search for a more general solution here
489		if ((! $auth_ldap && ! $auth_pam && ! $auth_cas && ! $auth_shib&& ! $auth_saml && ! $auth_phpbb)
490				|| (
491						( ($auth_ldap && $skip_admin)
492							|| ($auth_shib && $shib_skip_admin)
493							|| ($auth_saml && $saml_skip_admin)
494							|| ($auth_pam && $pam_skip_admin)
495							|| ($auth_cas && $cas_skip_admin)
496							|| ($auth_phpbb && $phpbb_skip_admin)
497						)
498						&& $user == 'admin')
499				|| ($auth_ldap && ($prefs['auth_ldap_permit_tiki_users'] == 'y' && $userTiki))
500			) {
501			// if the user verified ok, log them in
502			if ($userTiki) {//user validated in tiki, update lastlogin and be done
503				if ($auth_ldap) {
504					return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result, 'tiki'];
505				}
506				return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result];
507			// if the user password was incorrect but the account was there, give an error
508			} elseif ($userTikiPresent) { //user ixists in tiki but bad password
509				return [false, $user, $result];
510			} // if the user was not found, give an error
511			// this could be for future uses
512			else {
513				return [false, $user, $result];
514			}
515
516		// For the alternate auth methods, attempt to validate user
517		// return back one of two conditions
518		// Valid User or Bad password
519		// next see if we need to check PAM
520		} elseif ($auth_pam) {
521			$result = $this->validate_user_pam($user, $pass);
522			switch ($result) {
523				case USER_VALID:
524					$userPAM = true;
525					break;
526
527				case PASSWORD_INCORRECT:
528					$userPAM = false;
529					break;
530			}
531
532			// start off easy
533			// if the user verified in Tiki and PAM, log in
534			if ($userPAM && $userTiki) {
535				return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result];
536			} elseif (! $userTikiPresent && ! $userPAM) { // if the user wasn't found in either system, just fail
537				return [false, $user, $result];
538			} elseif ($userPAM && ! $userTikiPresent) {	// if the user was logged into PAM but not found in Tiki
539				// see if we can create a new account
540				if ($pam_create_tiki) {
541					// need to make this better! *********************************************************
542					$result = $this->add_user($user, $pass, '');
543
544					// if it worked ok, just log in
545					if ($result == USER_VALID) {
546						// before we log in, update the login counter
547						return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result];
548					// if the server didn't work, do something!
549					} elseif ($result == SERVER_ERROR) {
550						// check the notification status for this type of error
551						return [false, $user, $result];
552					} else {
553					// otherwise don't log in.
554						return [false, $user, $result];
555					}
556				} else {
557				// otherwise
558					// just say no!
559					return [false, $user, $result];
560				}
561			} // if the user was logged into PAM and found in Tiki (no password in Tiki user table necessary)
562			elseif ($userPAM && $userTikiPresent) {
563				return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result];
564			}
565		} elseif ($auth_cas) {
566		// next see if we need to check CAS
567			$result = $this->validate_user_cas($user);
568
569			switch ($result) {
570				case USER_VALID:
571					$userCAS = true;
572					break;
573
574				case PASSWORD_INCORRECT:
575					$userCAS = false;
576					break;
577			}
578
579			if ($this->user_exists($user)) {
580				$userTikiPresent = true;
581			} else {
582				$userTikiPresent = false;
583			}
584
585			// start off easy
586			// if the user verified in Tiki and by CAS, log in
587			if ($userCAS && $userTikiPresent) {
588				return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result];
589			} elseif (! $userCAS) {
590				// if the user wasn't authenticated through CAS, just fail
591				return [false, $user, $result];
592			} elseif ($userCAS && ! $userTikiPresent) {
593			// if the user was authenticated by CAS but not found in Tiki
594
595				// see if we can create a new account
596				if ($cas_create_tiki) {
597					// need to make this better! *********************************************************
598					$randompass = $this->genPass();
599					// in case CAS auth is turned off accidentally;
600					// we don't want ppl to be able to login as any user with blank passwords
601					$result = $this->add_user($user, $randompass, '');
602
603					// if it worked ok, just log in
604					if ($result == USER_VALID) {
605						// before we log in, update the login counter
606						return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result];
607					// if the server didn't work, do something!
608					} elseif ($result == SERVER_ERROR) {
609						// check the notification status for this type of error
610						return [false, $user, $result];
611					} else {
612					// otherwise don't log in.
613						return [false, $user, $result];
614					}
615				} else {
616				// otherwise
617					// just say no!
618					return [false, $user, $result];
619				}
620			} // if the user was authenticated by CAS and found in Tiki (no password in Tiki user table necessary)
621			elseif ($userCAS && $userTikiPresent) {
622				return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result];
623			}
624		} elseif ($auth_shib) {
625			// next see if we need to check Shibboleth
626
627			if ($this->user_exists($user)) {
628				$userTikiPresent = true;
629			} else {
630				$userTikiPresent = false;
631			}
632
633			// Shibboleth login was not successful
634			if (! isset($_SERVER['HTTP_SHIB_IDENTITY_PROVIDER'])) {
635				return false;
636			}
637
638			// Collect the shibboleth related attributes.
639			$shibmail = $_SERVER['HTTP_MAIL'];
640			$shibaffiliation = $_SERVER['HTTP_SHIB_EP_UNSCOPEDAFFILIATION'];
641			$shibproviderid = $_SERVER['HTTP_SHIB_IDENTITY_PROVIDER'];
642
643			// Get the affiliation information to log in
644			$shibaffiliarray = preg_split('/;/', TikiLib::strtoupper($shibaffiliation));
645			$validaffiliarray = preg_split('/,/', TikiLib::strtoupper($prefs['shib_affiliation']));
646			$validafil = false;
647
648			foreach ($shibaffiliarray as $affil) {
649				if (in_array($affil, $validaffiliarray)) {
650					$validafil = true;
651				}
652			}
653
654			// start off easy
655			// if the user verified in Tiki and by Shibboleth, log in
656			if ($userTikiPresent && $validafil) {
657				return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, USER_VALID];
658			} else {
659				$smarty = TikiLib::lib('smarty');
660				// see if we can create a new account
661				if ($shib_create_tiki) {
662					if (! (strlen($user) > 0 and strlen($shibmail) > 0 and strlen($shibaffiliation) > 0)) {
663						$errmsg = 'User registration error: You do not have the required shibboleth attributes (';
664
665						if (strlen($user) == 0) {
666							$errmsg = $errmsg . 'User ';
667						}
668
669						if (strlen($shibmail) == 0) {
670							$errmsg = $errmsg . 'Mail ';
671						}
672
673						if (strlen($shibaffiliation) == 0) {
674							$errmsg = $errmsg . 'Affiliation ';
675						}
676
677						$errmsg = $errmsg . '). For further information on this error goto the ((ShibReg)) Page';
678
679						$smarty->assign('msg', $errmsg);
680						$smarty->display('error.tpl');
681						exit;
682					} else {
683						if ($validafil) {
684							// Create the user
685							// need to make this better! *********************************************************
686							$randompass = $this->genPass();
687							// in case Shibboleth auth is turned off accidentally;
688							// we don't want ppl to be able to login as any user with blank passwords
689
690							$result = $this->add_user($user, $randompass, $shibmail);
691
692							// if it worked ok, just log in
693							if ($result == USER_VALID) {
694								// Add to the default Group
695								if ($prefs['shib_usegroup'] == 'y') {
696									$result = $this->assign_user_to_group($user, $prefs['shib_group']);
697								}
698
699								// before we log in, update the login counter
700								return [$this->_ldap_sync_and_update_lastlogin($user, $randompass), $user, $result];
701							} elseif ($result == SERVER_ERROR) {
702							// if the server didn't work, do something!
703
704								// check the notification status for this type of error
705								return [false, $user, $result];
706							} else {
707							// otherwise don't log in.
708								return [false, $user, $result];
709							}
710						} else {
711							$vaffils = '';
712							foreach ($validaffiliarray as $vaffil) {
713								$vaffils = $vaffils . $vaffil . ", ";
714							}
715							$vaffils = rtrim($vaffils, ", ");
716							$errmsg = '<H1 style="text-align: center;">User login error</H1>' .
717												'<BR/><BR/>You must have one of the following affiliations to get into this wiki.<BR/><BR/>' .
718												'<B>' . $vaffils . '</B><BR><BR/><BR/>' .
719												'For further information on this error goto the <a href="./tiki-index.php?page=ShibReg">Shibreg</a> Page';
720
721							$smarty->assign('msg', $errmsg);
722							$smarty->display('error.tpl');
723							exit;
724						}
725					}
726				} else {
727					$smarty->assign('msg', 'The user [ ' . $user . ' ] is not registered with this wiki.');
728					$smarty->display('error.tpl');
729					exit;
730				}
731			}
732		} elseif ($auth_saml) {
733			// next see if we need to check SAML
734			if (isset($_SESSION['samlUserdata']) && ! empty($_SESSION['samlUserdata']) ||
735				isset($_SESSION['samlNameId']) && ! empty($_SESSION['samlNameId'])
736				) {
737				$saml_username = $saml_email = '';
738				$saml_groups = [];
739
740				if (empty($_SESSION['samlUserdata'])) {
741					$saml_username = $_SESSION['samlNameId'];
742					$saml_email = $saml_username;
743				} else {
744					$usernameMapping = isset($prefs['saml_attrmap_username']) ? $prefs['saml_attrmap_username'] : '';
745					$emailMapping = isset($prefs['saml_attrmap_mail']) ? $prefs['saml_attrmap_mail'] : '';
746					$groupMapping = isset($prefs['saml_attrmap_group']) ? $prefs['saml_attrmap_group'] : '';
747
748					if (! empty($usernameMapping) && isset($_SESSION['samlUserdata'][$usernameMapping]) && ! empty($_SESSION['samlUserdata'][$usernameMapping][0])) {
749						$saml_username = $_SESSION['samlUserdata'][$usernameMapping][0];
750					}
751
752					if (! empty($emailMapping) && isset($_SESSION['samlUserdata'][$emailMapping]) && ! empty($_SESSION['samlUserdata'][$usernameMapping][0])) {
753						$saml_email = $_SESSION['samlUserdata'][$emailMapping][0];
754					}
755
756					if (! empty($groupMapping) && isset($_SESSION['samlUserdata'][$groupMapping]) && ! empty($_SESSION['samlUserdata'][$groupMapping])) {
757						$group_values = $_SESSION['samlUserdata'][$groupMapping];
758
759						foreach ($group_values as $group_value) {
760							if (isset($prefs['saml_groupmap_admins']) && ! empty($prefs['saml_groupmap_admins'])) {
761								if (strcasecmp($prefs['saml_groupmap_admins'], $group_value) == 0) {
762									$saml_groups[] = "Admins";
763								}
764							}
765							if (isset($prefs['saml_groupmap_registered']) && ! empty($prefs['saml_groupmap_registered'])) {
766								if (strcasecmp($prefs['saml_groupmap_registered'], $group_value) == 0) {
767									$saml_groups[] = "Registered";
768								}
769							}
770						}
771					}
772
773					// Code SAML Custom role here
774				}
775
776				$matcher = isset($prefs['saml_option_account_matcher']) ? $prefs['saml_option_account_matcher'] : 'email';
777
778				if ($matcher == 'email') {
779					if (empty($saml_email)) {
780						Feedback::error(tra("The email could not be retrieved from the IdP and is required"));
781						return [false, $username, SERVER_ERROR];
782					} else {
783						$username = $this->get_user_by_email($saml_email);
784						if ($this->user_exists($username)) {
785							$userTikiPresent = true;
786						} else {
787							$userTikiPresent = false;
788							if (! isset($prefs['saml_options_autocreate']) || $prefs['saml_options_autocreate'] != 'y') {
789								Feedback::error(tr('The user [ %0 ] is not registered with this wiki and autocreate is disabled.', $saml_email));
790								return [false, $username, USER_NOT_FOUND];
791							}
792						}
793					}
794				} else {
795					if (empty($saml_username)) {
796						Feedback::error(tra("The username could not be retrieved from the IdP and is required"));
797						return [false, $username, SERVER_ERROR];
798					} else {
799						$username = $saml_username;
800						if ($this->user_exists($saml_username)) {
801							$userTikiPresent = true;
802						} else {
803							$userTikiPresent = false;
804
805							if (! isset($prefs['saml_options_autocreate']) || $prefs['saml_options_autocreate'] != 'y') {
806								Feedback::error(tr('The user [ %0 ] is not registered with this wiki and autocreate is disabled.', $saml_username));
807								return [false, $username, USER_NOT_FOUND];
808							}
809						}
810					}
811				}
812
813				if (empty($username)) {
814					$username = $saml_username;
815				}
816
817				$cookie_site = preg_replace("/[^a-zA-Z0-9]/", "", $prefs['cookie_name']);
818				$user_cookie_site = 'tiki-user-' . $cookie_site;
819				$_SESSION["$user_cookie_site"] = $username;
820
821				$randompass = $this->genPass();
822				if (! $userTikiPresent) {
823					// Create user
824					if (empty($saml_groups)) {
825						if (isset($prefs['saml_option_default_group']) && ! empty($prefs['saml_option_default_group'])) {
826							$saml_groups[] = $prefs['saml_option_default_group'];
827						}
828					}
829
830					$result = $this->add_user($username, $randompass, $saml_email, '', false, null, null, null, $saml_groups);
831
832					if (! $result) {
833						Feedback::error(tr('The user [ %0|%1 ] is not registered with this wiki and the creation process failed.', $username, $saml_email));
834						return [false, $username, SERVER_ERROR];
835					}
836
837					// if it worked ok, just log in
838					if ($result == USER_VALID) {
839						// before we log in, update the login counter
840						return [$this->update_lastlogin($username), $username, $result];
841					} elseif ($result == SERVER_ERROR) {
842						// check the notification status for this type of error
843						return [false, $username, $result];
844					} else {
845						// otherwise don't log in.
846						return [false, $username, $result];
847					}
848				} else {
849					// Update user
850					if ($username != 'admin') { // Prevent change groups of the admin account
851						if (isset($prefs['saml_options_sync_group']) && $prefs['saml_options_sync_group'] == 'y') {
852							if (! empty($saml_groups)) {
853								$this->assign_user_to_groups($username, $saml_groups);
854							}
855						}
856					}
857					return [$this->update_lastlogin($username), $username, USER_VALID];
858				}
859			}
860		} elseif ($auth_ldap) {
861			// next see if we need to check LDAP
862			// check the user account
863			$result = $this->validate_user_ldap($user, $pass);
864
865			switch ($result) {
866				case USER_VALID:
867					$userLdap = true;
868					$userLdapPresent = true;
869					break;
870
871				case PASSWORD_INCORRECT:
872					$userLdapPresent = true;
873					break;
874			}
875
876			// start off easy
877			// if the user is in Tiki and password is verified in LDAP, log in
878			if ($userLdap && $userTikiPresent) {
879				if ($userLdapPresent) {
880					# Sync again user attributes from LDAP (such as the RealName, mail and country) with user data in Tiki to prevent un-sync'ing them in a later stage
881					$this->init_ldap($user, $pass);
882					$this->ldap_sync_user_data($user, $this->ldap->get_user_attributes());
883				}
884				return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result];
885			} elseif (! $userTikiPresent && ! $userLdapPresent) {
886			// if the user wasn't found in either system, just fail
887
888				return [false, $user, $result];
889			} elseif ($userTiki && ! $userLdapPresent) {
890			// if the user was logged into Tiki but not found in LDAP
891
892				// see if we can create a new account
893				if ($create_auth) {
894					// need to make this better! *********************************************************
895					$result = $this->create_user_ldap($user, $pass);
896
897					// if it worked ok, just log in
898					if ($result == USER_VALID) {
899						// before we log in, update the login counter
900						return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result];
901					} // if the server didn't work, do something!
902					elseif ($result == SERVER_ERROR) {
903						// check the notification status for this type of error
904						return [false, $user, $result];
905					} // otherwise don't log in.
906					else {
907						return [false, $user, $result];
908					}
909				} else {
910					// otherwise
911					// just say no!
912					return [false, $user, $result];
913				}
914			} elseif ($userLdap && ! $userTikiPresent) {
915				// if the user was logged into Auth but not found in Tiki
916				// see if we are allowed to create a new account
917				if ($ldap_create_tiki) {
918					$ldap_user_attr = $this->ldap->get_user_attributes();
919					// Get user attributes such as the real name, email and country from the data received by the ldap auth
920					$this->ldap_sync_user_data($user, $ldap_user_attr);
921					// Use what was configured in ldap admin config, otherwise assume the attribute name is "mail" as is usual
922					$email = $ldap_user_attr[empty($prefs['auth_ldap_emailattr']) ? 'mail' : $prefs['auth_ldap_emailattr']];
923					$result = $this->add_user($user, $pass, $email);
924					$this->disable_tiki_auth($user); //disable that user's password in tiki - since we use ldap
925
926					// if it worked ok, just log in
927					if ($result == $user) {
928						// before we log in, update the login counter
929						return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result];
930					} elseif ($result == SERVER_ERROR) {
931					// if the server didn't work, do something!
932						// check the notification status for this type of error
933						return [false, $user, $result];
934					} else { 					// otherwise don't log in.
935						return [false, $user, $result];
936					}
937				} else { 				// otherwise
938					// just say no!
939					return [false, $user, $result];
940				}
941			} // if the user was logged into Auth and found in Tiki (no password in Tiki user table necessary)
942			elseif ($userLdap && $userTikiPresent) {
943				return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result];
944			}
945		} elseif ($auth_phpbb) {
946			$result = $this->validate_user_phpbb($user, $pass);
947
948			switch ($result) {
949				case USER_VALID:
950					$userPhpbb = true;
951					break;
952
953				case PASSWORD_INCORRECT:
954					$userPhpbb = false;
955					break;
956			}
957
958			// start off easy
959			// if the user verified in Tiki and phpBB, log in
960			if ($userPhpbb && $userTiki) {
961				return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result];
962			} elseif (! $userTikiPresent && ! $userPhpbb) {
963			// if the user wasn't found in either system, just fail
964				return [false, $user, USER_UNKNOWN];
965			} elseif ($userPhpbb && ! $userTikiPresent) {
966			// if the user was logged into phpBB but not found in Tiki
967
968				// see if we can create a new account
969				if ($phpbb_create_tiki) {
970					// get the user email and then add the user to Tiki
971					$the_email = $this->phpbbauth->grabEmail($user);
972					$result = $this->add_user($user, $pass, $the_email);
973
974					// if it worked ok, just log in
975					if ($result == USER_VALID) {
976						// before we log in, update the login counter
977						return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result];
978					} // if the server didn't work, do something!
979					elseif ($result == SERVER_ERROR) {
980						// check the notification status for this type of error
981						return [false, $user, $result];
982					} // otherwise don't log in.
983					else {
984						return [false, $user, $result];
985					}
986				} else { 				// otherwise
987					// just say no!
988					return [false, $user, $result];
989				}
990			} elseif ($userTikiPresent && ! $userPhpbb) {
991			// if the user was found in Tiki, but not found in phpBB, we should probably disable the user
992				if ($phpbb_disable_tikionly) {
993					// would probably be better do flag the user as not active? How do you do that?
994					// and it also would be better to check if the user is active first.. :)
995					$this->invalidate_account($user);
996					$logslib = TikiLib::lib('logs');
997					$logslib->add_log('auth_phpbb', 'NOTICE: Invalidated user ' . $user . ' due to missing phpBB account.');
998				}
999				return [false, $user, ACCOUNT_DISABLED];
1000			} // if the user was logged into phpBB and found in Tiki (no password in Tiki user table necessary)
1001			elseif ($userPhpbb && $userTikiPresent) {
1002				return [$this->_ldap_sync_and_update_lastlogin($user, $pass), $user, $result];
1003			}
1004		}
1005
1006		// we will never get here
1007		return [false, $user, $result];
1008	}
1009
1010	// validate the user through PAM
1011	function validate_user_pam($user, $pass)
1012	{
1013		global $prefs;
1014		$tikilib = TikiLib::lib('tiki');
1015
1016		// just make sure we're supposed to be here
1017		if ($prefs['auth_method'] != 'pam') {
1018			return false;
1019		}
1020
1021		// Read page AuthPAM at tw.o, it says about a php module required.
1022		// maybe and if extension line could be added here... module requires $error
1023		// as reference.
1024		$error = '';
1025		if (pam_auth($user, $pass, $error)) {
1026			return USER_VALID;
1027		} else {
1028			// Uncomment the following to see errors on that
1029			// error_log("TIKI ERROR PAM: $error User: $user Pass: $pass");
1030			return PASSWORD_INCORRECT;
1031		}
1032	}
1033
1034	function check_cas_authentication($user_cookie_site)
1035	{
1036		global $prefs, $webdav_access;
1037		$tikilib = TikiLib::lib('tiki');
1038
1039		// Avoid CAS authentication check if the client is not able to handle HTTP redirects to another domain. This includes:
1040		//  - WebDAV requests
1041		//  - Javascript/AJAX requests
1042		//
1043		if (( isset($webdav_access) && $webdav_access === true )
1044			|| ( isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' )
1045		) {
1046			return true;
1047		}
1048
1049		// just make sure we're supposed to be here
1050		if (! $this->_init_cas_client()) {
1051			return false;
1052		}
1053
1054		if (! empty($_SESSION['phpCAS']['user'])) {
1055			$_SESSION[$user_cookie_site] = strtolower($_SESSION['phpCAS']['user']);
1056		}
1057
1058		if (isset($_REQUEST['ticket']) && empty($_SESSION[$user_cookie_site])) {
1059			$cas_user = '';
1060			$_SESSION['cas_is_validating'] = false;
1061			$this->validate_user_cas($cas_user, true);
1062			die();
1063		}
1064
1065		// Check for CAS (re-)validation
1066		//  Only if :
1067		//   - using CAS auth method
1068		//   - not calling tiki-login.php nor tiki-logout.php
1069		//   - not using 'admin' user
1070		//   - the request is not a POST ( which does not keep its params with CAS redirections )
1071		//   - either the CAS validation timed out or the validation process has not ended within 5 seconds which often means that the redirection to the CAS server failed
1072		//
1073		if (php_sapi_name() !== 'cli'
1074			&& (isset($_SESSION[$user_cookie_site]) || $prefs['cas_autologin'] == 'y')
1075			&& basename($_SERVER['SCRIPT_NAME']) != 'tiki-login.php'
1076			&& basename($_SERVER['SCRIPT_NAME']) != 'tiki-logout.php'
1077			&& (! isset($_SESSION[$user_cookie_site]) || $_SESSION[$user_cookie_site] != 'admin' )
1078			&& empty($_POST)
1079			&& ( ( $prefs['cas_authentication_timeout'] && $tikilib->now - $_SESSION['cas_validation_time'] > $prefs['cas_authentication_timeout'] )
1080				|| ( isset($_SESSION['cas_is_validating']) && $_SESSION['cas_is_validating'] === true && $tikilib->now - $_SESSION['cas_validation_time'] > 5 ) )
1081		) {
1082			unset($_SESSION["$user_cookie_site"]);
1083			unset($_SESSION['phpCAS']['user']);
1084
1085			$_SESSION['cas_validation_time'] = $tikilib->now;
1086			$_SESSION['cas_is_validating'] = true;
1087			$cas_user = '';
1088
1089			// phpCAS will always redirect to CAS validate URL
1090			$this->validate_user_cas($cas_user, true);
1091
1092			die();
1093		}
1094	}
1095
1096	function _init_cas_client()
1097	{
1098		global $prefs;
1099
1100		// just make sure we're supposed to be here
1101		if ($prefs['auth_method'] != 'cas') {
1102			return false;
1103		}
1104		if (self::$cas_initialized === false) {
1105			// initialize phpCAS
1106			phpCAS::client($prefs['cas_version'], '' . $prefs['cas_hostname'], (int) $prefs['cas_port'], '' . $prefs['cas_path'], false);
1107			self::$cas_initialized = true;
1108		}
1109
1110		return true;
1111	}
1112
1113	// validate the user through CAS
1114	function validate_user_cas(&$user, $checkOnly = false)
1115	{
1116		global $prefs, $base_url;
1117		$tikilib = TikiLib::lib('tiki');
1118
1119		// just make sure we're supposed to be here
1120		if (! $this->_init_cas_client()) {
1121			return false;
1122		}
1123
1124		// Redirect to this URL after authentication
1125		if (! empty($prefs['cas_extra_param']) && basename($_SERVER['SCRIPT_NAME']) == 'tiki-login.php') {
1126			phpCAS::setFixedServiceURL($base_url . 'tiki-login.php?cas=y&' . $prefs['cas_extra_param']);
1127		}
1128
1129		// check CAS authentication
1130		phpCAS::setNoCasServerValidation();
1131		if ($checkOnly) {
1132			unset($_SESSION['phpCAS']['auth_checked']);
1133			$auth = phpCAS::checkAuthentication();
1134		} else {
1135			$auth = phpCAS::forceAuthentication();
1136		}
1137		$_SESSION['cas_validation_time'] = $tikilib->now;
1138
1139		// at this step, the user has been authenticated by the CAS server
1140		// and the user's login name can be read with phpCAS::getUser().
1141		if ($auth && ($user = strtolower(phpCAS::getUser()))) {
1142			return USER_VALID;
1143		} else {
1144			$user = null;
1145			return PASSWORD_INCORRECT;
1146		}
1147	}
1148
1149	/**
1150	 * Get php-saml auth object
1151	 */
1152	function check_saml_authentication($user_cookie_site)
1153	{
1154		global $prefs, $base_url;
1155
1156		if ($prefs['auth_method'] != 'saml'||! class_exists('\OneLogin\Saml2\Auth')) {
1157			return;
1158		}
1159
1160		$clicked_on_saml_link = false;
1161
1162		// Check endpoints
1163		if (array_key_exists('auth', $_REQUEST) && $_REQUEST['auth'] == 'saml') {
1164			$saml_instance = $this->get_saml_auth();
1165			$saml_instance->login();
1166		} elseif (array_key_exists('saml_metadata', $_REQUEST)) {
1167			$samlSettingsInfo = $this->get_saml_settings();
1168			$saml_settings = new OneLogin_Saml2_Settings($samlSettingsInfo, true);
1169			$metadata = $saml_settings->getSPMetadata();
1170			$errors = $saml_settings->validateMetadata($metadata);
1171			if (empty($errors)) {
1172				header('Content-Type: text/xml');
1173				echo $metadata;
1174				exit();
1175			} else {
1176				throw new OneLogin_Saml2_Error(
1177					'Invalid SP metadata: ' . implode(', ', $errors),
1178					OneLogin_Saml2_Error::METADATA_SP_INVALID
1179				);
1180			}
1181		} elseif (array_key_exists('saml_acs', $_REQUEST)) {
1182			$clicked_on_saml_link = true;
1183			$saml_instance = $this->get_saml_auth();
1184			try {
1185				$saml_instance->processResponse();
1186			} catch (Exception $e) {
1187				Feedback::error($e->getMessage());
1188				return;
1189			}
1190			$errors = $saml_instance->getErrors();
1191			if (! empty($errors)) {
1192				Feedback::error(implode(', ', $errors));
1193				return;
1194			}
1195			if (! $saml_instance->isAuthenticated()) {
1196				Feedback::error(tra("SAML Login failed. User not authenticated"));
1197				return;
1198			}
1199
1200			$_SESSION['samlUserdata'] = $saml_instance->getAttributes();
1201			$_SESSION['samlNameId'] = $saml_instance->getNameId();
1202			$_SESSION['samlSessionIndex'] = $saml_instance->getSessionIndex();
1203/*
1204			if (isset($_POST['RelayState']) && OneLogin_Saml2_Utils::getSelfURL() != $_POST['RelayState']) {
1205				$saml_instance->redirectTo($_POST['RelayState']);
1206			}
1207*/
1208		} elseif (array_key_exists('saml_sls', $_REQUEST)) {
1209			$saml_instance = $this->get_saml_auth();
1210
1211			try {
1212				$saml_instance->processSLO(false);
1213			} catch (Exception $e) {
1214				Feedback::error($e->getMessage());
1215				return;
1216			}
1217			$errors = $saml_instance->getErrors();
1218			if (! empty($errors)) {
1219				Feedback::error(implode(', ', $errors));
1220				return;
1221			} else {
1222				unset($_SESSION['samlUserdata']);
1223				unset($_SESSION['samlNameId']);
1224				unset($_SESSION['samlSessionIndex']);
1225			}
1226		}
1227
1228		$already_logged_as_admin = isset($_SESSION["$user_cookie_site"]) && $_SESSION["$user_cookie_site"] == 'admin';
1229		$force_saml_login = ! (isset($prefs['saml_options_skip_admin']) && $prefs['saml_options_skip_admin'] == 'y');
1230
1231		if ($clicked_on_saml_link || ($force_saml_login && ! $already_logged_as_admin)) {
1232			$this->validate_user("", "", "", "");
1233		}
1234	}
1235
1236	/**
1237	 * Get php-saml auth object
1238	 */
1239	function get_saml_auth()
1240	{
1241		$samlSettingsInfo = $this->get_saml_settings();
1242
1243		if (! class_exists('\OneLogin\Saml2\Auth')) {
1244			return;
1245		}
1246
1247		try {
1248			$auth = new Saml2\Auth($samlSettingsInfo);
1249		} catch (Exception $e) {
1250			print_r("There is a problem with the SAML settings, review them: " . $e->getMessage());
1251			exit();
1252		}
1253
1254		return $auth;
1255	}
1256
1257	/**
1258	 * Build a settingsInfo array based on SAML settings store at Tiki
1259	 */
1260	function get_saml_settings()
1261	{
1262		global $prefs;
1263		global $base_url;
1264
1265		$samlSettingsInfo = [
1266			'strict' => isset($prefs['saml_advanced_strict']) && $prefs['saml_advanced_strict'] == 'y' ? true : false,
1267			'debug' => isset($prefs['saml_advanced_debug']) && $prefs['saml_advanced_debug'] == 'y' ? true : false,
1268			'sp' => [
1269				'entityId' => (! empty($prefs['saml_advanced_sp_entity_id']) ? $prefs['saml_advanced_sp_entity_id'] : 'php-saml'),
1270				'assertionConsumerService' => [
1271					'url' => $base_url . 'tiki-login.php?saml_acs'
1272				],
1273				'singleLogoutService' => [
1274					'url' => $base_url . 'tiki-login.php?saml_sls'
1275				],
1276				'NameIDFormat' => (! empty($prefs['saml_advanced_nameidformat']) ? $prefs['saml_advanced_nameidformat'] : 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'),
1277				'x509cert' => isset($prefs['saml_advanced_sp_x509cert']) ? $prefs['saml_advanced_sp_x509cert'] : '',
1278				'privateKey' => isset($prefs['saml_advanced_sp_privatekey']) ? $prefs['saml_advanced_sp_privatekey'] : '',
1279			],
1280			'idp' => [
1281				'entityId' => isset($prefs['saml_idp_entityid']) ? $prefs['saml_idp_entityid'] : '',
1282				'singleSignOnService' => [
1283					'url' => isset($prefs['saml_idp_sso']) ? $prefs['saml_idp_sso'] : '',
1284				],
1285				'singleLogoutService' => [
1286					'url' => isset($prefs['saml_idp_slo']) ? $prefs['saml_idp_slo'] : '',
1287				],
1288				'x509cert' => isset($prefs['saml_idp_x509cert']) ? $prefs['saml_idp_x509cert'] : '',
1289				'lowercaseUrlencoding' => isset($prefs['saml_advanced_idp_lowercase_url_encoding']) && $prefs['saml_advanced_idp_lowercase_url_encoding'] == 'y' ? true : false,
1290			],
1291			'security' => [
1292				'signMetadata' => isset($prefs['saml_advanced_metadata_signed']) && $prefs['saml_advanced_metadata_signed'] == 'y' ? true : false,
1293				'nameIdEncrypted' => isset($prefs['saml_advanced_nameid_encrypted']) && $prefs['saml_advanced_nameid_encrypted'] == 'y' ? true : false,
1294				'authnRequestsSigned' => isset($prefs['saml_advanced_authn_request_signed']) && $prefs['saml_advanced_authn_request_signed'] == 'y' ? true : false,
1295				'logoutRequestSigned' => isset($prefs['saml_advanced_logout_request_signed']) && $prefs['saml_advanced_logout_request_signed'] == 'y' ? true : false,
1296				'logoutResponseSigned' => isset($prefs['saml_advanced_logout_response_signed']) && $prefs['saml_advanced_logout_response_signed'] == 'y' ? true : false,
1297				'wantMessagesSigned' => isset($prefs['saml_advanced_want_message_signed']) && $prefs['saml_advanced_want_message_signed'] == 'y' ? true : false,
1298				'wantAssertionsSigned' => isset($prefs['saml_advanced_want_assertion_signed']) && $prefs['saml_advanced_want_assertion_signed'] == 'y' ? true : false,
1299				'wantAssertionsEncrypted' => isset($prefs['saml_advanced_want_assertion_encrypted']) && $prefs['saml_advanced_want_assertion_encrypted'] == 'y' ? true : false,
1300				'requestedAuthnContext' => isset($prefs['saml_advanced_requestedauthncontext']) && $prefs['saml_advanced_requestedauthncontext'],
1301				'signatureAlgorithm' => isset($prefs['saml_advanced_sign_algorithm']) ? $prefs['saml_advanced_sign_algorithm'] : 'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
1302			]
1303		];
1304		return $samlSettingsInfo;
1305	}
1306
1307
1308	/**
1309	 * Initiates the Tiki LDAP library.
1310	 *
1311	 * Passes it a set of options according to Tiki's preferences.
1312	 * FIXME: a similar piece of code can be found at two other places in this file.
1313	 */
1314	function init_ldap($user, $pass)
1315	{
1316		global $prefs;
1317		if (! isset($this->ldap)) {
1318			require_once('auth/ldap.php');
1319			$ldap_options = [
1320					'host' => $prefs['auth_ldap_host'],
1321					'port' => $prefs['auth_ldap_port'],
1322					'useStartTls' => $prefs['auth_ldap_starttls'],
1323					'useSsl' => $prefs['auth_ldap_ssl'],
1324					'baseDn' => $prefs['auth_ldap_basedn'],
1325					'scope' => $prefs['auth_ldap_scope'],
1326					'bind_type' => $prefs['auth_ldap_type'],
1327					'username' => $user,
1328					'password' => $pass,
1329					'userdn' => $prefs['auth_ldap_userdn'],
1330					'useroc' => $prefs['auth_ldap_useroc'],
1331					'userattr' => $prefs['auth_ldap_userattr'],
1332					'fullnameattr' => $prefs['auth_ldap_nameattr'],
1333					'emailattr' => $prefs['auth_ldap_emailattr'],
1334					'countryattr' => $prefs['auth_ldap_countryattr'],
1335					'groupdn' => $prefs['auth_ldap_groupdn'],
1336					'groupattr' => $prefs['auth_ldap_groupattr'],
1337					'groupoc' => $prefs['auth_ldap_groupoc'],
1338					'groupnameattr' => $prefs['auth_ldap_groupnameatr'],
1339					'groupdescattr' => $prefs['auth_ldap_groupdescatr'],
1340					'groupmemberattr' => $prefs['auth_ldap_memberattr'],
1341					'groupmemberisdn' => $prefs['auth_ldap_memberisdn'],
1342					'usergroupattr' => $prefs['auth_ldap_usergroupattr'],
1343					'groupgroupattr' => $prefs['auth_ldap_groupgroupattr'],
1344					'debug' => $prefs['auth_ldap_debug']
1345			];
1346			// print_r($ldap_options);
1347			$this->ldap = new TikiLdapLib($ldap_options);
1348		}
1349	}
1350
1351	/**
1352	 * Validates the user via LDAP and gets a LDAP connection
1353	 *
1354	 * @param user: username
1355	 * @param pass: password
1356	 */
1357	function validate_user_ldap($user, $pass)
1358	{
1359		if (! $pass) { // An LDAP password cannot be empty. Treat specially so that Tiki does *NOT* unintentionally request an unauthenticated bind.
1360			return PASSWORD_INCORRECT;
1361		}
1362
1363		global $prefs;
1364		$logslib = TikiLib::lib('logs');
1365
1366		if ($prefs['auth_ldap_debug'] == 'y') {
1367			$logslib->add_log('ldap', 'UserLib::validate_user_ldap()');
1368		}
1369
1370		// First connection on the ldap server in anonymous, now we can search the real name of the $user
1371		// It's required to pass in param the username & password because the username is used to determine the realname (dn)
1372		$this->init_ldap($user, $pass);
1373
1374		$err = $this->ldap->bind();
1375
1376		// Change the default bind_type to use the full, call get_user_attributes function to use the realname (dn) in the credentials test
1377		$this->ldap->setOption('bind_type', 'full');
1378		$this->ldap->get_user_attributes();
1379
1380		// Credentials test! To test it we force the reconnection.
1381		$err = $this->ldap->bind(true);
1382
1383		switch ($err) {
1384			case LdapException::LDAP_INVALID_CREDENTIALS:
1385				return PASSWORD_INCORRECT;
1386
1387			case LdapException::LDAP_INVALID_SYNTAX:
1388			case LdapException::LDAP_NO_SUCH_OBJECT:
1389			case LdapException::LDAP_INVALID_DN_SYNTAX:
1390				return USER_NOT_FOUND;
1391
1392			case LdapException::LDAP_SUCCESS:
1393				if ($prefs['auth_ldap_debug'] == 'y') {
1394					$logslib->add_log('ldap', 'Bind successful.');
1395				}
1396				return USER_VALID;
1397
1398			default:
1399				return SERVER_ERROR;
1400		}
1401
1402		// this should never happen
1403		die('Assertion failed ' . __FILE__ . ':' . __LINE__);
1404	}
1405
1406	// validate the user from a phpBB database
1407	function validate_user_phpbb($user, $pass)
1408	{
1409		require_once('auth/phpbb.php');
1410		$this->phpbbauth = new TikiPhpBBLib();
1411
1412		switch ($this->phpbbauth->check($user, $pass)) {
1413			case PHPBB_INVALID_CREDENTIALS:
1414				return PASSWORD_INCORRECT;
1415				break;
1416
1417			case PHPBB_INVALID_SYNTAX:
1418			case PHPBB_NO_SUCH_USER:
1419				return USER_NOT_FOUND;
1420				break;
1421
1422			case PHPBB_SUCCESS:
1423				//$logslib->add_log('phpbb','PhpBB user validation successful.');
1424				return USER_VALID;
1425				break;
1426
1427			default:
1428				return SERVER_ERROR;
1429		}
1430		// this should never happen
1431		die('Assertion failed ' . __FILE__ . ':' . __LINE__);
1432	}
1433
1434	/**
1435	 * Help function to disable a user's password.
1436	 *
1437	 * Used, whenever the user password shall not be
1438	 * hold in the tiki db but in LDAP or somewhere else.
1439	 */
1440	function disable_tiki_auth($user)
1441	{
1442		global $tiki, $prefs;
1443
1444		if ($prefs['auth_ldap_debug'] == 'y') {
1445			TikiLib::lib('logs')->add_log('ldap', 'UserLib::disable_tiki_auth()');
1446		}
1447		$query = 'update `users_users` set `hash`=? where binary `login` = ?';
1448		$result = $this->query($query, ['', $user]);
1449	}
1450
1451	/**
1452	 * Synchronizes all existing Tiki users to what is in the LDAP directory.
1453	 *
1454	 * Retrieves all users info from LDAP.
1455	 * Creates the corresponding Tiki users from this data.
1456	 */
1457	public function ldap_sync_all_users()
1458	{
1459		global $prefs;
1460		$logslib = TikiLib::lib('logs');
1461
1462		if ($prefs['syncUsersWithDirectory'] != 'y') {
1463			return false;
1464		}
1465
1466		require_once('auth/ldap.php');
1467		if ($prefs['auth_ldap_debug'] == 'y') {
1468			$logslib->add_log('ldap', 'UsersLib::ldap_sync_all_users(): Syncing all Tiki users to LDAP');
1469		}
1470
1471		$bind_type = 'default';
1472
1473		switch ($prefs['auth_ldap_type']) { // Must be anonymous or admin
1474			case 'default':
1475				break;
1476
1477			default:
1478				if (! empty($prefs['auth_ldap_adminuser'])) {
1479					$bind_type = 'explicit';
1480					break;
1481				}
1482				return false;
1483				break;
1484		}
1485
1486		// FIXME: Similar to the contents of the init_ldap method:
1487		$ldap_options = [
1488					'host' => $prefs['auth_ldap_host'],
1489					'port' => $prefs['auth_ldap_port'],
1490					'useStartTls' => $prefs['auth_ldap_starttls'],
1491					'useSsl' => $prefs['auth_ldap_ssl'],
1492					'baseDn' => $prefs['auth_ldap_basedn'],
1493					'scope' => $prefs['auth_ldap_scope'],
1494					'bind_type' => $bind_type,
1495					'binddn' => $prefs['auth_ldap_adminuser'],
1496					'bindpw' => $prefs['auth_ldap_adminpass'],
1497					'userdn' => $prefs['auth_ldap_userdn'],
1498					'useroc' => $prefs['auth_ldap_useroc'],
1499					'userattr' => $prefs['auth_ldap_userattr'],
1500					'fullnameattr' => $prefs['auth_ldap_nameattr'],
1501					'emailattr' => $prefs['auth_ldap_emailattr'],
1502					'countryattr' => $prefs['auth_ldap_countryattr'],
1503					'groupdn' => $prefs['auth_ldap_groupdn'],
1504					'groupattr' => $prefs['auth_ldap_groupattr'],
1505					'groupoc' => $prefs['auth_ldap_groupoc'],
1506					'groupnameattr' => $prefs['auth_ldap_groupnameatr'],
1507					'groupdescattr' => $prefs['auth_ldap_groupdescatr'],
1508					'groupmemberattr' => $prefs['auth_ldap_memberattr'],
1509					'groupmemberisdn' => $prefs['auth_ldap_memberisdn'],
1510					'usergroupattr' => $prefs['auth_ldap_usergroupattr'],
1511					'groupgroupattr' => $prefs['auth_ldap_groupgroupattr'],
1512					'debug' => $prefs['auth_ldap_debug']
1513		];
1514
1515		$user_ldap = new TikiLdapLib($ldap_options);
1516
1517		// Retrieve all users from LDAP:
1518		if (! ($users_attributes = $user_ldap->get_all_users_attributes())) {
1519			return false;
1520		}
1521
1522		foreach ($users_attributes as $user_attributes) {
1523			$user = $user_attributes[$prefs['auth_ldap_userattr']];
1524			$this->add_user($user, '', $user);
1525
1526			if ($prefs['auth_method'] == 'ldap') {
1527				$this->disable_tiki_auth($user);
1528			}
1529
1530			$this->ldap_sync_user_data($user, $user_attributes);
1531		}
1532	}
1533
1534	/**
1535	 * Synchronize all groups with LDAP directory
1536	 *
1537	 * For each user, makes sure that user is member of the same groups as specified in their LDAP entry.
1538	 */
1539	public function ldap_sync_all_groups()
1540	{
1541		global $prefs;
1542		if ($prefs['auth_ldap_debug'] == 'y') {
1543			TikiLib::lib('logs')->add_log('ldap', 'UsersLib::ldap_sync_all_groups()');
1544		}
1545
1546		if ($prefs['syncGroupsWithDirectory'] != 'y') {
1547			return false;
1548		}
1549
1550		$users = $this->list_all_users();
1551
1552		foreach ($users as $user) {
1553			$this->_ldap_sync_groups($user, null);
1554		}
1555	}
1556
1557	/**
1558	 * Updates the info about the current Tiki user with the info found in the LDAP directory.
1559	 *
1560	 * @see \UsersLib::disable_tiki_auth()
1561	 * @see \UsersLib::ldap_sync_user_data()
1562	 */
1563	function ldap_sync_user($user, $pass)
1564	{
1565		if ($user == 'admin') {
1566			return true;
1567		}
1568
1569		global $prefs;
1570		$logslib = TikiLib::lib('logs');
1571		$ret = true;
1572		$this->init_ldap($user, $pass);
1573
1574		if ($prefs['auth_ldap_debug'] == 'y') {
1575			$logslib->add_log('ldap', 'Syncing user with ldap');
1576		}
1577
1578		// sync user information
1579		if ($prefs['auth_method'] == 'ldap') {
1580			$this->disable_tiki_auth($user);
1581		}
1582
1583		if ($prefs['syncUsersWithDirectory'] == 'y') {
1584			$this->ldap_sync_user_data($user, $this->ldap->get_user_attributes());
1585		}
1586
1587		return $ret;
1588	}
1589
1590	/**
1591	 * Sets Tiki user fields with the values found about a given user in LDAP.
1592	 *
1593	 * (name, email, country)
1594	 *
1595	 * @param user: username
1596	 * @param attributes: Name and value for each LDAP attribute of the user.
1597	 */
1598	function ldap_sync_user_data($user, $attributes)
1599	{
1600		global $prefs;
1601
1602		if ($prefs['auth_ldap_debug'] == 'y') {
1603			TikiLib::lib('logs')->add_log('ldap', 'UsersLib::ldap_sync_user_data()');
1604		}
1605		$u = ['login' => $user];
1606
1607		$userPreferenceToLdapPreferenceMap = [
1608			'realName' => 'auth_ldap_nameattr',
1609			'email' => 'auth_ldap_emailattr',
1610			'country' => 'auth_ldap_countryattr',
1611		];
1612
1613		foreach ($userPreferenceToLdapPreferenceMap as $preference => $ldapPreference) {
1614			if ($preference == 'email') {
1615				$userPreferenceValue = $this->get_user_email($user);
1616			} else {
1617				$userPreferenceValue = $this->get_user_preference($user, $preference);
1618			}
1619			$isSetLdapPreferenceValue = isset($attributes[$prefs[$ldapPreference]]);
1620			$ldapPreferenceValue = $isSetLdapPreferenceValue ? $attributes[$prefs[$ldapPreference]] : '';
1621
1622			if ($userPreferenceValue && empty($ldapPreferenceValue)) {
1623				$u[$preference] = '';
1624			} else {
1625				if ($isSetLdapPreferenceValue) {
1626					// Ldap attributes can (by default) have multiple values, check if the current user preference is one of
1627					// the values of the attribute, in that case keep the same value
1628					if (is_array($ldapPreferenceValue)
1629						&& $userPreferenceValue
1630						&& in_array($userPreferenceValue, $ldapPreferenceValue)
1631					) {
1632						$u[$preference] = $userPreferenceValue;
1633						continue;
1634					}
1635					if (is_array($ldapPreferenceValue)) {
1636						// Ldap attributes can (by default) have multiple values
1637						// so we always take the first from the list in case of a multi value field
1638						$ldapPreferenceValue = reset($ldapPreferenceValue);
1639					}
1640					if ($isSetLdapPreferenceValue) {
1641						$u[$preference] = $ldapPreferenceValue;
1642					}
1643				}
1644			}
1645		}
1646
1647		if (count($u) > 1) {
1648			$this->set_user_fields($u);
1649		}
1650	}
1651
1652	/**
1653	 * For a given user, makes sure this user is member of all the groups they should be,
1654	 * according to their entry in the LDAP directory.
1655	 *
1656	 * @param user: username
1657	 * @param pass: password (might be null)
1658	 */
1659	private function _ldap_sync_groups($user, $pass)
1660	{
1661		if ($user == 'admin') {
1662			return true;
1663		}
1664
1665		global $prefs;
1666		$logslib = TikiLib::lib('logs');
1667		static $ldap_group_options = [];
1668		static $ext_dir = null;
1669		$ret = true;
1670
1671		$this->init_ldap($user, $pass);
1672		$this->ldap->setOption('username', $user);
1673		$this->ldap->setOption('password', $pass);
1674
1675		if ($prefs['auth_ldap_debug'] == 'y') {
1676			$logslib->add_log('ldap', 'UsersLib::_ldap_sync_groups(): Syncing group with ldap');
1677		}
1678		$userattributes = $this->ldap->get_user_attributes(true);
1679
1680		if ($prefs['syncGroupsWithDirectory'] == 'y' && $userattributes[$prefs['auth_ldap_group_corr_userattr']] != null) {
1681			// sync external group information of user
1682			$ldapgroups = [];
1683
1684			if ($prefs['auth_ldap_group_external'] == 'y') {
1685				// External directory for groups
1686				if (! isset($ext_dir)) {
1687					$ldap_group_options = [
1688							'host' => $prefs['auth_ldap_group_host'],
1689							'port' => $prefs['auth_ldap_group_port'],
1690							'useStartTls' => $prefs['auth_ldap_group_starttls'],
1691							'useSsl' => $prefs['auth_ldap_group_ssl'],
1692							'baseDn' => $prefs['auth_ldap_group_basedn'],
1693							'scope' => $prefs['auth_ldap_group_scope'],
1694							'userdn' => $prefs['auth_ldap_group_userdn'],
1695							'useroc' => $prefs['auth_ldap_group_useroc'],
1696							'userattr' => $prefs['auth_ldap_group_userattr'],
1697							'username' => $userattributes[$prefs['auth_ldap_group_corr_userattr']],
1698							'groupdn' => $prefs['auth_ldap_groupdn'],
1699							'groupattr' => $prefs['auth_ldap_groupattr'],
1700							'groupoc' => $prefs['auth_ldap_groupoc'],
1701							'groupnameattr' => $prefs['auth_ldap_groupnameatr'],
1702							'groupdescattr' => $prefs['auth_ldap_groupdescatr'],
1703							'groupmemberattr' => $prefs['auth_ldap_memberattr'],
1704							'groupmemberisdn' => $prefs['auth_ldap_memberisdn'],
1705							'usergroupattr' => $prefs['auth_ldap_usergroupattr'],
1706							'groupgroupattr' => $prefs['auth_ldap_groupgroupattr'],
1707							'debug' => $prefs['auth_ldap_group_debug']
1708					];
1709
1710					if (empty($prefs['auth_ldap_group_adminuser'])) {
1711						// Anonymous
1712						$ldap_group_options['bind_type'] = 'default';
1713					} else {
1714						// Explicit
1715						$ldap_group_options['bind_type'] = 'explicit';
1716						$ldap_group_options['binddn'] = $prefs['auth_ldap_group_adminuser'];
1717						$ldap_group_options['bindpw'] = $prefs['auth_ldap_group_adminpass'];
1718					}
1719
1720					$ext_dir = new TikiLdapLib($ldap_group_options);
1721				}
1722
1723				$ext_dir->setOption('username', $userattributes[$prefs['auth_ldap_group_corr_userattr']]);
1724				$ldapgroups = $ext_dir->get_groups(true);
1725			} else {
1726				if (! empty($prefs['auth_ldap_adminuser'])) {
1727					$this->ldap->setOption('bind_type', 'explicit');
1728					$this->ldap->setOption('binddn', $prefs['auth_ldap_adminuser']);
1729					$this->ldap->setOption('bindpw', $prefs['auth_ldap_adminpass']);
1730					$this->ldap->bind(true);
1731				}
1732
1733				$ldapgroups = $this->ldap->get_groups(true);
1734
1735				if (! empty($prefs['auth_ldap_adminuser'])) {
1736					$this->ldap->setOption('bind_type', $prefs['auth_ldap_type']);
1737					$this->ldap->bind(true);
1738				}
1739			}
1740
1741			$this->_ldap_sync_group_data($user, $ldapgroups);
1742		}
1743
1744		return $ret;
1745	}
1746
1747	/**
1748	 * Sync Tiki groups with LDAP groups data
1749	 *
1750	 * For each group, assigns the user to it if it is not already a member of it.
1751	 *
1752	 * Called from \UsersLib::_ldap_sync_groups()
1753	 *
1754	 * @param user: username
1755	 * @param ldapgroups: list of LDAP group names
1756	 */
1757	private function _ldap_sync_group_data($user, $ldapgroups)
1758	{
1759		global $prefs;
1760		$logslib = TikiLib::lib('logs');
1761
1762		if (! count($ldapgroups)) {
1763			return;
1764		}
1765
1766		$ldapgroups_simple = [];
1767		$tikigroups = $this->get_user_groups($user);
1768		foreach ($ldapgroups as $group) {
1769			$gname = $group[$prefs['auth_ldap_groupattr']];
1770			$ldapgroups_simple[] = $gname; // needed later
1771			if ($this->group_exists($gname) && ! $this->group_is_external($gname)) { // group exists
1772				//check if we need to sync group information
1773				if (isset($group[$prefs['auth_ldap_groupdescattr']])) {
1774					$ginfo = $this->get_group_info($gname);
1775					if ($group[$prefs['auth_ldap_groupdescattr']] != $ginfo['groupDesc']) {
1776						$this->set_group_description($gname, $group[$prefs['auth_ldap_groupdescattr']]);
1777					}
1778				}
1779			} elseif (! $this->group_exists($gname)) { // create group
1780				if (isset($group[$prefs['auth_ldap_groupdescattr']])) {
1781					$gdesc = $group[$prefs['auth_ldap_groupdescattr']];
1782				} else {
1783					$gdesc = '';
1784				}
1785				$logslib->add_log('ldap', 'Creating external group ' . $gname);
1786				$this->add_group($gname, $gdesc, '', 0, 0, '', '', 0, '', 0, 0, 'y');
1787			}
1788
1789			// add user
1790			if (! in_array($gname, $tikigroups)) {
1791				$logslib->add_log('ldap', 'Adding user ' . $user . ' to external group ' . $gname);
1792				$this->assign_user_to_group($user, $gname);
1793			}
1794		}
1795
1796		// now clean up group membership if user has been unassigned from a group in ldap
1797		$extgroups = $this->get_user_external_groups($user);
1798		foreach ($extgroups as $eg) {
1799			if (! in_array($eg, $ldapgroups_simple)) {
1800				$logslib->add_log('ldap', 'Removing user ' . $user . ' from external group ' . $eg);
1801				$this->remove_user_from_group($user, $eg);
1802			}
1803		}
1804	}
1805
1806	/**
1807	 * Update infos for a user, and which groups it is in, reading from LDAP.
1808	 *
1809	 * Called after a user has been created or logged from LDAP.
1810	 *
1811	 * @param user: username
1812	 * @param pass: password
1813	 * @see \UserLib::_ldap_sync_user()
1814	 * @see \UserLib::_ldap_sync_groups()
1815	 */
1816	function _ldap_sync_user_and_groups($user, $pass)
1817	{
1818		global $prefs;
1819
1820		if ($prefs['auth_ldap_debug'] == 'y') {
1821			TikiLib::lib('logs')->add_log('ldap', 'UsersLib::_ldap_sync_user_and_groups()');
1822		}
1823
1824		$ret = true;
1825		$ret &= $this->ldap_sync_user($user, $pass);
1826		$ret &= $this->_ldap_sync_groups($user, $pass);
1827
1828		// Invalidate cache
1829		$cachelib = TikiLib::lib('cache');
1830		$cacheKey = 'user_details_' . $user;
1831		$cachelib->invalidate($cacheKey);
1832
1833		return($ret);
1834	}
1835
1836	function set_group_description($group, $description)
1837	{
1838		$query = 'update `users_groups` set `groupDesc`=? where `groupName`=?';
1839		$result = $this->query($query, [$description, $group]);
1840	}
1841
1842	function group_is_external($group)
1843	{
1844		$gi = $this->get_group_info($group);
1845		if ($gi['isExternal'] == 'y') {
1846			return true;
1847		}
1848		return false;
1849	}
1850
1851	// simple function - no group inclusion or intertiki
1852	function get_user_external_groups($user)
1853	{
1854		$userid = $this->get_user_id($user);
1855		$query = 'select u.`groupName`' .
1856						' from `users_usergroups` u, `users_groups` g' .
1857						' where u.`groupName`=g.`groupName` and u.`userId`=? and g.`isExternal`=?';
1858
1859		$result = $this->query($query, [(int) $userid, 'y']);
1860		$ret = [];
1861
1862		while ($res = $result->fetchRow()) {
1863			 $ret[] = $res['groupName'];
1864		}
1865
1866		return $ret;
1867	}
1868
1869	/**
1870	 * Validate the user in the Tiki database
1871
1872	 * @param user: username
1873	 * @param pass: password
1874	 */
1875	function validate_user_tiki($user, $pass, $validate_phase = false)
1876	{
1877		global $prefs;
1878
1879		$userUpper = TikiLib::strtoupper($user);
1880		// first verify that the user exists
1881		$query = 'select `userId`,`login`,`waiting`, `hash`, `email`,`valid` from `users_users` where upper(`login`) = ?';
1882		$result = $this->query($query, [$userUpper]);
1883
1884
1885		switch ($result->numRows()) {
1886			case 0:
1887				if ($prefs['login_allow_email'] == 'y') {								//if no users found, check check if email is being used to login
1888					$query = 'select `userId`,`login`,`waiting`, `hash`, `email`,`valid` from `users_users` where upper(`email`) = ?';
1889					$result = $this->query($query, [$userUpper]);
1890					if ($result->numRows() > 1) {
1891						return [EMAIL_AMBIGUOUS, $user];					// if there is more than one user with that email
1892					} elseif ($result->numRows() == 1) {
1893						break;													// if there is only one user, exit switch
1894					}
1895				}
1896				return [USER_NOT_FOUND, $user];
1897
1898			case 1:
1899				break;
1900
1901			default:
1902				return [USER_AMBIGOUS, $user];
1903		}
1904
1905
1906
1907		$res = $result->fetchRow();
1908		$user = $res['login'];
1909
1910		// check for account flags
1911		if ($res['waiting'] === 'u') {				// if account is in validation mode.
1912			if (!empty($pass) && $pass === $res['valid']) { 			// if user successfully provides code from email
1913				return [USER_VALID, $user];
1914			} else {
1915				return [ACCOUNT_WAITING_USER, $user];  // if code validation fails, (or user tries to log in before verifying)
1916			}
1917		} elseif ($res['waiting'] === 'a') {         // if account needs administrator validation
1918			if (!empty($res['valid']) && $pass === $res['valid']) { 			// if admin successfully validates account
1919				return [USER_VALID, $user];
1920			} else {
1921				return [ACCOUNT_DISABLED, $user];
1922			}
1923		}
1924
1925		if ($validate_phase) {
1926			return [USER_PREVIOUSLY_VALIDATED, $user];		// if email verification code is used an a validated account, deny.
1927		}
1928
1929
1930		// next verify the password with every hashes methods
1931
1932
1933		if ($res['hash'][0] == '$') {				// if password was created by crypt (old tiki hash) or password_hash (current tiki hash)
1934			if (password_verify($pass, $res['hash'])) {
1935				if (password_needs_rehash($res['hash'], PASSWORD_DEFAULT)) {
1936					$this->set_user_password($res['userId'], $pass);			//if its a old hash style, rehash it in a more secure way
1937				}
1938				return [USER_VALID, $user];
1939			} else {
1940				return [PASSWORD_INCORRECT, $user];      // if the password was incorrect, dont give the md5's a spin
1941			}
1942		}
1943
1944		if (! empty($pass) && $res['hash'] === md5($pass)) { 								// very method md5(pass), for compatibility
1945			$this->set_user_password($res['userId'], $pass);
1946			return [USER_VALID, $user];
1947		}
1948		if (! empty($pass) && $res['hash'] === md5($user . $pass)) { 					// ancient method md5(user.pass), for compatibility
1949			$this->set_user_password($res['userId'], $pass);
1950			return [USER_VALID, $user];
1951		}
1952		if (! empty($pass) && $res['hash'] === md5($user . $pass . trim($res['email']))) { // very ancient method md5(user.pass.email), for compatibility
1953			$this->set_user_password($res['userId'], $pass);
1954			return [USER_VALID, $user];
1955		}
1956
1957			return [PASSWORD_INCORRECT, $user];
1958	}
1959
1960
1961	/**
1962	 * Stores a users password in the database
1963	 *
1964	 * @param userId: the id of the user.
1965	 * @param pass: the clear text password to be hashed and stored
1966	 */
1967	private function set_user_password($userId, $pass)
1968	{
1969
1970		$hash = password_hash($pass, PASSWORD_DEFAULT);
1971		$query = 'update `users_users` set `hash`=? where `userId`=?';
1972		$result = $this->query($query, [$hash, $userId]);
1973
1974	//todo: a little error checking would be nice.
1975	}
1976
1977	/**
1978	 * Synchronizes Tiki user and group info from LDAP.
1979	 *
1980	 * @param user: User name.
1981	 * @param pass: Password.
1982	 */
1983	private function _ldap_sync_and_update_lastlogin($user, $pass)
1984	{
1985		global $prefs;
1986		global $tikilib;
1987
1988		if ($prefs['auth_ldap_debug'] == 'y') {
1989			TikiLib::lib('logs')->add_log('ldap', 'UsersLib::_ldap_sync_and_update_lastlogin()');
1990		}
1991
1992		$ret = $this->update_lastlogin($user);
1993
1994		if (empty($current)) {
1995			// First time
1996			$current = 0;
1997		}
1998
1999		if ($prefs['auth_method'] === 'ldap' && ($prefs['syncGroupsWithDirectory'] == 'y' || $prefs['syncUsersWithDirectory'] == 'y' )) {
2000			$ret &= $this->_ldap_sync_user_and_groups($user, $pass);
2001		}
2002
2003		return $ret;
2004	}
2005
2006	/**
2007	 * Updates date and time of current and last (previous) login.
2008	 *
2009	 * Called when the user logs in.
2010	 * The updated fields are: currentLogin and lastLogin.
2011	 * Resets unsuccessful_logins field.
2012	 *
2013	 * @param user: Username
2014	 */
2015	public function update_lastlogin($user)
2016	{
2017		$previous = $this->getOne('select `currentLogin` from `users_users` where `login`= ?', [$user]);
2018		if (is_null($previous)) {
2019			// First login
2020			$previous = $this->now; // TODO: Should we really set lastLogin on the first login?
2021		}
2022
2023		$query = 'update `users_users` set `lastLogin`=?, `currentLogin`=?, `unsuccessful_logins`=? where `login`=? and (`waiting` <> \'a\' OR `waiting` IS NULL)';	// don't update last login if waiting for admin
2024		$this->query(
2025			$query,
2026			[
2027				(int)$previous,
2028				(int)$this->now,
2029				0,
2030				$user
2031			]
2032		);
2033
2034		return true;
2035	}
2036
2037	/**
2038	 * Creates a new user in the LDAP directory
2039	 *
2040	 * @param user: username
2041	 * @param pass: password
2042	 */
2043	function create_user_ldap($user, $pass)
2044	{
2045		// todo: no more pear::auth! all in pear::ldap2
2046		global $prefs;
2047		$tikilib = TikiLib::lib('tiki');
2048
2049		$options = [];
2050		$options['url'] = $prefs['auth_ldap_url'];
2051		$options['host'] = $prefs['auth_ldap_host'];
2052		$options['port'] = $prefs['auth_ldap_port'];
2053		$options['scope'] = $prefs['auth_ldap_scope'];
2054		$options['baseDn'] = $prefs['auth_ldap_basedn'];
2055		$options['userdn'] = $prefs['auth_ldap_userdn'];
2056		$options['userattr'] = $prefs['auth_ldap_userattr'];
2057		$options['useroc'] = $prefs['auth_ldap_useroc'];
2058		$options['groupdn'] = $prefs['auth_ldap_groupdn'];
2059		$options['groupattr'] = $prefs['auth_ldap_groupattr'];
2060		$options['groupoc'] = $prefs['auth_ldap_groupoc'];
2061		$options['memberattr'] = $prefs['auth_ldap_memberattr'];
2062		$options['memberisdn'] = ($prefs['auth_ldap_memberisdn'] == 'y');
2063		$options['binduser'] = $prefs['auth_ldap_adminuser'];
2064		$options['bindpw'] = $prefs['auth_ldap_adminpass'];
2065
2066		// set additional attributes here
2067		$userattr = [];
2068		$userattr['email'] = ( $prefs['login_is_email'] == 'y' )
2069												? $user
2070												: $this->getOne('select `email` from `users_users` where `login`=?', [$user]);
2071
2072
2073		// set the Auth options
2074		$a = new Auth('LDAP', $options);
2075
2076		// check if the login correct
2077		if ($a->addUser($user, $pass, $userattr) === true) {
2078			$status = USER_VALID;
2079		} else {
2080			// otherwise use the error status given back
2081			$status = $a->getStatus();
2082		}
2083
2084		return $status;
2085	}
2086
2087
2088	/**
2089	* This is a lighter version of get_users_names designed for AJAX checking of userrealnames
2090	*/
2091	function get_users_light($offset = 0, $maxRecords = -1, $sort_mode = 'login_asc', $find = '', $group = '')
2092	{
2093		global $prefs, $tiki_p_list_users, $tiki_p_admin;
2094
2095		if ($tiki_p_list_users !== 'y' && $tiki_p_admin != 'y') {
2096			return [];
2097		}
2098
2099		$mid = '';
2100		$bindvars = [];
2101		if (! empty($group)) {
2102			if (! is_array($group)) {
2103				$group = [$group];
2104			}
2105			$mid = ', `users_usergroups` uug where uu.`userId`=uug.`userId` and uug.`groupName` in (' .
2106							implode(',', array_fill(0, count($group), '?')) . ')';
2107
2108			$bindvars = $group;
2109		}
2110		if (! empty($find)) {
2111			$findesc = '%' . $find . '%';
2112			if (empty($mid)) {
2113				$mid .= ' where uu.`login` like ?';
2114			} else {
2115				$mid .= ' and uu.`login` like ?';
2116			}
2117			$bindvars[] = $findesc;
2118		}
2119
2120		$query = "select uu.`login` from `users_users` uu $mid order by " . $this->convertSortMode($sort_mode);
2121		$result = $this->fetchAll($query, $bindvars, $maxRecords, $offset);
2122
2123		$ret = [];
2124
2125		foreach ($result as $res) {
2126			$ret[$res['login']] = $this->clean_user($res['login']);
2127		}
2128
2129		if (! empty($findesc) && $prefs['user_show_realnames'] == 'y') {
2130			$query = "select `user` from `tiki_user_preferences` where `prefName` = 'realName' and `value` like ?";
2131			$result = $this->fetchAll($query, [$findesc], $maxRecords, $offset);
2132			foreach ($result as $res) {
2133				if (! isset($ret[$res['user']])) {
2134					$ret[$res['user']] = $this->clean_user($res['user']);
2135				}
2136			}
2137		}
2138		asort($ret);
2139		return($ret);
2140	}
2141
2142	function get_users_names($offset = 0, $maxRecords = -1, $sort_mode = 'login_asc', $find = '')
2143	{
2144		global $tiki_p_list_users, $tiki_p_admin;
2145
2146		if ($tiki_p_list_users !== 'y' && $tiki_p_admin != 'y') {
2147			return [];
2148		}
2149
2150		// This function gets an array of user login names.
2151		if (! empty($find)) {
2152			$findesc = '%' . $find . '%';
2153			$mid = ' where `login` like ?';
2154			$bindvars = [$findesc];
2155		} else {
2156			$mid = '';
2157			$bindvars = [];
2158		}
2159
2160		$query = "select `login` from `users_users` $mid order by " . $this->convertSortMode($sort_mode);
2161		$result = $this->query($query, $bindvars, $maxRecords, $offset);
2162		$ret = [];
2163
2164		while ($res = $result->fetchRow()) {
2165			$ret[] = $res['login'];
2166		}
2167
2168		return ($ret);
2169	}
2170
2171	function get_members($group)
2172	{
2173		$group_results = true;
2174		if (! is_array($group)) {
2175			$group = [$group];
2176			$group_results = false;
2177		} elseif (count($group) == 0) {
2178			return [];
2179		}
2180		$users = $this->fetchAll('SELECT ug.groupName, u.login FROM `users_usergroups` ug INNER JOIN `users_users` u ON u.userId = ug.userId WHERE ug.groupName IN ('
2181			. implode(',', array_fill(0, count($group), '?')) . ')', $group);
2182
2183		if (! $group_results) {
2184			return array_map(function ($row) {
2185				return $row['login'];
2186			}, $users);
2187		} else {
2188			$grouped = [];
2189			foreach ($users as $row) {
2190				$grouped[$row['groupName']][] = $row['login'];
2191			}
2192			return $grouped;
2193		}
2194	}
2195
2196	function get_users(
2197		$offset = 0,
2198		$maxRecords = -1,
2199		$sort_mode = 'login_asc',
2200		$find = '',
2201		$initial = '',
2202		$inclusion = false,
2203		$group = '',
2204		$email = '',
2205		$notconfirmed = false,
2206		$notvalidated = false,
2207		$neverloggedin = false
2208	) {
2209
2210		$hasPermission = function ($group) {
2211			$perms = Perms::get(['type' => 'group', 'object' => $group]);
2212			if (! $perms->group_view_members && ! $perms->list_users && ! $perms->admin_users) {
2213				return false;
2214			}
2215
2216			return true;
2217		};
2218
2219		if (is_array($group)) {
2220			$group = array_filter($group, $hasPermission);
2221
2222			if (empty($group)) {
2223				return [];
2224			}
2225		} elseif (! $hasPermission($group)) {
2226			return [];
2227		}
2228
2229		$mid = '';
2230		$bindvars = [];
2231		$mmid = '';
2232		$mbindvars = [];
2233		// Return an array of users indicating name, email, last changed pages, versions, lastLogin
2234
2235		//TODO : recurse included groups
2236		if (! empty($group)) {
2237			if (! is_array($group)) {
2238				$group = [$group];
2239			}
2240			$mid = ', `users_usergroups` uug where uu.`userId`=uug.`userId` and uug.`groupName` in (' . implode(',', array_fill(0, count($group), '?')) . ')';
2241			$mmid = $mid;
2242			$bindvars = $group;
2243			$mbindvars = $bindvars;
2244		}
2245		if (! empty($email)) {
2246			$mid .= $mid == '' ? ' where' : ' and';
2247			$mid .= ' uu.`email` like ?';
2248			$mmid = $mid;
2249			$bindvars[] = '%' . $email . '%';
2250			$mbindvars[] = '%' . $email . '%';
2251		}
2252
2253		if (! empty($find)) {
2254			$mid .= $mid == '' ? ' where' : ' and';
2255			$mid .= ' uu.`login` like ?';
2256			$mmid = $mid;
2257			$bindvars[] = '%' . $find . '%';
2258			$mbindvars[] = '%' . $find . '%';
2259		}
2260
2261		if (! empty($initial)) {
2262			$mid = ' where `login` like ?';
2263			$mmid = $mid;
2264			$bindvars = [$initial . '%'];
2265			$mbindvars = $bindvars;
2266		}
2267
2268		if ($notconfirmed && $notvalidated) {
2269			$mid .= $mid == '' ? ' where' : ' and';
2270			$mid .= ' (uu.`waiting` = \'u\' or uu.`waiting` = \'a\')';
2271			$mmid = $mid;
2272		} else {
2273			if ($notconfirmed) {
2274				$mid .= $mid == '' ? ' where' : ' and';
2275				$mid .= ' uu.`waiting` = \'u\'';
2276				$mmid = $mid;
2277			}
2278
2279			if ($notvalidated) {
2280				$mid .= $mid == '' ? ' where' : ' and';
2281				$mid .= ' uu.`waiting` = \'a\'';
2282				$mmid = $mid;
2283			}
2284		}
2285
2286		if ($neverloggedin) {
2287			$mid .= $mid == '' ? ' where' : ' and';
2288			$mid .= ' (uu.`lastLogin` is null or uu.`lastLogin` = 0)';
2289			$mmid = $mid;
2290		}
2291		$query = "select uu.* from `users_users` uu $mid order by " . $this->convertSortMode($sort_mode);
2292		$query_cant = "select count(*) from `users_users` uu $mmid";
2293		$ret = $this->fetchAll($query, $bindvars, $maxRecords, $offset);
2294		$cant = $this->getOne($query_cant, $mbindvars);
2295
2296		foreach ($ret as &$res) {
2297			if (! $perms->admin_users) {
2298				// Filter out sensitive data
2299				unset($res['email']);
2300				unset($res['hash']);
2301				unset($res['provpass']);
2302			}
2303
2304			$res['user'] = $res['login'];
2305			$user = $res['user'];
2306
2307			if ($inclusion) {
2308				$groups = $this->get_user_groups_inclusion($user);
2309			} else {
2310				$groups = $this->get_user_groups($user);
2311			}
2312
2313			$res['groups'] = $groups;
2314			$res['age'] = $this->now - $res['registrationDate'];
2315			$res['user_information'] = $this->get_user_preference($user, 'user_information', 'public');
2316			$res['editable'] = $this->user_can_be_edited($user);
2317		}
2318
2319		$retval = [];
2320		$retval['data'] = $ret;
2321		$retval['cant'] = $cant;
2322		return $retval;
2323	}
2324
2325	/**
2326	 * @param string $edited_user : username (login) of the user that might be edited
2327	 * @param string $editing_user : username of user doing the editing (or logged-in user if omitted)
2328	 * @return bool : true if $editing_user can edit $edited_user
2329	 */
2330	function user_can_be_edited($edited_user, $editing_user = '')
2331	{
2332		global $user;
2333
2334		if (empty($editing_user)) {
2335			$editing_user = $user;
2336		}
2337
2338		$editable = false;
2339		if ($this->user_has_permission($editing_user, 'tiki_p_admin')) {
2340			$editable = true;
2341		} elseif ($this->user_has_permission($editing_user, 'tiki_p_admin_users') && ! $this->user_has_permission($edited_user, 'tiki_p_admin')) {
2342			$editable = true;
2343		}
2344		return $editable;
2345	}
2346
2347	function group_inclusion($group, $include)
2348	{
2349		$query = 'insert into `tiki_group_inclusion`(`groupName`,`includeGroup`) values(?,?)';
2350		$result = $this->query($query, [$group, $include]);
2351	}
2352
2353	function get_included_container_groups($group, $recur = true)
2354	{
2355		$includedGroups = $this->get_included_groups($group, $recur);
2356		$groups = $this->get_group_info($includedGroups);
2357		return array_filter($groups, function ($item) {
2358			return $item["isTplGroup"] == "y";
2359		});
2360	}
2361
2362
2363	function get_included_groups($group, $recur = true)
2364	{
2365		$engroup = urlencode($group);
2366		if (! $recur || ! isset($this->groupinclude_cache[$engroup])) {
2367			$query = 'select `includeGroup` from `tiki_group_inclusion` where `groupName`=?';
2368			$result = $this->query($query, [$group]);
2369			$ret = [];
2370
2371			while ($res = $result->fetchRow()) {
2372				$ret[] = $res['includeGroup'];
2373				if ($recur) {
2374					$ret2 = $this->get_included_groups($res['includeGroup']);
2375					$ret = array_merge($ret, $ret2);
2376				}
2377			}
2378
2379			$back = array_unique($ret);
2380
2381			if ($recur) {
2382				$this->groupinclude_cache[$engroup] = $back;
2383			}
2384			return $back;
2385		} else {
2386			return $this->groupinclude_cache[$engroup];
2387		}
2388	}
2389	function get_including_groups($group, $recur = 'n')
2390	{
2391		$query = 'select `groupName` from `tiki_group_inclusion` where `includeGroup`=? order by `groupName`';
2392		$result = $this->query($query, [$group]);
2393		$ret = [];
2394		while ($res = $result->fetchRow()) {
2395			$ret[] = $res['groupName'];
2396			if ($recur == 'y') {
2397				$ret = array_merge($ret, $this->get_including_groups($res['groupName']));
2398			}
2399		}
2400		if ($recur == 'y') {
2401			sort($ret);
2402		}
2403		return $ret;
2404	}
2405	function user_is_in_group($user, $group)
2406	{
2407		$user_groups = $this->get_user_groups($user);
2408		if (in_array($group, $user_groups)) {
2409			return true;
2410		} else {
2411			return false;
2412		}
2413	}
2414
2415	function remove_user_from_group($user, $group, $bulk = false)
2416	{
2417		global $prefs;
2418
2419		$tikilib = TikiLib::lib('tiki');
2420		$cachelib = TikiLib::lib('cache');
2421
2422		$cachelib->invalidate('user_details_' . $user);
2423		$tikilib->invalidate_usergroups_cache($user);
2424		$this->invalidate_usergroups_cache($user); // this is needed as cache is present in this instance too
2425
2426		$userid = $this->get_user_id($user);
2427
2428		$query = 'delete from `users_usergroups` where `userId` = ? and `groupName` = ?';
2429		$result = $this->query($query, [$userid, $group]);
2430
2431		$query = 'update `users_users` set `default_group`=? where `login`=? and `default_group`=?';
2432		$this->query($query, ['Registered', $user, $group]);
2433
2434		TikiLib::events()->trigger('tiki.user.groupleave', [
2435			'type' => 'user',
2436			'object' => $user,
2437			'group' => $group,
2438			'bulk_import' => $bulk,
2439		]);
2440
2441		$_SESSION['u_info']['group'] = 'Registered';
2442	}
2443
2444	function remove_user_from_all_groups($user)
2445	{
2446		global $prefs;
2447		$userid = $this->get_user_id($user);
2448		$query = 'delete from `users_usergroups` where `userId` = ?';
2449		$result = $this->query($query, [$userid]);
2450		TikiLib::events()->trigger('tiki.user.update', ['type' => 'user', 'object' => $user]);
2451	}
2452
2453	function get_groups_userchoice()
2454	{
2455		$ret = [];
2456		$groups = $this->get_groups(0, -1, '', '', '', 'n', '', 'y');
2457		foreach ($groups['data'] as $g) {
2458			$ret[] = $g['groupName'];
2459		}
2460		return $ret;
2461	}
2462
2463	function get_groups_for_permissions()
2464	{
2465		$query = "SELECT * from users_groups
2466					where isTplGroup <> 'y' and groupName not in (select groupName
2467					from  tiki_group_inclusion where includeGroup in (SELECT groupName
2468					from users_groups where isTplGroup = 'y')) order by id asc;";
2469		$ret = $this->fetchAll($query);
2470		$retval = [];
2471		$retval['data'] = $ret;
2472		$retval['cant'] = count($ret);
2473		return $retval;
2474	}
2475
2476	function get_template_groups_containers()
2477	{
2478		$query = "SELECT * from users_groups
2479					where isTplGroup = 'y' order by id asc;";
2480		$ret = $this->fetchAll($query);
2481		$retval = [];
2482		$retval['data'] = $ret;
2483		$retval['cant'] = count($ret);
2484		return $retval;
2485	}
2486
2487	function get_group_children($groupName)
2488	{
2489		$query = "SELECT DISTINCT *  from users_groups as ug
2490					where ug.groupName  in (select groupName from  tiki_group_inclusion where includeGroup = ?) order by id asc;";
2491		$ret = $this->fetchAll($query, [$groupName]);
2492		$retval = [];
2493		$retval['data'] = $ret;
2494		$retval['cant'] = count($ret);
2495		return $retval;
2496	}
2497
2498	function get_group_children_with_permissions($groupName)
2499	{
2500		$query = "SELECT DISTINCT ug.*  from users_groups as ug join users_grouppermissions as up on ug.groupName = up.groupName
2501					where ug.groupName  in (select groupName from  tiki_group_inclusion where includeGroup = ?) order by id asc;";
2502		$ret = $this->fetchAll($query, [$groupName]);
2503		$retval = [];
2504		$retval['data'] = $ret;
2505		$retval['cant'] = count($ret);
2506		return $retval;
2507	}
2508
2509	function get_groups($offset = 0, $maxRecords = -1, $sort_mode = 'groupName_asc', $find = '', $initial = '', $details = "y", $inGroups = '', $userChoice = '')
2510	{
2511		$mid = '';
2512		$bindvars = [];
2513		if ($find) {
2514			$mid = ' where `groupName` like ?';
2515			$bindvars[] = '%' . $find . '%';
2516		}
2517
2518		if ($initial) {
2519			$mid = ' where `groupName` like ?';
2520			$bindvars = [$initial . '%'];
2521		}
2522
2523		if ($inGroups) {
2524			$mid .= $mid ? ' and ' : ' where ';
2525			$mid .= '`groupName` in (';
2526			$cpt = 0;
2527			foreach ($inGroups as $grp => $value) {
2528				if ($cpt++) {
2529					$mid .= ',';
2530				}
2531				$mid .= '?';
2532				$bindvars[] = $grp;
2533			}
2534			$mid .= ')';
2535		}
2536
2537		if ($userChoice) {
2538			$mid .= $mid ? ' and ' : ' where ';
2539			$mid .= "`userChoice` = 'y'";
2540		}
2541
2542		$query = "select * from `users_groups` $mid order by " . $this->convertSortMode($sort_mode);
2543		$query_cant = "select count(*) from `users_groups` $mid";
2544		$ret = $this->fetchAll($query, $bindvars, $maxRecords, $offset);
2545		$cant = $this->getOne($query_cant, $bindvars);
2546
2547		foreach ($ret as &$res) {
2548			if ($details == 'y') {
2549				$perms = $this->get_group_permissions($res['groupName']);
2550				$res['perms'] = $perms;
2551				$res['permcant'] = count($perms);
2552				$groups = $this->get_included_groups($res['groupName']);
2553				$res['included'] = $groups;
2554				$res['included_direct'] = $this->get_included_groups($res['groupName'], false);
2555			}
2556		}
2557
2558		$retval = [];
2559		$retval['data'] = $ret;
2560		$retval['cant'] = $cant;
2561		return $retval;
2562	}
2563
2564	function list_all_users()
2565	{
2566		global $tiki_p_list_users, $tiki_p_admin;
2567		$cachelib = TikiLib::lib('cache');
2568
2569		if ($tiki_p_list_users !== 'y' && $tiki_p_admin != 'y') {
2570			return [];
2571		}
2572
2573		if (! $users = $cachelib->getSerialized('userslist')) {
2574			$users = [];
2575			$result = $this->query('select `login`,`userId` from `users_users` order by `login`', []);
2576			while ($res = $result->fetchRow()) {
2577				$users["{$res['userId']}"] = $res['login'];
2578			}
2579			$cachelib->cacheItem('userslist', serialize($users));
2580		}
2581
2582		return $users;
2583	}
2584
2585	function list_regular_groups()
2586	{
2587
2588		$groups = [];
2589		$result = $this->query('select `id`, `groupName` from `users_groups` where isRole <> ?  order by `groupName`', ['y']);
2590		while ($res = $result->fetchRow()) {
2591			$groups[] = ["groupName" => $res['groupName'], "id" => $res['id']];
2592		}
2593
2594		return $groups;
2595	}
2596
2597	function list_role_groups()
2598	{
2599		return $this->fetchAll("
2600            SELECT ug.id, ug.groupName FROM `users_groups` ug
2601			WHERE
2602			ug.isRole = 'y'
2603			group by ug.id, ug.GroupName");
2604	}
2605
2606
2607	function list_all_groups()
2608	{
2609		$cachelib = TikiLib::lib('cache');
2610
2611		if (! $groups = $cachelib->getSerialized('grouplist')) {
2612			$groups = [];
2613			$result = $this->query('select `groupName` from `users_groups` order by `groupName`', []);
2614			while ($res = $result->fetchRow()) {
2615				$groups[] = $res['groupName'];
2616			}
2617			$cachelib->cacheItem('grouplist', serialize($groups));
2618		}
2619
2620		return $groups;
2621	}
2622
2623	function list_all_groupIds()
2624	{
2625		$cachelib = TikiLib::lib('cache');
2626
2627		if (! $groups = $cachelib->getSerialized('groupIdlist')) {
2628			$groups = $this->fetchAll('select `id`, `groupName`, isRole from `users_groups` order by `groupName`', []);
2629			$cachelib->cacheItem('groupIdlist', serialize($groups));
2630		}
2631
2632		return $groups;
2633	}
2634
2635	function list_can_include_groups($group)
2636	{
2637		$list = [];
2638		$query = 'select `groupName` from `users_groups`';
2639		$result = $this->query($query);
2640		while ($res = $result->fetchRow()) {
2641			if ($res['groupName'] != $group) {
2642				$includedGroups = $this->get_included_groups($res['groupName']);
2643				if (! in_array($group, $includedGroups)) {
2644					$list[] = $res['groupName'];
2645				}
2646			}
2647		}
2648		return $list;
2649	}
2650
2651	function list_all_groups_with_permission()
2652	{
2653		$groups = array_map(function ($g) {
2654			return ['groupName' => $g];
2655		}, $this->list_all_groups());
2656
2657		$filtered = Perms::filter(
2658			['type' => 'group'],
2659			'object',
2660			$groups,
2661			['object' => 'groupName'],
2662			'group_view'
2663		);
2664
2665		return array_map(function ($g) {
2666			return $g['groupName'];
2667		}, $filtered);
2668	}
2669
2670
2671	function remove_user($user)
2672	{
2673		$cachelib = TikiLib::lib('cache');
2674
2675		if ($user == 'admin') {
2676			return false;
2677		}
2678
2679		$userexists_cache[$user] = null;
2680
2681		$userId = $this->getOne('select `userId` from `users_users` where `login` = ?', [$user]);
2682
2683		$groupTracker = $this->get_tracker_usergroup($user);
2684		if ($groupTracker && $groupTracker['usersTrackerId']) {
2685			$trklib = TikiLib::lib('trk');
2686
2687			$itemId = $trklib->get_item_id($groupTracker['usersTrackerId'], $groupTracker['usersFieldId'], $user);
2688			if ($itemId) {
2689				$trklib->remove_tracker_item($itemId);
2690			}
2691		}
2692
2693		$tracker = $this->get_usertracker($userId);
2694		if ($tracker && $tracker['usersTrackerId']) {
2695			$trklib = TikiLib::lib('trk');
2696
2697			$itemId = $trklib->get_item_id($tracker['usersTrackerId'], $tracker['usersFieldId'], $user);
2698			if ($itemId) {
2699				$trklib->remove_tracker_item($itemId);
2700			}
2701		}
2702
2703		$query = 'delete from `users_users` where binary `login` = ?';
2704		$result = $this->query($query, [ $user ]);
2705		$query = 'delete from `users_usergroups` where `userId`=?';
2706		$result = $this->query($query, [ $userId ]);
2707		$query = 'delete from `tiki_user_login_cookies` where `userId`=?';
2708		$result = $this->query($query, [ $userId ]);
2709		$query = 'delete from `tiki_user_watches` where binary `user`=?';
2710		$result = $this->query($query, [$user]);
2711		$query = 'delete from `tiki_user_preferences` where binary `user`=?';
2712		$result = $this->query($query, [$user]);
2713		$query = 'delete from `tiki_newsletter_subscriptions` where binary `email`=? and `isUser`=?';
2714		$result = $this->query($query, [$user, 'y']);
2715		$this->query('delete from `tiki_user_reports` where `user` = ?', [$user]);
2716		$this->query('delete from `tiki_user_reports_cache` where `user` = ?', [$user]);
2717		$this->query('delete from `tiki_user_mailin_struct` where `username` = ?', [$user]);
2718
2719		$cachelib->invalidate('userslist');
2720
2721		TikiLib::events()->trigger('tiki.user.delete', ['type' => 'user', 'object' => $user]);
2722
2723		return true;
2724	}
2725
2726	function change_login($from, $to)
2727	{
2728		global $user;
2729		$cachelib = TikiLib::lib('cache');
2730
2731		if ($from == 'admin') {
2732			return false;
2733		}
2734
2735		$userexists_cache[$user] = null;
2736
2737		$userId = $this->getOne('select `userId` from `users_users` where `login` = ?', [$from]);
2738
2739		if ($userId) {
2740			$this->query('update `users_users` set `login`=? where `userId` = ?', [$to, (int)$userId]);
2741			$this->query('update `tiki_wiki_attachments` set `user`=? where `user`=?', [$to, $from]);
2742			$this->query('update `tiki_webmail_contacts` set `user`=? where `user`=?', [$to, $from]);
2743			$this->query('update `tiki_webmail_contacts_fields` set `user`=? where `user`=?', [$to, $from]);
2744			$this->query('update `tiki_userpoints` set `user`=? where `user`=?', [$to, $from]);
2745			$this->query('update `tiki_userfiles` set `user`=? where `user`=?', [$to, $from]);
2746			$this->query('update `tiki_user_watches` set `user`=? where `user`=?', [$to, $from]);
2747			$this->query('update `tiki_user_votings` set `user`=? where `user`=?', [$to, $from]);
2748			$this->query('update `tiki_user_tasks` set `user`=? where `user`=?', [$to, $from]);
2749			$this->query('update `tiki_user_tasks` set `creator`=? where `creator`=?', [$to, $from]);
2750			$this->query('update `tiki_user_tasks_history` set `lasteditor`=? where `lasteditor`=?', [$to, $from]);
2751			$this->query('update `tiki_user_taken_quizzes` set `user`=? where `user`=?', [$to, $from]);
2752			$this->query('update `tiki_user_quizzes` set `user`=? where `user`=?', [$to, $from]);
2753			$this->query('update `tiki_user_preferences` set `user`=? where `user`=?', [$to, $from]);
2754			$this->query('update `tiki_user_postings` set `user`=? where `user`=?', [$to, $from]);
2755			$this->query('update `tiki_user_notes` set `user`=? where `user`=?', [$to, $from]);
2756			$this->query('update `tiki_user_menus` set `user`=? where `user`=?', [$to, $from]);
2757			$this->query('update `tiki_user_bookmarks_urls` set `user`=? where `user`=?', [$to, $from]);
2758			$this->query('update `tiki_user_bookmarks_folders` set `user`=? where `user`=?', [$to, $from]);
2759			$this->query('update `tiki_user_assigned_modules` set `user`=? where `user`=?', [$to, $from]);
2760			$this->query('update `tiki_tags` set `user`=? where `user`=?', [$to, $from]);
2761			$this->query('update `tiki_suggested_faq_questions` set `user`=? where `user`=?', [$to, $from]);
2762			$this->query('update `tiki_submissions` set `author`=? where `author`=?', [$to, $from]);
2763			$this->query('update `tiki_shoutbox` set `user`=? where `user`=?', [$to, $from]);
2764			$this->query('update `tiki_sessions` set `user`=? where `user`=?', [$to, $from]);
2765			$this->query('update `tiki_semaphores` set `user`=? where `user`=?', [$to, $from]);
2766			$this->query('update `tiki_received_pages` set `receivedFromUser`=? where `receivedFromUser`=?', [$to, $from]);
2767			$this->query('update `tiki_received_articles` set `author`=? where `author`=?', [$to, $from]);
2768			$this->query('update `tiki_private_messages` set `poster`=? where `poster`=?', [$to, $from]);
2769			$this->query('update `tiki_private_messages` set `toNickname`=? where `toNickname`=?', [$to, $from]);
2770			$this->query('update `tiki_pages` set `user`=? where `user`=?', [$to, $from]);
2771			$this->query('update `tiki_pages` set `creator`=? where `creator`=?', [$to, $from]);
2772			$this->query('update `tiki_page_footnotes` set `user`=? where `user`=?', [$to, $from]);
2773			$this->query('update `tiki_newsletters` set `author`=? where `author`=?', [$to, $from]);
2774			$this->query('update `tiki_minical_events` set `user`=? where `user`=?', [$to, $from]);
2775			$this->query('update `tiki_minical_topics` set `user`=? where `user`=?', [$to, $from]);
2776			$this->query('update `tiki_mailin_accounts` set `user`=? where `user`=?', [$to, $from]);
2777			$this->query('update `tiki_live_support_requests` set `operator`=? where `operator`=?', [$to, $from]);
2778			$this->query('update `tiki_live_support_requests` set `tiki_user`=? where `tiki_user`=?', [$to, $from]);
2779			$this->query('update `tiki_live_support_requests` set `user`=? where `user`=?', [$to, $from]);
2780			$this->query('update `tiki_live_support_operators` set `user`=? where `user`=?', [$to, $from]);
2781			$this->query('update `tiki_live_support_messages` set `user`=? where `user`=?', [$to, $from]);
2782			$this->query('update `tiki_live_support_messages` set `username`=? where `username`=?', [$to, $from]);
2783			$this->query('update `tiki_images` set `user`=? where `user`=?', [$to, $from]);
2784			$this->query('update `tiki_history` set `user`=? where `user`=?', [$to, $from]);
2785			$this->query('update `tiki_galleries` set `user`=? where `user`=?', [$to, $from]);
2786			$this->query('update `tiki_forums_reported` set `user`=? where `user`=?', [$to, $from]);
2787			$this->query('update `tiki_forums_queue` set `user`=? where `user`=?', [$to, $from]);
2788			$this->query('update `tiki_forums` set `moderator`=? where `moderator`=?', [$to, $from]);
2789			$this->query('update `tiki_forum_reads` set `user`=? where `user`=?', [$to, $from]);
2790			$this->query('update `tiki_files` set `user`=? where `user`=?', [$to, $from]);
2791			$this->query('update `tiki_files` set `lastModifUser`=? where `lastModifUser`=?', [$to, $from]);
2792			$this->query('update `tiki_files` set `lockedby`=? where `lockedby`=?', [$to, $from]);
2793			$this->query('update `tiki_file_galleries` set `user`=? where `user`=?', [$to, $from]);
2794			$this->query('update `tiki_file_drafts` set `user`=? where `user`=?', [$to, $from]);
2795			$this->query('update `tiki_copyrights` set `userName`=? where `userName`=?', [$to, $from]);
2796			$this->query('update `tiki_comments` set `userName`=? where `userName`=?', [$to, $from]);
2797			$this->query('update `tiki_chat_users` set `nickname`=? where `nickname`=?', [$to, $from]);
2798			$this->query('update `tiki_chat_messages` set `poster`=? where `poster`=?', [$to, $from]);
2799			$this->query('update `tiki_chat_channels` set `moderator`=? where `moderator`=?', [$to, $from]);
2800			$this->query('update `tiki_calendars` set `user`=? where `user`=?', [$to, $from]);
2801			$this->query('update `tiki_calendar_roles` set `username`=? where `username`=?', [$to, $from]);
2802			$this->query('update `tiki_calendar_items` set `user`=? where `user`=?', [$to, $from]);
2803			$this->query('update `tiki_blogs` set `user`=? where `user`=?', [$to, $from]);
2804			$this->query('update `tiki_blog_posts` set `user`=? where `user`=?', [$to, $from]);
2805			$this->query('update `tiki_banning` set `user`=? where `user`=?', [$to, $from]);
2806			$this->query('update `tiki_banners` set `client`=? where `client`=?', [$to, $from]);
2807			$this->query('update `tiki_articles` set `author`=? where `author`=?', [$to, $from]);
2808			$this->query('update `tiki_actionlog` set `user`=? where `user`=?', [$to, $from]);
2809			$this->query('update `messu_messages` set `user`=? where `user`=?', [$to, $from]);
2810			$this->query('update `messu_messages` set `user_from`=? where `user_from`=?', [$to, $from]);
2811			$this->query('update `tiki_newsletter_subscriptions` set `email`=? where `email`=? and `isUser`=?', [$to, $from, 'y']);
2812			$this->query('update `tiki_object_relations` set `source_itemId`=? where source_type="user" and `source_itemId`=?', [$to, $from]);
2813			$this->query('update `tiki_object_relations` set `target_itemId`=? where target_type="user" and `target_itemId`=?', [$to, $from]);
2814			$this->query('update `tiki_freetagged_objects` set `user`=? where `user`=?', [$to, $from]);
2815
2816			$this->query(
2817				'update `tiki_tracker_item_fields`ttif' .
2818				' left join `tiki_tracker_fields` ttf on (ttif.`fieldId`=ttf.`fieldId`)' .
2819				' set `value`=? where ttif.`value`=? and ttf.`type`=?',
2820				[$to, $from, 'u']
2821			);
2822			$this->query('update `tiki_tracker_items` set `createdBy`=? where `createdBy`=?', [$to, $from]);
2823			$this->query('update `tiki_tracker_items` set `lastModifBy`=? where `lastModifBy`=?', [$to, $from]);
2824
2825			$result = $this->query("select `fieldId`, `itemChoices` from `tiki_tracker_fields` where `type`='u'");
2826
2827			while ($res = $result->fetchRow()) {
2828				$this->query('update `tiki_tracker_item_fields` set `value`=? where `value`=? and `fieldId`=?', [$to, $from, $res['fieldId']]);
2829
2830				$u = ($res['itemChoices'] != '' ) ? unserialize($res['itemChoices']) : [];
2831
2832				if ($value = array_search($from, $u)) {
2833					$u[$value] = $to;
2834					$u = serialize($u);
2835					$this->query('update `tiki_tracker_fields` set `itemChoices`=? where `fieldId`=?', [$u, $res['fieldId']]);
2836				}
2837			}
2838			$cachelib->invalidate('userslist');
2839			TikiLib::events()->trigger('tiki.user.update', ['type' => 'user', 'object' => $from]);
2840			TikiLib::events()->trigger('tiki.user.update', ['type' => 'user', 'object' => $to]);
2841			return true;
2842		} else {
2843			return false;
2844		}
2845	}
2846
2847	function remove_group($group)
2848	{
2849		if ($group == 'Anonymous' || $group == 'Registered') {
2850			return false;
2851		}
2852		$info = $this->get_group_info($group);
2853
2854		$query = 'delete from `tiki_group_inclusion` where `groupName` = ? or `includeGroup` = ?';
2855		$result = $this->query($query, [$group, $group]);
2856
2857		$query = [];
2858		$query[] = 'delete from `users_groups` where `groupName` = ?';
2859		$query[] = 'delete from `users_usergroups` where `groupName` = ?';
2860		$query[] = 'delete from `users_grouppermissions` where `groupName` = ?';
2861		$query[] = 'delete from `users_objectpermissions` where `groupName` = ?';
2862		$query[] = 'delete from `tiki_newsletter_groups` where `groupName` = ?';
2863		$query[] = 'delete from `tiki_group_watches` where `group` = ?';
2864
2865		foreach ($query as $q) {
2866			$this->query($q, [$group]);
2867		}
2868
2869		$this->query('update `users_users` set `default_group`=? where `default_group`=?', ['Registered', $group]);
2870
2871		if (isset($info['id'])) {
2872			TikiLib::lib('categ')->detach_managed_category($info['id']);
2873
2874			TikiLib::lib('attribute')->delete_objects_with( 'tiki.category.templatedgroupid', $info['id']);
2875			TikiLib::lib('attribute')->delete_objects_with( 'tiki.menu.templatedgroupid', $info['id']);
2876		}
2877
2878		TikiLib::events()->trigger('tiki.group.delete', [
2879			'type' => 'group',
2880			'object' => $group,
2881		]);
2882
2883		$cachelib = TikiLib::lib('cache');
2884		$cachelib->invalidate('grouplist');
2885		$cachelib->invalidate('group_theme_' . $group);
2886
2887		return $result;
2888	}
2889
2890	function get_user_default_group($user)
2891	{
2892		if (! isset($user)) {
2893			return 'Anonymous';
2894		}
2895		if (isset($_SESSION['u_info']) && $user == $_SESSION['u_info']['login']) {
2896			if (isset($_SESSION['u_info']['group']) && is_string($_SESSION['u_info']['group'])) {
2897				return $_SESSION['u_info']['group'];
2898			} elseif (isset($_SESSION['u_info']['group']['groupName']) && is_string($_SESSION['u_info']['group']['groupName'])) {
2899				return $_SESSION['u_info']['group']['groupName'];
2900			}
2901		}
2902		$query = 'select `default_group` from `users_users` where `login` = ?';
2903		$result = $this->getOne($query, [$user]);
2904		$ret = '';
2905		if (! is_null($result) && $result != '') {
2906			$ret = $result;
2907		} else {
2908			$groups = $this->get_user_groups($user);
2909			foreach ($groups as $gr) {
2910				if ($gr != 'Anonymous' and $gr != 'Registered' and $gr != '') {
2911					$ret = $gr;
2912					break;
2913				}
2914			}
2915			if (! $ret) {
2916				$ret = 'Registered';
2917			}
2918		}
2919		return $ret;
2920	}
2921
2922	/**
2923	 * Returns the wiki page name for the current user and checks for useGroupHome pref
2924	 *
2925	 * @param string $user  current logged in user
2926	 * @return string       page name
2927	 */
2928	function get_user_default_homepage($user)
2929	{
2930		global $prefs;
2931
2932		if ($prefs['useGroupHome'] !== 'y') {
2933			return $prefs['wikiHomePage'];
2934		}
2935
2936		$home = '';
2937		$group = $this->get_user_default_group($user);
2938
2939		if ($group) {
2940			$home = $this->get_group_home($group);
2941		}
2942		if (! $home) {	// work through the other groups this user is a member of
2943			$query = "select g.`groupHome`, g.`groupName`" .
2944				" from `users_usergroups` as gu, `users_users` as u, `users_groups`as g" .
2945				" where gu.`userId`= u.`userId` and u.`login`=? and gu.`groupName`= g.`groupName` and g.`groupHome` != '' and g.`groupHome` is not null";
2946
2947			$result = $this->query($query, [$user]);
2948
2949			while ($res = $result->fetchRow()) {
2950				if ($home != '') {
2951					$groups = $this->get_included_groups($res['groupName']);
2952					if (in_array($group, $groups)) {
2953						$home = $res['groupHome'];
2954						$group = $res['groupName'];
2955					}
2956				} else {
2957					$home = $res['groupHome'];
2958					$group = $res['groupName'];
2959				}
2960			}
2961		}
2962		$home = $this->best_multilingual_page($home);
2963
2964		$validHome = substr($home, 0, 1) === '/'
2965			|| preg_match(',^https?://,', $home)
2966			|| TikiLib::lib('tiki')->page_exists($home);
2967
2968		if (! $validHome && $prefs['tikiIndex'] === 'tiki-index.php') {
2969			$home = $prefs['wikiHomePage'];
2970		}
2971
2972		return $home;
2973	}
2974
2975	function best_multilingual_page($page)
2976	{
2977		global $prefs;
2978
2979		if ($prefs['feature_multilingual'] != 'y') {
2980			return ($page);
2981		}
2982
2983		$info = $this->get_page_info($page);
2984		$multilinguallib = TikiLib::lib('multilingual');
2985		$bestLangPageId = $multilinguallib->selectLangObj('wiki page', $info['page_id'], $prefs['language']);
2986
2987		if ($info['page_id'] == $bestLangPageId) {
2988			return $page;
2989		}
2990
2991		return $this->get_page_name_from_id($bestLangPageId);
2992	}
2993
2994	/* Returns a theme/style for this ithe default group of the current user. */
2995	function get_user_group_theme()
2996	{
2997		global $user;
2998		$group = $this->get_user_default_group($user);
2999
3000		$cachelib = TikiLib::lib('cache');
3001		$k = 'group_theme_' . $group;
3002
3003		if ($data = $cachelib->getCached($k)) {
3004			$return = $data;
3005		} elseif (! empty($group)) {
3006			$query = 'select `groupTheme` from `users_groups` where `groupName` = ?';
3007			$return = $this->getOne($query, [$group]);
3008			$cachelib->cacheItem($k, $return);
3009		}
3010		return $return;
3011	}
3012
3013	/* Returns a default category for user's default_group
3014	*/
3015	function get_user_group_default_category($user)
3016	{
3017		$query = 'select `groupDefCat` from `users_groups` ug, `users_users` uu where `login` = ? and ug.`groupName` = uu.`default_group`';
3018		$result = $this->getOne($query, [$user]);
3019
3020		return $result;
3021	}
3022
3023	//modified get_user_groups() to know if the user is part of the group directly or through groups inclusion
3024	function get_user_groups_inclusion($user)
3025	{
3026		$userid = $this->get_user_id($user);
3027
3028		$query = 'select `groupName` from `users_usergroups` where `userId`=?';
3029		$result = $this->query($query, [(int)$userid]);
3030		$real = []; //really assigned groups (not (only) included)
3031		$ret = [];
3032
3033		while ($res = $result->fetchRow()) {
3034			$real[] = $res['groupName'];
3035			foreach ($this->get_included_groups($res['groupName']) as $group) {
3036				$ret[$group] = 'included';
3037			}
3038		}
3039
3040		foreach ($real as $group) {
3041			$ret[$group] = 'real';
3042		}
3043
3044		return $ret;
3045	}
3046
3047	function get_group_home($group)
3048	{
3049		$query = 'select `groupHome` from `users_groups` where `groupName`=?';
3050		$result = $this->getOne($query, [$group]);
3051		$ret = '';
3052
3053		if (! is_null($result)) {
3054			$ret = $result;
3055		}
3056
3057		return $ret;
3058	}
3059
3060	/**
3061	 * Return information about users that belong to a
3062	 * specific group
3063	 *
3064	 * @param string $group group name
3065	 * @param int $offset
3066	 * @param int $max
3067	 * @param string $what which user fields to retrieve
3068	 * @param string $sort_mode
3069	 * @return array list of users
3070	 */
3071	function get_group_users($group, $offset = 0, $max = -1, $what = 'login', $sort_mode = 'login_asc')
3072	{
3073		if (empty($group)) {
3074			return [];
3075		}
3076
3077		$w = $what == '*' ? 'uu.*, ug.`created`, ug.`expire` ' : "uu.`$what`";
3078
3079		if (strpos($sort_mode, 'created_') !== false) {
3080			$sort_mode = 'ug.' . $sort_mode;	// avoid ambiguity of created column
3081		}
3082		$query = "select $w from `users_users` uu, `users_usergroups` ug where uu.`userId`=ug.`userId` and `groupName`=? order by " .
3083						$this->convertSortMode($sort_mode);
3084
3085		$result = $this->fetchAll($query, $group, $max, $offset);
3086		$ret = [];
3087
3088		foreach ($result as $res) {
3089			$ret[] = ($what == '*') ? $res : $res[$what];
3090		}
3091
3092		return $ret;
3093	}
3094
3095	function get_recur_group_users($group, $recur = 0, $what = 'login')
3096	{
3097		$users = $this->get_group_users($group, 0, -1, $what);
3098		if ($recur > 0) {
3099			$includings = $this->get_including_groups($group, 'n');
3100			--$recur;
3101			foreach ($includings as $including) {
3102				$users = array_merge($users, $this->get_recur_group_users($including, $recur, $what));
3103			}
3104		}
3105		return $users;
3106	}
3107
3108	function get_user_info($user, $inclusion = false, $field = 'login')
3109	{
3110		global $prefs;
3111		if ($field == 'userId') {
3112			$user = (int)$user;
3113		} elseif ($field != 'login') {
3114			return false;
3115		}
3116
3117		$result = $this->query("select * from `users_users` where `$field`=?", [$user]);
3118		if ($res = $result->fetchRow()) {
3119			$res['groups'] = ( $inclusion ) ? $this->get_user_groups_inclusion($res['login']) : $this->get_user_groups($res['login']);
3120			$res['age'] = ( ! isset($res['registrationDate']) ) ? 0 : $this->now - $res['registrationDate'];
3121
3122			if ($prefs['login_is_email'] == 'y' && isset($res['login']) && $res['login'] != 'admin') {
3123				$res['email'] = $res['login'];
3124			}
3125
3126			$res['editable'] = $this->user_can_be_edited($res['login']);
3127
3128			return $res;
3129		}
3130	}
3131
3132	function get_userid_info($user, $inclusion = false)
3133	{
3134		return $this->get_user_info($user, $inclusion, 'userId');
3135	}
3136
3137	/**
3138	 * Helps recognize nicknames which anonymous users have chosen
3139	 * A leading tab is for recognizing anonymous entries. Real usernames don't start with a tab
3140	 * @param string $username	user nickname or name
3141	 * @return string				string which should be displayed
3142	 */
3143	function distinguish_anonymous_users($username = "")
3144	{
3145		if (empty($username)) {
3146			return "";
3147		}
3148		if ($username[0] == "\t") {
3149			$username = $username . ' (' . tra('unverified') . ')';
3150		}
3151		return $username;
3152	}
3153
3154
3155	/**
3156	 * Creates DOM tag for user info with popup or not depending on prefs etc
3157	 * @param string $auser     user to find info for (current user if empty)
3158	 * @param string $body      content of the anchor tag (user name if empty)
3159	 * @param string $class		add a class to the a tag (default userlink)
3160	 * @return string           HTML anchor tag
3161	 */
3162	function build_userinfo_tag($auser = '', $body = '', $class = 'userlink', $show_popup = 'y', $elementId = '')
3163	{
3164		global $user, $prefs;
3165
3166		if (! $auser) {
3167			$auser = $user;
3168		}
3169		$realn = $this->clean_user($auser);
3170
3171		if (! $body) {
3172			$body = $realn;
3173		}
3174
3175		if ($elementId) {
3176			$idStr = " id=\"$elementId\"";
3177		} else {
3178			$idStr = '';
3179		}
3180
3181		$isSelf = ($auser === $user) ? true : false;
3182		// Only process if feature_friends enabled, user_information public or we query ourselfs
3183		if (($this->get_user_preference($auser, 'user_information', 'public') != 'public') && ($prefs['feature_friends'] != 'y') && ! $isSelf) {
3184			return "<span{$idStr}>$body</span>";
3185		}
3186
3187
3188		$id = $this->get_user_id($auser);
3189		if ($id == -1) {
3190			return $body;
3191		}
3192
3193		$extra = '';
3194		if ($show_popup == "n") {
3195			//do nothing for adding a tip
3196			$title = '';
3197		} elseif ($prefs['feature_community_mouseover'] == 'y' && ($this->get_user_preference($auser, 'show_mouseover_user_info', 'y') == 'y' || $prefs['feature_friends'] == 'y')) {
3198			$data = TikiLib::lib('service')->getUrl([
3199				'controller' => 'user',
3200				'action' => 'info',
3201				'username' => $auser,
3202			]);
3203			$extra .= ' data-ajaxtips="' . htmlspecialchars($data, ENT_QUOTES) . '"';
3204			$class .= ' ajaxtips';
3205
3206			if ($auser === $user) {
3207				$title = tra('Your Information');
3208			} else {
3209				$title = tra('User Information');
3210			}
3211		} elseif ($prefs['user_show_realnames'] == 'y') {
3212			$class .= ' tips';
3213			$title = tr('User') . ':' . $realn;
3214		} else {
3215			$class .= ' tips';
3216			$title = tr('User') . ':' . $auser;
3217		}
3218
3219		if (empty($prefs['urlOnUsername'])) {
3220			$url = 'tiki-user_information.php?userId=' . $id;
3221			if ($prefs['feature_sefurl'] == 'y') {
3222				include_once('tiki-sefurl.php');
3223				$url = filter_out_sefurl($url);
3224			}
3225		} else {
3226			$url = preg_replace(
3227				['/%userId%/', '/%user%/'],
3228				[$id, $auser],
3229				$prefs['urlOnUsername']
3230			);
3231		}
3232
3233		$lat = $this->get_user_preference($auser, 'lat');
3234		$lon = $this->get_user_preference($auser, 'lon');
3235		$zoom = $this->get_user_preference($auser, 'zoom');
3236
3237		if (! ($lat == 0 && $lon == 0)) {
3238			$class .= ' geolocated';
3239			$extra .= ' data-geo-lat="' . $lat . '" data-geo-lon="' . $lon . '"';
3240
3241			if ($zoom) {
3242				$extra .= ' data-geo-zoom="' . $zoom . '"';
3243			}
3244		}
3245
3246		if ($title) {
3247			$titleStr = ' title="' . htmlspecialchars($title, ENT_QUOTES) . '"';
3248		} else {
3249			$titleStr = '';
3250		}
3251
3252		$body = "<a{$idStr}{$titleStr} href=\"$url\" class=\"$class\"$extra>" . $body . '</a>';
3253
3254		return $body;
3255	}
3256
3257
3258
3259	// UNRELIABLE. In particular, lastLogin and currentLogin aren't properly maintained due to missing user_details_ cache invalidation
3260	// refactoring to use new cachelib instead of global var in memory - batawata 2006-02-07
3261	function get_user_details($login, $useCache = true)
3262	{
3263		$cachelib = TikiLib::lib('cache');
3264
3265		$cacheKey = 'user_details_' . $login;
3266
3267		if (! $useCache || ! $user_details = $cachelib->getSerialized($cacheKey)) {
3268			$user_details = [];
3269
3270			$query = 'SELECT `userId`, `login`, `email`, `lastLogin`, `currentLogin`,' .
3271							' `registrationDate`, `created`, `avatarName`, `avatarSize`,' .
3272							' `avatarFileType`, `avatarLibName`, `avatarType`' .
3273							' FROM `users_users` WHERE `login` = ?';
3274
3275			$result = $this->query($query, [$login]);
3276
3277			$user_details['info'] = $result->fetchRow();
3278
3279			$query = 'SELECT `prefName` , `value` FROM `tiki_user_preferences` WHERE `user` = ?';
3280			$result = $this->query($query, [$login]);
3281
3282			$user_details['preferences'] = [];
3283			$aUserPrefs = ['realName', 'homePage', 'country'];
3284
3285			while ($row = $result->fetchRow()) {
3286				$user_details['preferences'][$row['prefName']] = $row['value'];
3287
3288				// atention: this is redundant, for intertiki slave mode
3289				// we insert, delete and insert again this information,
3290				// because of nature of user information as being preferences
3291				if (in_array($row['prefName'], $aUserPrefs)) {
3292					$user_details['info'][$row['prefName']] = $row['value'];
3293				}
3294			}
3295
3296			$user_details['groups'] = $this->get_user_groups($login);
3297
3298			$cachelib->cacheItem($cacheKey, serialize($user_details));
3299
3300			global $user_preferences;
3301			$user_preferences[$login] = $user_details['preferences'];
3302		}
3303
3304		return $user_details;
3305	}
3306
3307	function set_default_group($user, $group)
3308	{
3309		// if user is not in group, assign user to group before setting default group
3310		$user_groups = $this->get_user_groups($user);
3311		if (! in_array($group, $user_groups) && ! empty($group)) {
3312			$this->assign_user_to_group($user, $group);
3313		}
3314		$query = 'update `users_users` set `default_group` = ? where `login` = ?';
3315		$this->query($query, [$group, $user]);
3316
3317		if ($user == $_SESSION['u_info']['login']) {
3318			$_SESSION['u_info']['group'] = $group;
3319		}
3320	}
3321
3322	function set_email_group($user, $email)
3323	{
3324		$query = 'select `id`, `groupName`, `emailPattern` from `users_groups` where `emailPattern`!=?';
3325		$groups = $this->fetchAll($query, ['']);
3326		$nb = 0;
3327
3328		if (empty($groups)) {
3329			return 0;
3330		}
3331
3332		$userGroups = $this->get_user_groups_inclusion($user);
3333		foreach ($groups as $group) {
3334			if (! isset($userGroups[$group['groupName']]) && preg_match($group['emailPattern'], $email)) {
3335				$this->assign_user_to_group($user, $group['groupName']);
3336				$this->set_default_group($user, $group['groupName']);
3337				++$nb;
3338			}
3339		}
3340		return $nb;
3341	}
3342
3343	function refresh_set_email_group()
3344	{
3345		$users = $this->list_users();
3346		$nb = 0;
3347		foreach ($users['data'] as $user) {
3348			$nb += $this->set_email_group($user['login'], $user['email']);
3349		}
3350		return $nb;
3351	}
3352
3353	function change_permission_level($perm, $level)
3354	{
3355		$query = 'update `users_permissions` set `level` = ? where `permName` = ?';
3356		$this->query($query, [$level, $perm]);
3357
3358		$cachelib = TikiLib::lib('cache');
3359		$cachelib->empty_type_cache('fgals_perms');
3360
3361		$menulib = TikiLib::lib('menu');
3362		$menulib->empty_menu_cache();
3363	}
3364
3365	function assign_level_permissions($group, $level)
3366	{
3367		$query = 'select `permName` from `users_permissions` where `level` = ?';
3368		$result = $this->query($query, [$level]);
3369
3370		while ($res = $result->fetchRow()) {
3371			$this->assign_permission_to_group($res['permName'], $group);
3372		}
3373
3374		$cachelib = TikiLib::lib('cache');
3375		$cachelib->empty_type_cache('fgals_perms');
3376		$cachelib->invalidate("groupperms_$group");
3377
3378		$menulib = TikiLib::lib('menu');
3379		$menulib->empty_menu_cache();
3380	}
3381
3382	function remove_level_permissions($group, $level)
3383	{
3384		$query = 'select `permName` from `users_permissions` where `level` = ?';
3385		$result = $this->query($query, [$level]);
3386
3387		while ($res = $result->fetchRow()) {
3388			$this->remove_permission_from_group($res['permName'], $group);
3389		}
3390
3391		$cachelib = TikiLib::lib('cache');
3392		$cachelib->empty_type_cache('fgals_perms');
3393		$cachelib->invalidate("groupperms_$group");
3394
3395		$menulib = TikiLib::lib('menu');
3396		$menulib->empty_menu_cache();
3397	}
3398
3399	function create_dummy_level($level)
3400	{
3401		$query = 'delete from `users_permissions` where `permName` = ?';
3402		$result = $this->query($query, ['']);
3403		$query = "insert into `users_permissions`(`permName`, `level`) values('', ?)";
3404		$this->query($query, [$level]);
3405
3406		$cachelib = TikiLib::lib('cache');
3407		$cachelib->empty_type_cache('fgals_perms');
3408
3409		$menulib = TikiLib::lib('menu');
3410		$menulib->empty_menu_cache();
3411	}
3412
3413	function get_permission_levels()
3414	{
3415		$query = 'select distinct(`level`) from `users_permissions`';
3416
3417		$result = $this->query($query);
3418		$ret = [];
3419
3420		while ($res = $result->fetchRow()) {
3421			$ret[] = $res['level'];
3422		}
3423
3424		return $ret;
3425	}
3426
3427	function get_tracker_usergroup($user)
3428	{
3429		$lastRes = '';
3430		$group = $this->get_user_default_group($user);
3431		if (! empty($group)) {
3432			$lastRes = $this->get_usertrackerid($group);
3433		}
3434		if (! $lastRes) {
3435			$groups = $this->get_user_groups($user);
3436			$query = 'select `groupName`, `usersTrackerId`, `usersFieldId`' .
3437							' from `users_groups`' .
3438							' where `groupName` in ( ' . implode(' , ', array_fill(0, count($groups), '?')) .
3439							' ) and `groupName` != ? and `usersTrackerId` > 0';
3440
3441			$groups[] = 'Anonymous';
3442			$result = $this->query($query, $groups);
3443
3444			while ($res = $result->fetchRow()) {
3445				$lastRes = $res;
3446				if ($res['groupName'] != 'Registered') {
3447					return 	$res ;
3448				}
3449			}
3450		}
3451
3452		return $lastRes;
3453	}
3454
3455	function get_grouptrackerid($group)
3456	{
3457		$res = $this->query('select `groupTrackerId`,`groupFieldId` from `users_groups` where `groupName`=?', [$group]);
3458		$ret = $res->fetchRow();
3459
3460		if (! $ret['groupTrackerId'] or ! $ret['groupFieldId']) {
3461			$groups = $this->get_included_groups($group);
3462			foreach ($groups as $gr) {
3463				$res = $this->query('select `groupTrackerId`,`groupFieldId` from `users_groups` where `groupName`=?', [$gr]);
3464				$ret = $res->fetchRow();
3465				if ($ret['groupTrackerId'] and $ret['groupFieldId']) {
3466					return $ret;
3467				}
3468			}
3469		} else {
3470			return $ret;
3471		}
3472		return false;
3473	}
3474
3475	/**
3476	 * @param $group
3477	 * @param $include_groups
3478	 * @throws Exception
3479	 */
3480	public function manage_group($group, $include_groups): void
3481	{
3482		$oldIncluded = $this->get_included_groups($group, false);
3483		$this->remove_all_inclusions($group);
3484		$info = $this->get_group_info($group);
3485
3486		if (isset($include_groups) and is_array($include_groups)) {
3487			$oldIncludes = array_diff($oldIncluded, $include_groups);
3488			$oldGroups = $this->get_group_info(array_values($oldIncludes));
3489			$parentGroupsIds = array_map(function ($item) {
3490				return $item["id"];
3491			}, array_filter($oldGroups, function ($item) {
3492				return $item["isTplGroup"] == "y";
3493			}));
3494			if (!empty($parentGroupsIds)) {
3495				TikiLib::lib('categ')->detach_managed_category($info["id"], $parentGroupsIds);
3496			}
3497
3498			foreach ($include_groups as $include) {
3499				if ($include && $group != $include) {
3500					$this->group_inclusion($group, $include);
3501				}
3502			}
3503
3504			$groups = $this->get_group_info($include_groups);
3505			$templateGroups = array_filter($groups, function ($item) {
3506				return $item["isTplGroup"] == "y";
3507			});
3508			foreach ($templateGroups as $templateGroup) {
3509				$categories = TikiLib::lib('categ')->get_managed_categories($templateGroup["id"]);
3510				$managedIds = array_unique(array_map(function ($item) {
3511					return $item["categId"];
3512				}, $categories));
3513
3514				foreach ($managedIds as $managedId) {
3515					TikiLib::lib('categ')->manage_sub_categories($managedId);
3516				}
3517			}
3518		}
3519	}
3520
3521	function get_usertrackerid($group)
3522	{
3523		$res = $this->query('select `usersTrackerId`,`usersFieldId` from `users_groups` where `groupName`=?', [$group]);
3524		$ret = $res->fetchRow();
3525
3526		if (! $ret['usersTrackerId'] or ! $ret['usersFieldId']) {
3527			$groups = $this->get_included_groups($group);
3528			foreach ($groups as $gr) {
3529				$res = $this->query('select `usersTrackerId`,`usersFieldId` from `users_groups` where `groupName`=?', [$gr]);
3530				$ret = $res->fetchRow();
3531				if ($ret['usersTrackerId'] and $ret['usersFieldId']) {
3532					return $ret;
3533				}
3534			}
3535		} else {
3536			return $ret;
3537		}
3538		return false;
3539	}
3540
3541
3542	function get_usertracker($uid)
3543	{
3544		if ($utr = $this->get_userid_info($uid)) {
3545			$utr['usersTrackerId'] = '';
3546			foreach ($utr['groups'] as $gr) {
3547				$utrid = $this->get_usertrackerid($gr);
3548				if (! empty($utrid['usersTrackerId']) && ! empty($utrid['usersFieldId'])) {
3549					$utrid['group'] = $gr;
3550					$utrid['user'] = $utr['login'];
3551					$utr = $utrid;
3552					break;
3553				}
3554			}
3555			return $utr;
3556		}
3557	}
3558
3559	function get_enabled_permissions()
3560	{
3561		global $prefs;
3562
3563		$raw = $this->get_raw_permissions();
3564		$out = [];
3565
3566		foreach ($raw as $permission) {
3567			$valid = empty($permission['prefs']);
3568
3569			if (! $valid) {
3570				foreach ($permission['prefs'] as $name) {
3571					if (isset($prefs[$name]) && $prefs[$name] == 'y') {
3572						$valid = true;
3573						break;
3574					}
3575				}
3576			}
3577
3578			if ($valid) {
3579				$out[$permission['name']] = $permission;
3580			}
3581		}
3582
3583		return $out;
3584	}
3585
3586	function get_permission_names_for($type)
3587	{
3588		// Compatibility hack without which no article results are returned by mysql full-text searches (basic search)
3589		if ($type == "cms") {
3590			$type = "articles";
3591		}
3592		$raw = $this->get_permissions(0, -1, 'permName_asc', '', $type);
3593		$out = [];
3594
3595		foreach ($raw['data'] as $permission) {
3596			$out[] = $permission['name'];
3597		}
3598
3599		return $out;
3600	}
3601
3602	/**
3603	 * Function for sorting permission in tiki-objectpermissions.php
3604	 * @param $permissions
3605	 * @return array $permissions
3606	 */
3607	private function getSortingPermissions($permissions)
3608	{
3609
3610		$file = 'db/config/tiki-objectpermissions_order.yml';
3611
3612		if (! file_exists($file)) {
3613			return $permissions;
3614		}
3615
3616		$content = file_get_contents($file);
3617
3618		try {
3619			$order = Yaml::parse($content);
3620		} catch (ParseException $e) {
3621			$logslib = TikiLib::lib('logs');
3622			$logslib->add_log('System', $file . ' - ' . $e->getMessage());
3623
3624			return $permissions;
3625		}
3626
3627		$step = 100;
3628		$maxTypes = 0;
3629		$typeWeights = [];
3630		$permissionWeights = [];
3631		$checkYaml = [];
3632
3633		foreach ($permissions as $perCheck) {
3634			$checkYaml[$perCheck['type']][$perCheck['name']] = ['result' => 1];
3635		}
3636
3637		foreach ($order as $key => $permissionList) {
3638			if (! isset($typeWeights[$key])) {
3639				$typeWeights[$key] = ['weight' => $maxTypes, 'next' => 0];
3640				$maxTypes += $step;
3641			}
3642			$weight = $typeWeights[$key]['weight'];
3643			$next = $typeWeights[$key]['next'];
3644			foreach ($permissionList as $permission) {
3645				if (! array_key_exists('tiki_p_' . $permission, $checkYaml[$key])) {
3646					continue;
3647				}
3648
3649				if ($checkYaml[$key]['tiki_p_' . $permission]['result'] == 1) {
3650					$permissionWeights['tiki_p_' . $permission] = $weight + $next++;
3651				}
3652			}
3653			$typeWeights[$key]['next'] = $next;
3654		}
3655
3656		foreach ($permissions as $permission) {
3657			if (isset($permissionWeights[$permission['name']])) {
3658				continue;
3659			}
3660			if (! isset($typeWeights[$permission['type']])) {
3661				$typeWeights[$permission['type']] = ['weight' => $maxTypes, 'next' => 0];
3662				$maxTypes += $step;
3663			}
3664			$permissionWeights[$permission['name']] = $typeWeights[$permission['type']]['weight'] + $typeWeights[$permission['type']]['next']++;
3665		}
3666
3667		usort($permissions, function ($a, $b) use ($permissionWeights) {
3668			$result = $permissionWeights[$a['name']] - $permissionWeights[$b['name']];
3669			return $result;
3670		});
3671
3672		return $permissions;
3673	}
3674
3675	private function get_raw_permissions()
3676	{
3677		static $permissions;
3678
3679		// Avoid multiple unserialize per page
3680		if ($permissions) {
3681			return $permissions;
3682		}
3683
3684		global $prefs;
3685		$cachelib = TikiLib::lib('cache');
3686
3687		if ($permissions = $cachelib->getSerialized('rawpermissions' . $prefs['language'])) {
3688			return $permissions;
3689		}
3690
3691		/**
3692		 * Define master permissions array
3693		 *
3694		 * NOTE: This is the order they appear in tiki-objectpermissions.php
3695		 *       and it's important to keep them grouped by 'type'
3696		 *
3697		 */
3698
3699		$permissions = [
3700			[
3701				'name' => 'tiki_p_acct_create_book',
3702				'description' => tra('Can create/close a book'),
3703				'level' => 'admin',
3704				'type' => 'accounting',
3705				'admin' => false,
3706				'prefs' => ['feature_accounting'],
3707				'scope' => 'global',
3708			],
3709			[
3710				'name' => 'tiki_p_acct_manage_accounts',
3711				'description' => tra('Can create/edit/lock accounts'),
3712				'level' => 'admin',
3713				'type' => 'accounting',
3714				'admin' => true,
3715				'prefs' => ['feature_accounting' ],
3716				'scope' => 'global',
3717			],
3718			[
3719				'name' => 'tiki_p_acct_book',
3720				'description' => tra('Create a new transaction'),
3721				'level' => 'editors',
3722				'type' => 'accounting',
3723				'admin' => false,
3724				'prefs' => ['feature_accounting'],
3725				'scope' => 'global',
3726			],
3727			[
3728				'name' => 'tiki_p_acct_view',
3729				'description' => tra('Permission to view the journal'),
3730				'level' => 'registered',
3731				'type' => 'accounting',
3732				'admin' => false,
3733				'prefs' => ['feature_accounting' ],
3734				'scope' => 'global',
3735			],
3736			[
3737				'name' => 'tiki_p_acct_book_stack',
3738				'description' => tra('Can book into the stack where statements can be changed'),
3739				'level' => 'editors',
3740				'type' => 'accounting',
3741				'admin' => false,
3742				'prefs' => ['feature_accounting'],
3743				'scope' => 'global',
3744			],
3745			[
3746				'name' => 'tiki_p_acct_book_import',
3747				'description' => tra('Can import statements from external accounts'),
3748				'level' => 'editors',
3749				'type' => 'accounting',
3750				'admin' => false,
3751				'prefs' => ['feature_accounting' ],
3752				'scope' => 'global',
3753			],
3754			[
3755				'name' => 'tiki_p_acct_manage_template',
3756				'description' => tra('Can manage templates for recurring transactions'),
3757				'level' => 'editors',
3758				'type' => 'accounting',
3759				'admin' => false,
3760				'prefs' => ['feature_accounting'],
3761				'scope' => 'global',
3762			],
3763			[
3764				'name' => 'tiki_p_admin_cms',
3765				'description' => tra('Can admin the articles'),
3766				'level' => 'admin',
3767				'type' => 'articles',
3768				'admin' => true,
3769				'prefs' => ['feature_articles'],
3770				'scope' => 'object',
3771			],
3772			[
3773				'name' => 'tiki_p_approve_submission',
3774				'description' => tra('Can approve submissions'),
3775				'level' => 'editors',
3776				'type' => 'articles',
3777				'admin' => false,
3778				'prefs' => ['feature_articles'],
3779				'scope' => 'object',
3780			],
3781			[
3782				'name' => 'tiki_p_articles_admin_topics',
3783				'description' => tra('Can admin article topics'),
3784				'level' => 'editors',
3785				'type' => 'articles',
3786				'admin' => false,
3787				'prefs' => ['feature_articles'],
3788				'scope' => 'global',
3789			],
3790			[
3791				'name' => 'tiki_p_articles_admin_types',
3792				'description' => tra('Can admin article types'),
3793				'level' => 'editors',
3794				'type' => 'articles',
3795				'admin' => false,
3796				'prefs' => ['feature_articles'],
3797				'scope' => 'global',
3798			],
3799			[
3800				'name' => 'tiki_p_articles_read_heading',
3801				'description' => tra('Can read article headings'),
3802				'level' => 'basic',
3803				'type' => 'articles',
3804				'admin' => false,
3805				'prefs' => ['feature_articles'],
3806				'scope' => 'object',
3807			],
3808			[
3809				'name' => 'tiki_p_autoapprove_submission',
3810				'description' => tra('Submitted articles are automatically approved'),
3811				'level' => 'editors',
3812				'type' => 'articles',
3813				'admin' => false,
3814				'prefs' => ['feature_articles'],
3815				'scope' => 'global',
3816			],
3817			[
3818				'name' => 'tiki_p_edit_article',
3819				'description' => tra('Can edit articles'),
3820				'level' => 'editors',
3821				'type' => 'articles',
3822				'admin' => false,
3823				'prefs' => ['feature_articles'],
3824				'scope' => 'object',
3825			],
3826			[
3827				'name' => 'tiki_p_edit_article_user',
3828				'description' => tra('Can edit the user (owner) of articles'),
3829				'level' => 'editors',
3830				'type' => 'articles',
3831				'admin' => false,
3832				'prefs' => ['feature_articles'],
3833				'scope' => 'object',
3834			],
3835			[
3836				'name' => 'tiki_p_edit_submission',
3837				'description' => tra('Can edit submissions'),
3838				'level' => 'editors',
3839				'type' => 'articles',
3840				'admin' => false,
3841				'prefs' => ['feature_articles'],
3842				'scope' => 'object',
3843			],
3844			[
3845				'name' => 'tiki_p_read_article',
3846				'description' => tra('Can read articles (applies to article or topic level)'),
3847				'level' => 'basic',
3848				'type' => 'articles',
3849				'admin' => false,
3850				'prefs' => ['feature_articles'],
3851				'scope' => 'object',
3852			],
3853			[
3854				'name' => 'tiki_p_remove_article',
3855				'description' => tra('Can remove articles'),
3856				'level' => 'editors',
3857				'type' => 'articles',
3858				'admin' => false,
3859				'prefs' => ['feature_articles'],
3860				'scope' => 'object',
3861			],
3862			[
3863				'name' => 'tiki_p_remove_submission',
3864				'description' => tra('Can remove submissions'),
3865				'level' => 'editors',
3866				'type' => 'articles',
3867				'admin' => false,
3868				'prefs' => ['feature_articles'],
3869				'scope' => 'object',
3870			],
3871			[
3872				'name' => 'tiki_p_submit_article',
3873				'description' => tra('Can submit articles'),
3874				'level' => 'basic',
3875				'type' => 'articles',
3876				'admin' => false,
3877				'prefs' => ['feature_articles'],
3878				'scope' => 'global',
3879			],
3880			[
3881				'name' => 'tiki_p_rate_article',
3882				'description' => tra('Can rate articles'),
3883				'level' => 'basic',
3884				'type' => 'articles',
3885				'admin' => false,
3886				'prefs' => ['feature_articles'],
3887				'scope' => 'object',
3888			],
3889			[
3890				'name' => 'tiki_p_bigbluebutton_view_rec',
3891				'description' => tra('Can view recordings from past meetings'),
3892				'level' => 'basic',
3893				'type' => 'bigbluebutton',
3894				'admin' => false,
3895				'prefs' => ['bigbluebutton_feature'],
3896				'scope' => 'object',
3897			],
3898			[
3899				'name' => 'tiki_p_bigbluebutton_join',
3900				'description' => tra('Can join a meeting'),
3901				'level' => 'basic',
3902				'type' => 'bigbluebutton',
3903				'admin' => false,
3904				'prefs' => ['bigbluebutton_feature'],
3905				'scope' => 'object',
3906			],
3907			[
3908				'name' => 'tiki_p_bigbluebutton_moderate',
3909				'description' => tra('Can moderate a meeting'),
3910				'level' => 'admin',
3911				'type' => 'bigbluebutton',
3912				'admin' => false,
3913				'prefs' => ['bigbluebutton_feature'],
3914				'scope' => 'object',
3915			],
3916			[
3917				'name' => 'tiki_p_bigbluebutton_create',
3918				'description' => tra('Can create a meeting'),
3919				'level' => 'admin',
3920				'type' => 'bigbluebutton',
3921				'admin' => false,
3922				'prefs' => ['bigbluebutton_feature'],
3923				'scope' => 'object',
3924			],
3925			[
3926				'name' => 'tiki_p_xmpp_chat',
3927				'description' => tra('Can use XMPP chat'),
3928				'level' => 'admin',
3929				'type' => 'xmpp',
3930				'admin' => false,
3931				'prefs' => ['xmpp_feature'],
3932				'scope' => 'global',
3933			],
3934			[
3935				'name' => 'tiki_p_blog_admin',
3936				'description' => tra('Can admin blogs'),
3937				'level' => 'editors',
3938				'type' => 'blogs',
3939				'admin' => true,
3940				'prefs' => ['feature_blogs'],
3941				'scope' => 'object',
3942			],
3943			[
3944				'name' => 'tiki_p_assign_perm_blog',
3945				'description' => tra('Can assign perms to blog'),
3946				'level' => 'admin',
3947				'type' => 'blogs',
3948				'admin' => false,
3949				'prefs' => ['feature_blogs'],
3950				'scope' => 'object',
3951			],
3952			[
3953				'name' => 'tiki_p_blog_post',
3954				'description' => tra('Can post to a blog'),
3955				'level' => 'registered',
3956				'type' => 'blogs',
3957				'admin' => false,
3958				'prefs' => ['feature_blogs'],
3959				'scope' => 'object',
3960			],
3961			[
3962				'name' => 'tiki_p_create_blogs',
3963				'description' => tra('Can create a blog'),
3964				'level' => 'editors',
3965				'type' => 'blogs',
3966				'admin' => false,
3967				'prefs' => ['feature_blogs'],
3968				'scope' => 'global',
3969			],
3970			[
3971				'name' => 'tiki_p_read_blog',
3972				'description' => tra('Can read blogs'),
3973				'level' => 'basic',
3974				'type' => 'blogs',
3975				'admin' => false,
3976				'prefs' => ['feature_blogs'],
3977				'scope' => 'object',
3978			],
3979			[
3980				'name' => 'tiki_p_blog_post_view_ref',
3981				'description' => tra('Can view in module and feed the blog posts'),
3982				'level' => 'basic',
3983				'type' => 'blogs',
3984				'admin' => false,
3985				'prefs' => ['feature_blogs'],
3986				'scope' => 'object',
3987			],
3988			[
3989				'name' => 'tiki_p_blog_view_ref',
3990				'description' => tra('Can view in module and feed the blog'),
3991				'level' => 'basic',
3992				'type' => 'blogs',
3993				'admin' => false,
3994				'prefs' => ['feature_blogs'],
3995				'scope' => 'object',
3996			],
3997			[
3998				'name' => 'tiki_p_admin_calendar',
3999				'description' => tr('Can create/admin calendars'),
4000				'level' => 'admin',
4001				'type' => 'calendar',
4002				'admin' => true,
4003				'prefs' => ['feature_calendar'],
4004				'scope' => 'object',
4005			],
4006			[
4007				'name' => 'tiki_p_add_events',
4008				'description' => tra('Can add events in the calendar'),
4009				'level' => 'registered',
4010				'type' => 'calendar',
4011				'admin' => false,
4012				'prefs' => ['feature_calendar'],
4013				'scope' => 'object',
4014			],
4015			[
4016				'name' => 'tiki_p_change_events',
4017				'description' => tra('Can edit events in the calendar'),
4018				'level' => 'registered',
4019				'type' => 'calendar',
4020				'admin' => false,
4021				'prefs' => ['feature_calendar'],
4022				'scope' => 'object',
4023			],
4024			[
4025				'name' => 'tiki_p_view_calendar',
4026				'description' => tra('Can browse the calendar'),
4027				'level' => 'basic',
4028				'type' => 'calendar',
4029				'admin' => false,
4030				'prefs' => ['feature_calendar'],
4031				'scope' => 'object',
4032			],
4033			[
4034				'name' => 'tiki_p_view_events',
4035				'description' => tra('Can view event details'),
4036				'level' => 'registered',
4037				'type' => 'calendar',
4038				'admin' => false,
4039				'prefs' => ['feature_calendar'],
4040				'scope' => 'object',
4041			],
4042			[
4043				'name' => 'tiki_p_calendar_add_my_particip',
4044				'description' => tra('Can add himself or herself to the participants'),
4045				'level' => 'registered',
4046				'type' => 'calendar',
4047				'admin' => false,
4048				'prefs' => ['feature_calendar'],
4049				'scope' => 'object',
4050			],
4051			[
4052				'name' => 'tiki_p_calendar_add_guest_particip',
4053				'description' => tra('Can add guest to the participants'),
4054				'level' => 'registered',
4055				'type' => 'calendar',
4056				'admin' => false,
4057				'prefs' => ['feature_calendar'],
4058				'scope' => 'object',
4059			],
4060			[
4061				'name' => 'tiki_p_view_tiki_calendar',
4062				'description' => tra('Can view Tiki tools calendar'),
4063				'level' => 'basic',
4064				'type' => 'calendar',
4065				'admin' => false,
4066				'prefs' => ['feature_action_calendar'],
4067				'scope' => 'global',
4068			],
4069			[
4070				'name' => 'tiki_p_admin_categories',
4071				'description' => tra('Can admin categories'),
4072				'level' => 'admin',
4073				'type' => 'category',
4074				'admin' => true,
4075				'prefs' => ['feature_categories'],
4076				'scope' => 'object',
4077			],
4078			[
4079				'name' => 'tiki_p_view_category',
4080				'description' => tra('Can see the category in a listing'),
4081				'level' => 'basic',
4082				'type' => 'category',
4083				'admin' => false,
4084				'prefs' => ['feature_categories'],
4085				'scope' => 'object',
4086			],
4087			[
4088				'name' => 'tiki_p_add_object',
4089				'description' => tra('Can add objects to the category (tiki_p_modify_object_categories permission required)'),
4090				'level' => 'editors',
4091				'type' => 'category',
4092				'admin' => false,
4093				'prefs' => ['feature_categories'],
4094				'scope' => 'object',
4095			],
4096			[
4097				'name' => 'tiki_p_remove_object',
4098				'description' => tra('Can remove objects from the category (tiki_p_modify_object_categories permission required)'),
4099				'level' => 'editors',
4100				'type' => 'category',
4101				'admin' => false,
4102				'prefs' => ['feature_categories'],
4103				'scope' => 'object',
4104			],
4105			[
4106				'name' => 'tiki_p_assign_perm_category',
4107				'description' => tra('Can assign perms to category'),
4108				'level' => 'admin',
4109				'type' => 'category',
4110				'admin' => false,
4111				'prefs' => ['feature_categories'],
4112				'scope' => 'object',
4113			],
4114			//array(
4115			//	'name' => 'tiki_p_create_category',
4116			//	'description' => tra('Can create new categories'),
4117			//	'level' => 'admin',
4118			//	'type' => 'category',
4119			//	'admin' => false,
4120			//	'prefs' => array('feature_categories'),
4121			//	'scope' => 'global',
4122			//),
4123			[
4124				'name' => 'tiki_p_admin_chat',
4125				'description' => tra('Administrator can create channels, remove channels, etc'),
4126				'level' => 'editors',
4127				'type' => 'chat',
4128				'admin' => true,
4129				'prefs' => ['feature_minichat', 'feature_live_support'],
4130				'scope' => 'global',
4131			],
4132			[
4133				'name' => 'tiki_p_chat',
4134				'description' => tra('Can use the chat system'),
4135				'level' => 'registered',
4136				'type' => 'chat',
4137				'admin' => false,
4138				'prefs' => ['feature_minichat', 'feature_live_support'],
4139				'scope' => 'global',
4140			],
4141			[
4142				'name' => 'tiki_p_admin_received_articles',
4143				'description' => tra('Can admin received articles'),
4144				'level' => 'editors',
4145				'type' => 'comm',
4146				'admin' => false,
4147				'prefs' => ['feature_comm'],
4148				'scope' => 'global',
4149			],
4150			[
4151				'name' => 'tiki_p_admin_received_pages',
4152				'description' => tra('Can admin received pages'),
4153				'level' => 'editors',
4154				'type' => 'comm',
4155				'admin' => false,
4156				'prefs' => ['feature_comm'],
4157				'scope' => 'global',
4158			],
4159			[
4160				'name' => 'tiki_p_send_articles',
4161				'description' => tra('Can send articles to other sites'),
4162				'level' => 'editors',
4163				'type' => 'comm',
4164				'admin' => false,
4165				'prefs' => ['feature_comm'],
4166				'scope' => 'global',
4167			],
4168			[
4169				'name' => 'tiki_p_sendme_articles',
4170				'description' => tra('Can send articles to this site'),
4171				'level' => 'registered',
4172				'type' => 'comm',
4173				'admin' => false,
4174				'prefs' => ['feature_comm'],
4175				'scope' => 'global',
4176			],
4177			[
4178				'name' => 'tiki_p_sendme_pages',
4179				'description' => tra('Can send pages to this site'),
4180				'level' => 'registered',
4181				'type' => 'comm',
4182				'admin' => false,
4183				'prefs' => ['feature_comm'],
4184				'scope' => 'global',
4185			],
4186			[
4187				'name' => 'tiki_p_send_pages',
4188				'description' => tra('Can send pages to other sites'),
4189				'level' => 'registered',
4190				'type' => 'comm',
4191				'admin' => false,
4192				'prefs' => ['feature_comm'],
4193				'scope' => 'global',
4194			],
4195			[
4196				'name' => 'tiki_p_post_comments',
4197				'description' => tra('Can post new comments'),
4198				'level' => 'registered',
4199				'type' => 'comments',
4200				'admin' => false,
4201				'prefs' => [
4202										'feature_wiki_comments',
4203										'feature_blogposts_comments',
4204										'feature_file_galleries_comments',
4205										'feature_image_galleries_comments',
4206										'feature_article_comments',
4207										'feature_faq_comments',
4208										'feature_poll_comments',
4209										'map_comments'
4210				],
4211				'scope' => 'object',
4212				'apply_to' => ['wiki', 'trackers', 'articles', 'blogs'],
4213			],
4214			[
4215				'name' => 'tiki_p_read_comments',
4216				'description' => tra('Can read comments'),
4217				'level' => 'basic',
4218				'type' => 'comments',
4219				'admin' => false,
4220				'prefs' => [
4221										'feature_wiki_comments',
4222										'feature_blogposts_comments',
4223										'feature_file_galleries_comments',
4224										'feature_image_galleries_comments',
4225										'feature_article_comments',
4226										'feature_faq_comments',
4227										'feature_poll_comments',
4228										'map_comments'
4229				],
4230				'scope' => 'object',
4231				'apply_to' => ['wiki', 'trackers', 'articles', 'blogs'],
4232			],
4233			[
4234				'name' => 'tiki_p_admin_comments',
4235				'description' => tra('Can admin comments'),
4236				'level' => 'admin',
4237				'type' => 'comments',
4238				'admin' => true,
4239				'prefs' => [
4240										'feature_wiki_comments',
4241										'feature_blogposts_comments',
4242										'feature_file_galleries_comments',
4243										'feature_image_galleries_comments',
4244										'feature_article_comments',
4245										'feature_faq_comments',
4246										'feature_poll_comments',
4247										'map_comments'
4248				],
4249				'scope' => 'object',
4250				'apply_to' => ['wiki', 'trackers', 'articles', 'blogs'],
4251			],
4252			[
4253				'name' => 'tiki_p_edit_comments',
4254				'description' => tra('Can edit all comments'),
4255				'level' => 'editors',
4256				'type' => 'comments',
4257				'admin' => false,
4258				'prefs' => ['
4259										feature_wiki_comments',
4260										'feature_blogposts_comments',
4261										'feature_file_galleries_comments',
4262										'feature_image_galleries_comments',
4263										'feature_article_comments',
4264										'feature_faq_comments',
4265										'feature_poll_comments',
4266										'map_comments'
4267				],
4268				'scope' => 'object',
4269				'apply_to' => ['wiki', 'trackers', 'articles', 'blogs'],
4270			],
4271			[
4272				'name' => 'tiki_p_remove_comments',
4273				'description' => tra('Can delete comments'),
4274				'level' => 'editors',
4275				'type' => 'comments',
4276				'admin' => false,
4277				'prefs' => [
4278										'feature_wiki_comments',
4279										'feature_blogposts_comments',
4280										'feature_file_galleries_comments',
4281										'feature_image_galleries_comments',
4282										'feature_article_comments',
4283										'feature_faq_comments',
4284										'feature_poll_comments',
4285										'map_comments'
4286				],
4287				'scope' => 'object',
4288				'apply_to' => ['wiki', 'trackers', 'articles', 'blogs'],
4289			],
4290			[
4291				'name' => 'tiki_p_vote_comments',
4292				'description' => tra('Can vote on comments'),
4293				'level' => 'registered',
4294				'type' => 'comments',
4295				'admin' => false,
4296				'prefs' => ['comments_vote'],
4297				'scope' => 'object',
4298				'apply_to' => ['wiki', 'trackers', 'articles', 'blogs'],
4299			],
4300			[
4301				'name' => 'tiki_p_admin_content_templates',
4302				'description' => tra('Can admin content templates'),
4303				'level' => 'admin',
4304				'type' => 'content templates',
4305				'admin' => true,
4306				'prefs' => ['feature_wiki_templates', 'feature_cms_templates'],
4307				'scope' => 'object',
4308			],
4309			[
4310				'name' => 'tiki_p_edit_content_templates',
4311				'description' => tra('Can edit content templates'),
4312				'level' => 'editors',
4313				'type' => 'content templates',
4314				'admin' => false,
4315				'prefs' => ['feature_wiki_templates', 'feature_cms_templates', 'feature_file_galleries_templates'],
4316				'scope' => 'object',
4317			],
4318			[
4319				'name' => 'tiki_p_lock_content_templates',
4320				'description' => tra('Can lock content templates'),
4321				'level' => 'editors',
4322				'type' => 'content templates',
4323				'admin' => false,
4324				'prefs' => ['feature_wiki_templates', 'feature_cms_templates', 'lock_content_templates'],
4325				'scope' => 'object',
4326			],
4327			[
4328				'name' => 'tiki_p_use_content_templates',
4329				'description' => tra('Can use content templates'),
4330				'level' => 'registered',
4331				'type' => 'content templates',
4332				'admin' => false,
4333				'prefs' => ['feature_wiki_templates', 'feature_cms_templates'],
4334				'scope' => 'object',
4335			],
4336			[
4337				'name' => 'tiki_p_admin_contribution',
4338				'description' => tra('Can admin contributions'),
4339				'level' => 'admin',
4340				'type' => 'contribution',
4341				'admin' => true,
4342				'prefs' => ['feature_contribution'],
4343				'scope' => 'global',
4344			],
4345			[
4346				'name' => 'tiki_p_admin_directory',
4347				'description' => tra('Can admin the directory'),
4348				'level' => 'editors',
4349				'type' => 'directory',
4350				'admin' => true,
4351				'prefs' => ['feature_directory'],
4352				'scope' => 'global',
4353			],
4354			[
4355				'name' => 'tiki_p_admin_directory_cats',
4356				'description' => tra('Can admin directory categories'),
4357				'level' => 'editors',
4358				'type' => 'directory',
4359				'admin' => false,
4360				'prefs' => ['feature_directory'],
4361				'scope' => 'global',
4362			],
4363			[
4364				'name' => 'tiki_p_admin_directory_sites',
4365				'description' => tra('Can admin directory sites'),
4366				'level' => 'editors',
4367				'type' => 'directory',
4368				'admin' => false,
4369				'prefs' => ['feature_directory'],
4370				'scope' => 'global',
4371			],
4372			[
4373				'name' => 'tiki_p_autosubmit_link',
4374				'description' => tra('Submitted links are valid'),
4375				'level' => 'editors',
4376				'type' => 'directory',
4377				'admin' => false,
4378				'prefs' => ['feature_directory'],
4379				'scope' => 'global',
4380			],
4381			[
4382				'name' => 'tiki_p_submit_link',
4383				'description' => tra('Can submit sites to the directory'),
4384				'level' => 'basic',
4385				'type' => 'directory',
4386				'admin' => false,
4387				'prefs' => ['feature_directory'],
4388				'scope' => 'global',
4389			],
4390			[
4391				'name' => 'tiki_p_validate_links',
4392				'description' => tra('Can validate submitted links'),
4393				'level' => 'editors',
4394				'type' => 'directory',
4395				'admin' => false,
4396				'prefs' => ['feature_directory'],
4397				'scope' => 'global',
4398			],
4399			[
4400				'name' => 'tiki_p_view_directory',
4401				'description' => tra('Can use the directory'),
4402				'level' => 'basic',
4403				'type' => 'directory',
4404				'admin' => false,
4405				'prefs' => ['feature_directory'],
4406				'scope' => 'global',
4407			],
4408			[
4409				'name' => 'tiki_p_dsn_query',
4410				'description' => tra('Can execute arbitrary queries on a given DSN'),
4411				'level' => 'admin',
4412				'type' => 'dsn',
4413				'admin' => false,
4414				'prefs' => [],
4415				'scope' => 'object',
4416			],
4417			[
4418				'name' => 'tiki_p_admin_faqs',
4419				'description' => tra('Can admin FAQs'),
4420				'level' => 'editors',
4421				'type' => 'faqs',
4422				'admin' => true,
4423				'prefs' => ['feature_faqs'],
4424				'scope' => 'object',
4425			],
4426			[
4427				'name' => 'tiki_p_suggest_faq',
4428				'description' => tra('Can suggest FAQ questions'),
4429				'level' => 'basic',
4430				'type' => 'faqs',
4431				'admin' => false,
4432				'prefs' => ['feature_faqs'],
4433				'scope' => 'object',
4434			],
4435			[
4436				'name' => 'tiki_p_view_faqs',
4437				'description' => tra('Can view FAQs'),
4438				'level' => 'basic',
4439				'type' => 'faqs',
4440				'admin' => false,
4441				'prefs' => ['feature_faqs'],
4442				'scope' => 'object',
4443			],
4444			[
4445				'name' => 'tiki_p_download_files',
4446				'description' => tra('Can download files'),
4447				'level' => 'basic',
4448				'type' => 'file galleries',
4449				'admin' => false,
4450				'prefs' => ['feature_file_galleries'],
4451				'scope' => 'object',
4452			],
4453			[
4454				'name' => 'tiki_p_upload_files',
4455				'description' => tra('Can upload files'),
4456				'level' => 'registered',
4457				'type' => 'file galleries',
4458				'admin' => false,
4459				'prefs' => ['feature_file_galleries'],
4460				'scope' => 'object',
4461			],
4462			[
4463				'name' => 'tiki_p_list_file_galleries',
4464				'description' => tra('Can list file galleries'),
4465				'level' => 'basic',
4466				'type' => 'file galleries',
4467				'admin' => false,
4468				'prefs' => ['feature_file_galleries'],
4469				'scope' => 'object',
4470			],
4471			[
4472				'name' => 'tiki_p_view_file_gallery',
4473				'description' => tra('Can view file galleries'),
4474				'level' => 'basic',
4475				'type' => 'file galleries',
4476				'admin' => false,
4477				'prefs' => ['feature_file_galleries'],
4478				'scope' => 'object',
4479			],
4480			[
4481				'name' => 'tiki_p_admin_file_galleries',
4482				'description' => tra('Can admin file galleries'),
4483				'level' => 'admin',
4484				'type' => 'file galleries',
4485				'admin' => true,
4486				'prefs' => ['feature_file_galleries'],
4487				'scope' => 'object',
4488			],
4489			[
4490				'name' => 'tiki_p_assign_perm_file_gallery',
4491				'description' => tra('Can assign permissions to file galleries'),
4492				'level' => 'admin',
4493				'type' => 'file galleries',
4494				'admin' => false,
4495				'prefs' => ['feature_file_galleries'],
4496				'scope' => 'object',
4497			],
4498			[
4499				'name' => 'tiki_p_batch_upload_file_dir',
4500				'description' => tra('Can use Directory Batch Load'),
4501				'level' => 'editors',
4502				'type' => 'file galleries',
4503				'admin' => false,
4504				'prefs' => ['feature_file_galleries_batch'],
4505				'scope' => 'object',
4506			],
4507			[
4508				'name' => 'tiki_p_batch_upload_files',
4509				'description' => tra('Can upload .zip file packages'),
4510				'level' => 'editors',
4511				'type' => 'file galleries',
4512				'admin' => false,
4513				'prefs' => ['feature_file_galleries'],
4514				'scope' => 'object',
4515			],
4516			[
4517				'name' => 'tiki_p_create_file_galleries',
4518				'description' => tra('Can create file galleries'),
4519				'level' => 'editors',
4520				'type' => 'file galleries',
4521				'admin' => false,
4522				'prefs' => ['feature_file_galleries'],
4523				'scope' => 'object',
4524			],
4525			[
4526				'name' => 'tiki_p_edit_gallery_file',
4527				'description' => tra('Can edit a gallery file'),
4528				'level' => 'editors',
4529				'type' => 'file galleries',
4530				'admin' => false,
4531				'prefs' => ['feature_file_galleries'],
4532				'scope' => 'object',
4533			],
4534			[
4535				'name' => 'tiki_p_remove_files',
4536				'description' => tra('Can remove files'),
4537				'level' => 'registered',
4538				'type' => 'file galleries',
4539				'admin' => false,
4540				'prefs' => ['feature_file_galleries'],
4541				'scope' => 'object',
4542			],
4543			[
4544				'name' => 'tiki_p_view_fgal_explorer',
4545				'description' => tra('Can view file galleries explorer'),
4546				'level' => 'basic',
4547				'type' => 'file galleries',
4548				'admin' => false,
4549				'prefs' => ['fgal_show_explorer'],
4550				'scope' => 'object',
4551			],
4552			[
4553				'name' => 'tiki_p_view_fgal_path',
4554				'description' => tra('Can view file galleries path'),
4555				'level' => 'basic',
4556				'type' => 'file galleries',
4557				'admin' => false,
4558				'prefs' => ['fgal_show_path'],
4559				'scope' => 'object',
4560			],
4561			[
4562				'name' => 'tiki_p_upload_javascript',
4563				'description' => tra('Can upload files containing JavaScript'),
4564				'level' => 'admin',
4565				'type' => 'file galleries',
4566				'admin' => false,
4567				'prefs' => ['feature_file_galleries'],
4568				'scope' => 'object',
4569			],
4570			[
4571				'name' => 'tiki_p_upload_svg',
4572				'description' => tra('Can upload SVG files'),
4573				'level' => 'admin',
4574				'type' => 'file galleries',
4575				'admin' => false,
4576				'prefs' => ['fgal_allow_svg'],
4577				'scope' => 'object',
4578			],
4579			[
4580				'name' => 'tiki_p_admin_forum',
4581				'description' => tra('Can admin forums'),
4582				'level' => 'admin',
4583				'type' => 'forums',
4584				'admin' => true,
4585				'prefs' => ['feature_forums'],
4586				'scope' => 'object',
4587			],
4588			[
4589				'name' => 'tiki_p_forum_attach',
4590				'description' => tra('Can attach files to forum posts'),
4591				'level' => 'registered',
4592				'type' => 'forums',
4593				'admin' => false,
4594				'prefs' => ['feature_forums'],
4595				'scope' => 'object',
4596			],
4597			[
4598				'name' => 'tiki_p_forum_autoapp',
4599				'description' => tra('Auto approve forum posts'),
4600				'level' => 'editors',
4601				'type' => 'forums',
4602				'admin' => false,
4603				'prefs' => ['feature_forums'],
4604				'scope' => 'object',
4605			],
4606			[
4607				'name' => 'tiki_p_forum_edit_own_posts',
4608				'description' => tra("Can edit one's own forum posts"),
4609				'level' => 'registered',
4610				'type' => 'forums',
4611				'admin' => false,
4612				'prefs' => ['feature_forums'],
4613				'scope' => 'object',
4614			],
4615			[
4616				'name' => 'tiki_p_forum_post',
4617				'description' => tra('Can post in forums'),
4618				'level' => 'registered',
4619				'type' => 'forums',
4620				'admin' => false,
4621				'prefs' => ['feature_forums'],
4622				'scope' => 'object',
4623			],
4624			[
4625				'name' => 'tiki_p_forum_post_topic',
4626				'description' => tra('Can start threads in forums'),
4627				'level' => 'registered',
4628				'type' => 'forums',
4629				'admin' => false,
4630				'prefs' => ['feature_forums'],
4631				'scope' => 'object',
4632			],
4633			[
4634				'name' => 'tiki_p_forum_read',
4635				'description' => tra('Can read forums'),
4636				'level' => 'basic',
4637				'type' => 'forums',
4638				'admin' => false,
4639				'prefs' => ['feature_forums'],
4640				'scope' => 'object',
4641			],
4642			[
4643				'name' => 'tiki_p_forums_report',
4644				'description' => tra('Can report posts to moderator'),
4645				'level' => 'registered',
4646				'type' => 'forums',
4647				'admin' => false,
4648				'prefs' => ['feature_forums'],
4649				'scope' => 'object',
4650			],
4651			[
4652				'name' => 'tiki_p_forum_vote',
4653				'description' => tra('Can vote on comments in forums'),
4654				'level' => 'registered',
4655				'type' => 'forums',
4656				'admin' => false,
4657				'prefs' => ['feature_forums'],
4658				'scope' => 'object',
4659			],
4660			[
4661				'name' => 'tiki_p_view_freetags',
4662				'description' => tra('Can browse tags'),
4663				'level' => 'basic',
4664				'type' => 'freetags',
4665				'admin' => false,
4666				'prefs' => ['feature_freetags'],
4667				'scope' => 'object',
4668			],
4669			[
4670				'name' => 'tiki_p_admin_freetags',
4671				'description' => tra('Can admin tags'),
4672				'level' => 'admin',
4673				'type' => 'freetags',
4674				'admin' => true,
4675				'prefs' => ['feature_freetags'],
4676				'scope' => 'object',
4677			],
4678			[
4679				'name' => 'tiki_p_freetags_tag',
4680				'description' => tra('Can tag objects'),
4681				'level' => 'registered',
4682				'type' => 'freetags',
4683				'admin' => false,
4684				'prefs' => ['feature_freetags'],
4685				'scope' => 'object',
4686			],
4687			[
4688				'name' => 'tiki_p_unassign_freetags',
4689				'description' => tra('Can unassign tags from an object'),
4690				'level' => 'basic',
4691				'type' => 'freetags',
4692				'admin' => false,
4693				'prefs' => ['feature_freetags'],
4694				'scope' => 'object',
4695			],
4696			[
4697				'name' => 'tiki_p_subscribe_groups',
4698				'description' => tra('Can subscribe to groups'),
4699				'level' => 'registered',
4700				'type' => 'group',
4701				'admin' => false,
4702				'prefs' => [],
4703				'scope' => 'global',
4704			],
4705			[
4706				'name' => 'tiki_p_invite_to_my_groups',
4707				'description' => tra('Can invite user to my groups'),
4708				'level' => 'editors',
4709				'type' => 'group',
4710				'admin' => false,
4711				'prefs' => [],
4712				'scope' => 'global',
4713			],
4714			[
4715				'name' => 'tiki_p_group_view',
4716				'description' => tra('Can view the group'),
4717				'level' => 'basic',
4718				'type' => 'group',
4719				'admin' => false,
4720				'prefs' => [],
4721				'scope' => 'object',
4722			],
4723			[
4724				'name' => 'tiki_p_group_view_members',
4725				'description' => tra('Can view the group members'),
4726				'level' => 'basic',
4727				'type' => 'group',
4728				'admin' => false,
4729				'prefs' => [],
4730				'scope' => 'object',
4731			],
4732			[
4733				'name' => 'tiki_p_group_add_member',
4734				'description' => tra('Can add group members'),
4735				'level' => 'admin',
4736				'type' => 'group',
4737				'admin' => false,
4738				'prefs' => [],
4739				'scope' => 'object',
4740			],
4741			[
4742				'name' => 'tiki_p_group_remove_member',
4743				'description' => tra('Can remove group members'),
4744				'level' => 'admin',
4745				'type' => 'group',
4746				'admin' => false,
4747				'prefs' => [],
4748				'scope' => 'object',
4749			],
4750			[
4751				'name' => 'tiki_p_group_join',
4752				'description' => tra('Can join or leave the group'),
4753				'level' => 'admin',
4754				'type' => 'group',
4755				'admin' => false,
4756				'prefs' => [],
4757				'scope' => 'object',
4758			],
4759			[
4760				'name' => 'tiki_p_h5p_view',
4761				'description' => tra('Can view H5P content'),
4762				'level' => 'registered',
4763				'type' => 'h5p',
4764				'admin' => false,
4765				'prefs' => ['h5p_enabled'],
4766				'scope' => 'global',    // adding as global to start with, probably will need to be object type eventually?
4767			],
4768			[
4769				'name' => 'tiki_p_h5p_edit',
4770				'description' => tra('Can edit H5P content'),
4771				'level' => 'editors',
4772				'type' => 'h5p',
4773				'admin' => false,
4774				'prefs' => ['h5p_enabled'],
4775				'scope' => 'global',
4776			],
4777			[
4778				'name' => 'tiki_p_h5p_admin',
4779				'description' => tra('Can administer H5P content'),
4780				'level' => 'admins',
4781				'type' => 'h5p',
4782				'admin' => false,
4783				'prefs' => ['h5p_enabled'],
4784				'scope' => 'global',
4785			],
4786			[
4787				'name' => 'tiki_p_edit_html_pages',
4788				'description' => tra('Can edit HTML pages'),
4789				'level' => 'editors',
4790				'type' => 'html pages',
4791				'admin' => false,
4792				'prefs' => ['feature_html_pages'],
4793				'scope' => 'global',
4794			],
4795			[
4796				'name' => 'tiki_p_view_html_pages',
4797				'description' => tra('Can view HTML pages'),
4798				'level' => 'basic',
4799				'type' => 'html pages',
4800				'admin' => false,
4801				'prefs' => ['feature_html_pages'],
4802				'scope' => 'global',
4803			],
4804			[
4805				'name' => 'tiki_p_admin_galleries',
4806				'description' => tra('Can admin Image Galleries'),
4807				'level' => 'editors',
4808				'type' => 'image galleries',
4809				'admin' => true,
4810				'prefs' => ['feature_galleries'],
4811				'scope' => 'object',
4812			],
4813			[
4814				'name' => 'tiki_p_assign_perm_image_gallery',
4815				'description' => tra('Can assign permissions to image galleries'),
4816				'level' => 'admin',
4817				'type' => 'image galleries',
4818				'admin' => false,
4819				'prefs' => ['feature_galleries'],
4820				'scope' => 'object',
4821			],
4822			[
4823				'name' => 'tiki_p_batch_upload_image_dir',
4824				'description' => tra('Can use Directory Batch Load'),
4825				'level' => 'editors',
4826				'type' => 'image galleries',
4827				'admin' => false,
4828				'prefs' => ['feature_galleries'],
4829				'scope' => 'object',
4830			],
4831			[
4832				'name' => 'tiki_p_batch_upload_images',
4833				'description' => tra('Can upload .zip files of images'),
4834				'level' => 'editors',
4835				'type' => 'image galleries',
4836				'admin' => false,
4837				'prefs' => ['feature_galleries'],
4838				'scope' => 'object',
4839			],
4840			[
4841				'name' => 'tiki_p_create_galleries',
4842				'description' => tra('Can create image galleries'),
4843				'level' => 'editors',
4844				'type' => 'image galleries',
4845				'admin' => false,
4846				'prefs' => ['feature_galleries'],
4847				'scope' => 'global',
4848			],
4849			[
4850				'name' => 'tiki_p_list_image_galleries',
4851				'description' => tra('Can list image galleries'),
4852				'level' => 'basic',
4853				'type' => 'image galleries',
4854				'admin' => false,
4855				'prefs' => ['feature_galleries'],
4856				'scope' => 'global',
4857			],
4858			[
4859				'name' => 'tiki_p_upload_images',
4860				'description' => tra('Can upload images'),
4861				'level' => 'registered',
4862				'type' => 'image galleries',
4863				'admin' => false,
4864				'prefs' => ['feature_galleries'],
4865				'scope' => 'object',
4866			],
4867			[
4868				'name' => 'tiki_p_view_image_gallery',
4869				'description' => tra('Can view image galleries'),
4870				'level' => 'basic',
4871				'type' => 'image galleries',
4872				'admin' => false,
4873				'prefs' => ['feature_galleries'],
4874				'scope' => 'object',
4875			],
4876			[
4877				'name' => 'tiki_p_admin_kaltura',
4878				'description' => tra('Can admin Kaltura video feature'),
4879				'level' => 'admin',
4880				'type' => 'media',
4881				'admin' => true,
4882				'prefs' => ['feature_kaltura'],
4883				'scope' => 'global',
4884			],
4885			[
4886				'name' => 'tiki_p_upload_videos',
4887				'description' => tra('Can upload video or record from webcam'),
4888				'level' => 'editors',
4889				'type' => 'media',
4890				'admin' => false,
4891				'prefs' => ['feature_kaltura'],
4892				'scope' => 'global',
4893			],
4894			[
4895				'name' => 'tiki_p_edit_videos',
4896				'description' => tra('Can edit media information'),
4897				'level' => 'editors',
4898				'type' => 'media',
4899				'admin' => false,
4900				'prefs' => ['feature_kaltura'],
4901				'scope' => 'global',
4902			],
4903			[
4904				'name' => 'tiki_p_delete_videos',
4905				'description' => tra('Can delete media'),
4906				'level' => 'editors',
4907				'type' => 'media',
4908				'admin' => false,
4909				'prefs' => ['feature_kaltura'],
4910				'scope' => 'global',
4911			],
4912			[
4913				'name' => 'tiki_p_download_videos',
4914				'description' => tra('Can download media'),
4915				'level' => 'registered',
4916				'type' => 'media',
4917				'admin' => false,
4918				'prefs' => ['feature_kaltura'],
4919				'scope' => 'global',
4920			],
4921			[
4922				'name' => 'tiki_p_list_videos',
4923				'description' => tra('Can list media'),
4924				'level' => 'basic',
4925				'type' => 'media',
4926				'admin' => false,
4927				'prefs' => ['feature_kaltura'],
4928				'scope' => 'global',
4929			],
4930			[
4931				'name' => 'tiki_p_view_videos',
4932				'description' => tra('Can view media'),
4933				'level' => 'basic',
4934				'type' => 'media',
4935				'admin' => false,
4936				'prefs' => ['feature_kaltura'],
4937				'scope' => 'global',
4938			],
4939			[
4940				'name' => 'tiki_p_broadcast_all',
4941				'description' => tra('Can broadcast messages to all users'),
4942				'level' => 'admin',
4943				'type' => 'messages',
4944				'admin' => false,
4945				'prefs' => ['feature_messages'],
4946				'scope' => 'global',
4947			],
4948			[
4949				'name' => 'tiki_p_broadcast',
4950				'description' => tra('Can broadcast messages to groups'),
4951				'level' => 'admin',
4952				'type' => 'group',
4953				'admin' => false,
4954				'prefs' => ['feature_messages'],
4955				'scope' => 'object',
4956			],
4957			[
4958				'name' => 'tiki_p_messages',
4959				'description' => tra('Can use the messaging system'),
4960				'level' => 'registered',
4961				'type' => 'messages',
4962				'admin' => false,
4963				'prefs' => ['feature_messages'],
4964				'scope' => 'global',
4965			],
4966			[
4967				'name' => 'tiki_p_admin_newsletters',
4968				'description' => tra('Can admin newsletters'),
4969				'level' => 'admin',
4970				'type' => 'newsletters',
4971				'admin' => true,
4972				'prefs' => ['feature_newsletters'],
4973				'scope' => 'object',
4974			],
4975			[
4976				'name' => 'tiki_p_batch_subscribe_email',
4977				'description' => tra('Can subscribe multiple email addresses at once (requires tiki_p_subscribe email)'),
4978				'level' => 'editors',
4979				'type' => 'newsletters',
4980				'admin' => false,
4981				'prefs' => ['feature_newsletters'],
4982				'scope' => 'global',
4983			],
4984			[
4985				'name' => 'tiki_p_send_newsletters',
4986				'description' => tra('Can send newsletters'),
4987				'level' => 'editors',
4988				'type' => 'newsletters',
4989				'admin' => false,
4990				'prefs' => ['feature_newsletters'],
4991				'scope' => 'object',
4992			],
4993			[
4994				'name' => 'tiki_p_subscribe_email',
4995				'description' => tra('Can subscribe any email address to newsletters'),
4996				'level' => 'editors',
4997				'type' => 'newsletters',
4998				'admin' => false,
4999				'prefs' => ['feature_newsletters'],
5000				'scope' => 'global',
5001			],
5002			[
5003				'name' => 'tiki_p_subscribe_newsletters',
5004				'description' => tra('Can subscribe to newsletters'),
5005				'level' => 'basic',
5006				'type' => 'newsletters',
5007				'admin' => false,
5008				'prefs' => ['feature_newsletters'],
5009				'scope' => 'object',
5010			],
5011			[
5012				'name' => 'tiki_p_view_newsletter',
5013				'description' => tra('Can view the archive of a newsletters'),
5014				'level' => 'basic',
5015				'type' => 'newsletters',
5016				'admin' => false,
5017				'prefs' => ['feature_newsletters'],
5018				'scope' => 'object',
5019			],
5020			[
5021				'name' => 'tiki_p_list_newsletters',
5022				'description' => tra('Can list newsletters'),
5023				'level' => 'basic',
5024				'type' => 'newsletters',
5025				'admin' => false,
5026				'prefs' => ['feature_newsletters'],
5027				'scope' => 'global',
5028			],
5029			[
5030				'name' => 'tiki_p_payment_admin',
5031				'description' => tra('Can administer payments'),
5032				'level' => 'admin',
5033				'type' => 'payment',
5034				'admin' => true,
5035				'prefs' => ['payment_feature'],
5036				'scope' => 'global',
5037			],
5038			[
5039				'name' => 'tiki_p_payment_view',
5040				'description' => tra('Can view payment requests and details'),
5041				'level' => 'admin',
5042				'type' => 'payment',
5043				'admin' => false,
5044				'prefs' => ['payment_feature'],
5045				'scope' => 'object',
5046			],
5047			[
5048				'name' => 'tiki_p_payment_manual',
5049				'description' => tra('Can enter manual payments'),
5050				'level' => 'admin',
5051				'type' => 'payment',
5052				'admin' => false,
5053				'prefs' => ['payment_feature'],
5054				'scope' => 'object',
5055			],
5056			[
5057				'name' => 'tiki_p_payment_request',
5058				'description' => tra('Can request a payment'),
5059				'level' => 'admin',
5060				'type' => 'payment',
5061				'admin' => false,
5062				'prefs' => ['payment_feature'],
5063				'scope' => 'object',
5064			],
5065			[
5066				'name' => 'tiki_p_perspective_view',
5067				'description' => tra('Can view the perspective'),
5068				'level' => 'basic',
5069				'type' => 'perspective',
5070				'admin' => false,
5071				'prefs' => ['feature_perspective'],
5072				'scope' => 'object',
5073			],
5074			[
5075				'name' => 'tiki_p_perspective_edit',
5076				'description' => tra('Can edit the perspective'),
5077				'level' => 'basic',
5078				'type' => 'perspective',
5079				'admin' => false,
5080				'prefs' => ['feature_perspective'],
5081				'scope' => 'object',
5082			],
5083			[
5084				'name' => 'tiki_p_perspective_create',
5085				'description' => tra('Can create a perspective'),
5086				'level' => 'basic',
5087				'type' => 'perspective',
5088				'admin' => false,
5089				'prefs' => ['feature_perspective'],
5090				'scope' => 'global',
5091			],
5092			[
5093				'name' => 'tiki_p_perspective_admin',
5094				'description' => tra('Can admin perspectives'),
5095				'level' => 'admin',
5096				'type' => 'perspective',
5097				'admin' => true,
5098				'prefs' => ['feature_perspective'],
5099				'scope' => 'object',
5100			],
5101			[
5102				'name' => 'tiki_p_admin_polls',
5103				'description' => tra('Can admin polls'),
5104				'level' => 'admin',
5105				'type' => 'polls',
5106				'admin' => true,
5107				'prefs' => ['feature_polls'],
5108				'scope' => 'global',
5109			],
5110			[
5111				'name' => 'tiki_p_view_poll_results',
5112				'description' => tra('Can view poll results'),
5113				'level' => 'basic',
5114				'type' => 'polls',
5115				'admin' => false,
5116				'prefs' => ['feature_polls'],
5117				'scope' => 'global',
5118			],
5119			[
5120				'name' => 'tiki_p_view_poll_choices',
5121				'description' => tra('Can view poll user choices'),
5122				'level' => 'basic',
5123				'type' => 'polls',
5124				'admin' => false,
5125				'prefs' => ['feature_polls'],
5126				'scope' => 'object',
5127			],
5128			[
5129				'name' => 'tiki_p_vote_poll',
5130				'description' => tra('Can vote in polls'),
5131				'level' => 'basic',
5132				'type' => 'polls',
5133				'admin' => false,
5134				'prefs' => ['feature_polls'],
5135				'scope' => 'object',
5136			],
5137			[
5138				'name' => 'tiki_p_view_poll_voters',
5139				'description' => tra('Can view poll voters'),
5140				'level' => 'basic',
5141				'type' => 'polls',
5142				'admin' => false,
5143				'prefs' => ['feature_polls'],
5144				'scope' => 'object',
5145			],
5146			[
5147				'name' => 'tiki_p_admin_quizzes',
5148				'description' => tra('Can admin quizzes'),
5149				'level' => 'editors',
5150				'type' => 'quizzes',
5151				'admin' => true,
5152				'prefs' => ['feature_quizzes'],
5153				'scope' => 'global',
5154			],
5155			[
5156				'name' => 'tiki_p_take_quiz',
5157				'description' => tra('Can take quizzes'),
5158				'level' => 'basic',
5159				'type' => 'quizzes',
5160				'admin' => false,
5161				'prefs' => ['feature_quizzes'],
5162				'scope' => 'global',
5163			],
5164			[
5165				'name' => 'tiki_p_view_quiz_stats',
5166				'description' => tra('Can view quiz stats'),
5167				'level' => 'basic',
5168				'type' => 'quizzes',
5169				'admin' => false,
5170				'prefs' => ['feature_quizzes'],
5171				'scope' => 'global',
5172			],
5173			[
5174				'name' => 'tiki_p_view_user_results',
5175				'description' => tra('Can view user quiz results'),
5176				'level' => 'editors',
5177				'type' => 'quizzes',
5178				'admin' => false,
5179				'prefs' => ['feature_quizzes'],
5180				'scope' => 'global',
5181			],
5182			[
5183				'name' => 'tiki_p_admin_sheet',
5184				'description' => tra('Can admin spreadsheets'),
5185				'level' => 'admin',
5186				'type' => 'sheet',
5187				'admin' => true,
5188				'prefs' => ['feature_sheet'],
5189				'scope' => 'object',
5190			],
5191			[
5192				'name' => 'tiki_p_edit_sheet',
5193				'description' => tra('Can create and edit spreadsheets'),
5194				'level' => 'editors',
5195				'type' => 'sheet',
5196				'admin' => false,
5197				'prefs' => ['feature_sheet'],
5198				'scope' => 'object',
5199			],
5200			[
5201				'name' => 'tiki_p_view_sheet',
5202				'description' => tra('Can view spreadsheets'),
5203				'level' => 'basic',
5204				'type' => 'sheet',
5205				'admin' => false,
5206				'prefs' => ['feature_sheet'],
5207				'scope' => 'object',
5208			],
5209			[
5210				'name' => 'tiki_p_view_sheet_history',
5211				'description' => tra('Can view spreadsheets history'),
5212				'level' => 'admin',
5213				'type' => 'sheet',
5214				'admin' => false,
5215				'prefs' => ['feature_sheet'],
5216				'scope' => 'object',
5217			],
5218			[
5219				'name' => 'tiki_p_admin_shoutbox',
5220				'description' => tra('Can admin the shoutbox (edit/remove messages)'),
5221				'level' => 'editors',
5222				'type' => 'shoutbox',
5223				'admin' => true,
5224				'prefs' => ['feature_shoutbox'],
5225				'scope' => 'global',
5226			],
5227			[
5228				'name' => 'tiki_p_post_shoutbox',
5229				'description' => tra('Can post messages in the shoutbox'),
5230				'level' => 'basic',
5231				'type' => 'shoutbox',
5232				'admin' => false,
5233				'prefs' => ['feature_shoutbox'],
5234				'scope' => 'global',
5235			],
5236			[
5237				'name' => 'tiki_p_view_shoutbox',
5238				'description' => tra('Can view the shoutbox'),
5239				'level' => 'basic',
5240				'type' => 'shoutbox',
5241				'admin' => false,
5242				'prefs' => ['feature_shoutbox'],
5243				'scope' => 'global',
5244			],
5245			[
5246				'name' => 'tiki_p_socialnetworks',
5247				'description' => tra('Can use social network integration'),
5248				'level' => 'registered',
5249				'type' => 'socialnetworks',
5250				'admin' => false,
5251				'prefs' => ['feature_socialnetworks'],
5252				'scope' => 'global',
5253			],
5254			[
5255				'name' => 'tiki_p_admin_socialnetworks',
5256				'description' => tra('Can register this site with social networks'),
5257				'level' => 'admin',
5258				'type' => 'socialnetworks',
5259				'admin' => true,
5260				'prefs' => ['feature_socialnetworks'],
5261				'scope' => 'global',
5262			],
5263			[
5264				'name' => 'tiki_p_live_support_admin',
5265				'description' => tra('Admin live support system'),
5266				'level' => 'admin',
5267				'type' => 'support',
5268				'admin' => true,
5269				'prefs' => ['feature_live_support'],
5270				'scope' => 'global',
5271			],
5272			[
5273				'name' => 'tiki_p_live_support',
5274				'description' => tra('Can use live support system'),
5275				'level' => 'basic',
5276				'type' => 'support',
5277				'admin' => false,
5278				'prefs' => ['feature_live_support'],
5279				'scope' => 'global',
5280			],
5281			[
5282				'name' => 'tiki_p_admin_surveys',
5283				'description' => tra('Can admin surveys'),
5284				'level' => 'editors',
5285				'type' => 'surveys',
5286				'admin' => true,
5287				'prefs' => ['feature_surveys'],
5288				'scope' => 'global',
5289			],
5290			[
5291				'name' => 'tiki_p_take_survey',
5292				'description' => tra('Can take surveys'),
5293				'level' => 'basic',
5294				'type' => 'surveys',
5295				'admin' => false,
5296				'prefs' => ['feature_surveys'],
5297				'scope' => 'object',
5298			],
5299			[
5300				'name' => 'tiki_p_view_survey_stats',
5301				'description' => tra('Can view survey stats'),
5302				'level' => 'basic',
5303				'type' => 'surveys',
5304				'admin' => false,
5305				'prefs' => ['feature_surveys'],
5306				'scope' => 'object',
5307			],
5308			[
5309				'name' => 'tiki_p_admin_tikitests',
5310				'description' => tra('Can admin TikiTests'),
5311				'level' => 'admin',
5312				'type' => 'tikitests',
5313				'admin' => false,
5314				'prefs' => ['feature_tikitests'],
5315				'scope' => 'global',
5316			],
5317			[
5318				'name' => 'tiki_p_edit_tikitests',
5319				'description' => tra('Can edit TikiTests'),
5320				'level' => 'editors',
5321				'type' => 'tikitests',
5322				'admin' => false,
5323				'prefs' => ['feature_tikitests'],
5324				'scope' => 'global',
5325			],
5326			[
5327				'name' => 'tiki_p_play_tikitests',
5328				'description' => tra('Can replay TikiTests'),
5329				'level' => 'registered',
5330				'type' => 'tikitests',
5331				'admin' => false,
5332				'prefs' => ['feature_tikitests'],
5333				'scope' => 'global',
5334			],
5335			[
5336				'name' => 'tiki_p_admin_trackers',
5337				'description' => tra('Can admin trackers'),
5338				'level' => 'admin',
5339				'type' => 'trackers',
5340				'admin' => true,
5341				'prefs' => ['feature_trackers'],
5342				'scope' => 'object',
5343			],
5344			[
5345				'name' => 'tiki_p_attach_trackers',
5346				'description' => tra('Can attach files to tracker items'),
5347				'level' => 'registered',
5348				'type' => 'trackers',
5349				'admin' => false,
5350				'prefs' => ['feature_trackers'],
5351				'scope' => 'object',
5352			],
5353			[
5354				'name' => 'tiki_p_tracker_view_attachments',
5355				'description' => tra('Can view tracker item attachments and download them'),
5356				'level' => 'registered',
5357				'type' => 'trackers',
5358				'admin' => false,
5359				'prefs' => ['feature_trackers'],
5360				'scope' => 'object',
5361			],
5362			[
5363				'name' => 'tiki_p_comment_tracker_items',
5364				'description' => tra('Can post tracker item comments'),
5365				'level' => 'basic',
5366				'type' => 'trackers',
5367				'admin' => false,
5368				'prefs' => ['feature_trackers'],
5369				'scope' => 'object',
5370			],
5371			[
5372				'name' => 'tiki_p_tracker_view_comments',
5373				'description' => tra('Can view tracker item comments'),
5374				'level' => 'basic',
5375				'type' => 'trackers',
5376				'admin' => false,
5377				'prefs' => ['feature_trackers'],
5378				'scope' => 'object',
5379			],
5380			[
5381				'name' => 'tiki_p_create_tracker_items',
5382				'description' => tra('Can create new tracker items'),
5383				'level' => 'registered',
5384				'type' => 'trackers',
5385				'admin' => false,
5386				'prefs' => ['feature_trackers'],
5387				'scope' => 'object',
5388			],
5389			[
5390				'name' => 'tiki_p_list_trackers',
5391				'description' => tra('Can list trackers'),
5392				'level' => 'basic',
5393				'type' => 'trackers',
5394				'admin' => false,
5395				'prefs' => ['feature_trackers'],
5396				'scope' => 'global',
5397			],
5398			[
5399				'name' => 'tiki_p_modify_tracker_items',
5400				'description' => tra('Can change tracker items'),
5401				'level' => 'registered',
5402				'type' => 'trackers',
5403				'admin' => false,
5404				'prefs' => ['feature_trackers'],
5405				'scope' => 'object',
5406			],
5407			[
5408				'name' => 'tiki_p_modify_tracker_items_pending',
5409				'description' => tra('Can change pending tracker items'),
5410				'level' => 'registered',
5411				'type' => 'trackers',
5412				'admin' => false,
5413				'prefs' => ['feature_trackers'],
5414				'scope' => 'object',
5415			],
5416			[
5417				'name' => 'tiki_p_modify_tracker_items_closed',
5418				'description' => tra('Can change closed tracker items'),
5419				'level' => 'registered',
5420				'type' => 'trackers',
5421				'admin' => false,
5422				'prefs' => ['feature_trackers'],
5423				'scope' => 'object',
5424			],
5425			[
5426				'name' => 'tiki_p_remove_tracker_items',
5427				'description' => tra('Can remove tracker items'),
5428				'level' => 'registered',
5429				'type' => 'trackers',
5430				'admin' => false,
5431				'prefs' => ['feature_trackers'],
5432				'scope' => 'object',
5433			],
5434			[
5435				'name' => 'tiki_p_remove_tracker_items_pending',
5436				'description' => tra('Can remove pending tracker items'),
5437				'level' => 'registered',
5438				'type' => 'trackers',
5439				'admin' => false,
5440				'prefs' => ['feature_trackers'],
5441				'scope' => 'object',
5442			],
5443			[
5444				'name' => 'tiki_p_remove_tracker_items_closed',
5445				'description' => tra('Can remove closed tracker items'),
5446				'level' => 'registered',
5447				'type' => 'trackers',
5448				'admin' => false,
5449				'prefs' => ['feature_trackers'],
5450				'scope' => 'object',
5451			],
5452			[
5453				'name' => 'tiki_p_tracker_view_ratings',
5454				'description' => tra('Can view rating result for tracker items'),
5455				'level' => 'basic',
5456				'type' => 'trackers',
5457				'admin' => false,
5458				'prefs' => ['feature_trackers'],
5459				'scope' => 'object',
5460			],
5461			[
5462				'name' => 'tiki_p_tracker_vote_ratings',
5463				'description' => tra('Can rate tracker items'),
5464				'level' => 'registered',
5465				'type' => 'trackers',
5466				'admin' => false,
5467				'prefs' => ['feature_trackers'],
5468				'scope' => 'object',
5469			],
5470			[
5471				'name' => 'tiki_p_tracker_revote_ratings',
5472				'description' => tra('Can re-rate tracker items'),
5473				'level' => 'registered',
5474				'type' => 'trackers',
5475				'admin' => false,
5476				'prefs' => ['feature_trackers'],
5477				'scope' => 'object',
5478			],
5479			[
5480				'name' => 'tiki_p_view_trackers',
5481				'description' => tra('Can view trackers'),
5482				'level' => 'basic',
5483				'type' => 'trackers',
5484				'admin' => false,
5485				'prefs' => ['feature_trackers'],
5486				'scope' => 'object',
5487			],
5488			[
5489				'name' => 'tiki_p_view_trackers_closed',
5490				'description' => tra('Can view closed trackers items'),
5491				'level' => 'registered',
5492				'type' => 'trackers',
5493				'admin' => false,
5494				'prefs' => ['feature_trackers'],
5495				'scope' => 'object',
5496			],
5497			[
5498				'name' => 'tiki_p_view_trackers_pending',
5499				'description' => tra('Can view pending trackers items'),
5500				'level' => 'editors',
5501				'type' => 'trackers',
5502				'admin' => false,
5503				'prefs' => ['feature_trackers'],
5504				'scope' => 'object',
5505			],
5506			[
5507				'name' => 'tiki_p_watch_trackers',
5508				'description' => tra('Can watch a tracker'),
5509				'level' => 'registered',
5510				'type' => 'trackers',
5511				'admin' => false,
5512				'prefs' => ['feature_trackers'],
5513				'scope' => 'object',
5514			],
5515			[
5516				'name' => 'tiki_p_export_tracker',
5517				'description' => tra('Can export tracker items'),
5518				'level' => 'registered',
5519				'type' => 'trackers',
5520				'admin' => false,
5521				'prefs' => ['feature_trackers'],
5522				'scope' => 'object',
5523			],
5524			[
5525				'name' => 'tiki_p_tracker_dump',
5526				'description' => tra('Can save a CSV backup of all trackers'),
5527				'level' => 'admin',
5528				'type' => 'trackers',
5529				'admin' => false,
5530				'prefs' => ['feature_trackers'],
5531				'scope' => 'global',
5532			],
5533			[
5534				'name' => 'tiki_p_tabular_admin',
5535				'description' => tr('Manage tracker views'),
5536				'level' => 'admin',
5537				'type' => 'tabular',
5538				'admin' => true,
5539				'prefs' => ['tracker_tabular_enabled'],
5540				'scope' => 'object',
5541			],
5542			[
5543				'name' => 'tiki_p_tabular_list',
5544				'description' => tr('View list view of tracker tabular data. Tracker item permissions apply.'),
5545				'level' => 'registered',
5546				'type' => 'tabular',
5547				'admin' => false,
5548				'prefs' => ['tracker_tabular_enabled'],
5549				'scope' => 'object',
5550			],
5551			[
5552				'name' => 'tiki_p_tabular_export',
5553				'description' => tr('Export a tracker tabular view to CSV. Tracker permissions may not apply.'),
5554				'level' => 'editors',
5555				'type' => 'tabular',
5556				'admin' => false,
5557				'prefs' => ['tracker_tabular_enabled'],
5558				'scope' => 'object',
5559			],
5560			[
5561				'name' => 'tiki_p_tabular_import',
5562				'description' => tr('Import a CSV file into a tabular tracker. Tracker permissions may not apply.'),
5563				'level' => 'editors',
5564				'type' => 'tabular',
5565				'admin' => false,
5566				'prefs' => ['tracker_tabular_enabled'],
5567				'scope' => 'object',
5568			],
5569			[
5570				'name' => 'tiki_p_trigger_transition',
5571				'description' => tra('Can trigger the transition between two states'),
5572				'level' => 'admin',
5573				'type' => 'transition',
5574				'admin' => false,
5575				'prefs' => ['feature_group_transition', 'feature_category_transition'],
5576				'scope' => 'object',
5577			],
5578			[
5579				'name' => 'tiki_p_admin_users',
5580				'description' => tra('Can admin users'),
5581				'level' => 'admin',
5582				'type' => 'user',
5583				'admin' => false,
5584				'prefs' => [],
5585				'scope' => 'global',
5586			],
5587			[
5588				'name' => 'tiki_p_cache_bookmarks',
5589				'description' => tra('Can cache user bookmarks'),
5590				'level' => 'admin',
5591				'type' => 'user',
5592				'admin' => false,
5593				'prefs' => ['feature_user_bookmarks'],
5594				'scope' => 'global',
5595			],
5596			[
5597				'name' => 'tiki_p_configure_modules',
5598				'description' => tra('Can configure modules'),
5599				'level' => 'registered',
5600				'type' => 'user',
5601				'admin' => false,
5602				'prefs' => ['feature_modulecontrols'],
5603				'scope' => 'global',
5604			],
5605			[
5606				'name' => 'tiki_p_create_bookmarks',
5607				'description' => tra('Can create user bookmarks'),
5608				'level' => 'registered',
5609				'type' => 'user',
5610				'admin' => false,
5611				'prefs' => ['feature_user_bookmarks'],
5612				'scope' => 'global',
5613			],
5614			[
5615				'name' => 'tiki_p_minical',
5616				'description' => tra('Can use the mini event calendar'),
5617				'level' => 'registered',
5618				'type' => 'user',
5619				'admin' => false,
5620				'prefs' => ['feature_minical'],
5621				'scope' => 'global',
5622			],
5623			[
5624				'name' => 'tiki_p_notepad',
5625				'description' => tra('Can use the notepad'),
5626				'level' => 'registered',
5627				'type' => 'user',
5628				'admin' => false,
5629				'prefs' => ['feature_notepad'],
5630				'scope' => 'global',
5631			],
5632			[
5633				'name' => 'tiki_p_tasks_admin',
5634				'description' => tra('Can admin public tasks'),
5635				'level' => 'admin',
5636				'type' => 'user',
5637				'admin' => false,
5638				'prefs' => ['feature_tasks'],
5639				'scope' => 'global',
5640			],
5641			[
5642				'name' => 'tiki_p_tasks',
5643				'description' => tra('Can use tasks'),
5644				'level' => 'registered',
5645				'type' => 'user',
5646				'admin' => false,
5647				'prefs' => ['feature_tasks'],
5648				'scope' => 'global',
5649			],
5650			[
5651				'name' => 'tiki_p_tasks_receive',
5652				'description' => tra('Can receive tasks from other users'),
5653				'level' => 'registered',
5654				'type' => 'user',
5655				'admin' => false,
5656				'prefs' => ['feature_tasks'],
5657				'scope' => 'global',
5658			],
5659			[
5660				'name' => 'tiki_p_tasks_send',
5661				'description' => tra('Can send tasks to other users'),
5662				'level' => 'registered',
5663				'type' => 'user',
5664				'admin' => false,
5665				'prefs' => ['feature_tasks'],
5666				'scope' => 'global',
5667			],
5668			[
5669				'name' => 'tiki_p_userfiles',
5670				'description' => tra('Can upload personal files'),
5671				'level' => 'registered',
5672				'type' => 'user',
5673				'admin' => false,
5674				'prefs' => ['feature_userfiles'],
5675				'scope' => 'global',
5676			],
5677			[
5678				'name' => 'tiki_p_usermenu',
5679				'description' => tra('Can create items in personal menu'),
5680				'level' => 'registered',
5681				'type' => 'user',
5682				'admin' => false,
5683				'prefs' => ['feature_usermenu'],
5684				'scope' => 'global',
5685			],
5686			[
5687				'name' => 'tiki_p_list_users',
5688				'description' => tra('Can list registered users'),
5689				'level' => 'registered',
5690				'type' => 'user',
5691				'admin' => false,
5692				'prefs' => [],
5693				'scope' => 'global',
5694			],
5695			[
5696				'name' => 'tiki_p_invite',
5697				'description' => tra('Can invite users by email, and include them in groups'),
5698				'level' => 'registered',
5699				'type' => 'user',
5700				'admin' => false,
5701				'prefs' => ['feature_invite'],
5702				'scope' => 'global',
5703			],
5704			[
5705				'name' => 'tiki_p_delete_account',
5706				'description' => tra('Can delete his/her own account'),
5707				'level' => 'admin',
5708				'type' => 'user',
5709				'admin' => false,
5710				'prefs' => [],
5711				'scope' => 'global',
5712			],
5713			[
5714				'name' => 'tiki_p_use_webmail',
5715				'description' => tra('Can use webmail'),
5716				'level' => 'registered',
5717				'type' => 'webmail',
5718				'admin' => false,
5719				'prefs' => ['feature_webmail', 'feature_contacts'],
5720				'scope' => 'global',
5721			],
5722			[
5723				'name' => 'tiki_p_use_group_webmail',
5724				'description' => tra('Can use group webmail'),
5725				'level' => 'registered',
5726				'type' => 'webmail',
5727				'admin' => false,
5728				'prefs' => ['feature_webmail', 'feature_contacts'],
5729				'scope' => 'global',
5730			],
5731			[
5732				'name' => 'tiki_p_admin_group_webmail',
5733				'description' => tra('Can admin group webmail accounts'),
5734				'level' => 'admin',
5735				'type' => 'webmail',
5736				'admin' => false,
5737				'prefs' => ['feature_webmail', 'feature_contacts'],
5738				'scope' => 'global',
5739			],
5740			[
5741				'name' => 'tiki_p_use_personal_webmail',
5742				'description' => tra('Can use personal webmail accounts'),
5743				'level' => 'registered',
5744				'type' => 'webmail',
5745				'admin' => false,
5746				'prefs' => ['feature_webmail', 'feature_contacts'],
5747				'scope' => 'global',
5748			],
5749			[
5750				'name' => 'tiki_p_admin_personal_webmail',
5751				'description' => tra('Can admin personal webmail accounts'),
5752				'level' => 'registered',
5753				'type' => 'webmail',
5754				'admin' => false,
5755				'prefs' => ['feature_webmail', 'feature_contacts'],
5756				'scope' => 'global',
5757			],
5758			[
5759				'name' => 'tiki_p_view',
5760				'description' => tra('Can view page/pages'),
5761				'level' => 'basic',
5762				'type' => 'wiki',
5763				'admin' => false,
5764				'prefs' => ['feature_wiki'],
5765				'scope' => 'object',
5766			],
5767			[
5768				'name' => 'tiki_p_edit',
5769				'description' => tra('Can edit pages'),
5770				'level' => 'registered',
5771				'type' => 'wiki',
5772				'admin' => false,
5773				'prefs' => ['feature_wiki'],
5774				'scope' => 'object',
5775			],
5776			[
5777				'name' => 'tiki_p_edit_inline',
5778				'description' => tra('Can inline-edit pages'),
5779				'level' => 'registered',
5780				'type' => 'wiki',
5781				'admin' => false,
5782				'prefs' => ['feature_wiki'],
5783				'scope' => 'object',
5784			],
5785			[
5786				'name' => 'tiki_p_wiki_view_history',
5787				'description' => tra('Can view wiki history'),
5788				'level' => 'basic',
5789				'type' => 'wiki',
5790				'admin' => false,
5791				'prefs' => ['feature_history'],
5792				'scope' => 'object',
5793			],
5794			[
5795				'name' => 'tiki_p_admin_wiki',
5796				'description' => tra('Can admin the wiki'),
5797				'level' => 'admin',
5798				'type' => 'wiki',
5799				'admin' => true,
5800				'prefs' => ['feature_wiki'],
5801				'scope' => 'object',
5802			],
5803			[
5804				'name' => 'tiki_p_assign_perm_wiki_page',
5805				'description' => tra('Can assign permissions to wiki pages'),
5806				'level' => 'admin',
5807				'type' => 'wiki',
5808				'admin' => false,
5809				'prefs' => ['feature_wiki'],
5810				'scope' => 'object',
5811			],
5812			[
5813				'name' => 'tiki_p_edit_copyrights',
5814				'description' => tra('Can edit copyright notices'),
5815				'level' => 'editors',
5816				'type' => 'wiki',
5817				'admin' => false,
5818				'prefs' => ['wiki_feature_copyrights'],
5819				'scope' => 'object',
5820			],
5821			[
5822				'name' => 'tiki_p_edit_dynvar',
5823				'description' => tra('Can edit dynamic variables'),
5824				'level' => 'editors',
5825				'type' => 'wiki',
5826				'admin' => false,
5827				'prefs' => ['feature_wiki'],
5828				'scope' => 'global',
5829			],
5830			[
5831				'name' => 'tiki_p_export_wiki',
5832				'description' => tra('Can export wiki pages using the export feature'),
5833				'level' => 'admin',
5834				'type' => 'wiki',
5835				'admin' => false,
5836				'prefs' => ['feature_wiki_export'],
5837				'scope' => 'object',
5838			],
5839			[
5840				'name' => 'tiki_p_lock',
5841				'description' => tra('Can lock pages'),
5842				'level' => 'editors',
5843				'type' => 'wiki',
5844				'admin' => false,
5845				'prefs' => ['feature_wiki_usrlock'],
5846				'scope' => 'object',
5847			],
5848			[
5849				'name' => 'tiki_p_minor',
5850				'description' => tra('Can save as a minor edit'),
5851				'level' => 'registered',
5852				'type' => 'wiki',
5853				'admin' => false,
5854				'prefs' => ['wiki_edit_minor'],
5855				'scope' => 'object',
5856			],
5857			[
5858				'name' => 'tiki_p_remove',
5859				'description' => tra('Can remove'),
5860				'level' => 'editors',
5861				'type' => 'wiki',
5862				'admin' => false,
5863				'prefs' => ['feature_wiki'],
5864				'scope' => 'object',
5865			],
5866			[
5867				'name' => 'tiki_p_rename',
5868				'description' => tra('Can rename pages'),
5869				'level' => 'editors',
5870				'type' => 'wiki',
5871				'admin' => false,
5872				'prefs' => ['feature_wiki'],
5873				'scope' => 'object',
5874			],
5875			[
5876				'name' => 'tiki_p_rollback',
5877				'description' => tra('Can roll back pages'),
5878				'level' => 'editors',
5879				'type' => 'wiki',
5880				'admin' => false,
5881				'prefs' => ['feature_wiki'],
5882				'scope' => 'object',
5883			],
5884			[
5885				'name' => 'tiki_p_upload_picture',
5886				'description' => tra('Can upload pictures to wiki pages'),
5887				'level' => 'registered',
5888				'type' => 'wiki',
5889				'admin' => false,
5890				'prefs' => ['feature_wiki_pictures'],
5891				'scope' => 'object',
5892			],
5893			[
5894				'name' => 'tiki_p_use_as_template',
5895				'description' => tra('Can use the page as a template for a tracker or unified search'),
5896				'level' => 'basic',
5897				'type' => 'wiki',
5898				'admin' => false,
5899				'prefs' => ['feature_wiki'],
5900				'scope' => 'object',
5901			],
5902			[
5903				'name' => 'tiki_p_wiki_view_ref',
5904				'description' => tra('Can view in module and feed the wiki pages reference'),
5905				'level' => 'basic',
5906				'type' => 'wiki',
5907				'admin' => false,
5908				'prefs' => ['feature_wiki'],
5909				'scope' => 'object',
5910			],
5911			[
5912				'name' => 'tiki_p_wiki_admin_attachments',
5913				'description' => tra('Can admin attachments on wiki pages'),
5914				'level' => 'editors',
5915				'type' => 'wiki',
5916				'admin' => false,
5917				'prefs' => ['feature_wiki_attachments'],
5918				'scope' => 'object',
5919			],
5920			[
5921				'name' => 'tiki_p_wiki_admin_ratings',
5922				'description' => tra('Can add and change ratings on wiki pages'),
5923				'level' => 'admin',
5924				'type' => 'wiki',
5925				'admin' => false,
5926				'prefs' => ['feature_wiki_ratings'],
5927				'scope' => 'global',
5928			],
5929			[
5930				'name' => 'tiki_p_wiki_attach_files',
5931				'description' => tra('Can attach files to wiki pages'),
5932				'level' => 'registered',
5933				'type' => 'wiki',
5934				'admin' => false,
5935				'prefs' => ['feature_wiki_attachments'],
5936				'scope' => 'object',
5937			],
5938			[
5939				'name' => 'tiki_p_wiki_view_attachments',
5940				'description' => tra('Can view and download wiki page attachments'),
5941				'level' => 'registered',
5942				'type' => 'wiki',
5943				'admin' => false,
5944				'prefs' => ['feature_wiki_attachments'],
5945				'scope' => 'object',
5946			],
5947			[
5948				'name' => 'tiki_p_wiki_view_comments',
5949				'description' => tra('Can view wiki comments'),
5950				'level' => 'basic',
5951				'type' => 'wiki',
5952				'admin' => false,
5953				'prefs' => ['feature_wiki_comments'],
5954				'scope' => 'object',
5955			],
5956			[
5957				'name' => 'tiki_p_wiki_view_ratings',
5958				'description' => tra('Can view rating of wiki pages'),
5959				'level' => 'basic',
5960				'type' => 'wiki',
5961				'admin' => false,
5962				'prefs' => ['feature_wiki_ratings'],
5963				'scope' => 'object',
5964			],
5965			[
5966				'name' => 'tiki_p_wiki_view_source',
5967				'description' => tra('Can view source of wiki pages'),
5968				'level' => 'basic',
5969				'type' => 'wiki',
5970				'admin' => false,
5971				'prefs' => ['feature_source'],
5972				'scope' => 'object',
5973			],
5974			[
5975				'name' => 'tiki_p_wiki_vote_ratings',
5976				'description' => tra('Can participate in rating of wiki pages'),
5977				'level' => 'registered',
5978				'type' => 'wiki',
5979				'admin' => false,
5980				'prefs' => ['feature_wiki_ratings'],
5981				'scope' => 'object',
5982			],
5983			[
5984				'name' => 'tiki_p_wiki_view_similar',
5985				'description' => tra('Can view similar wiki pages'),
5986				'level' => 'registered',
5987				'type' => 'wiki',
5988				'admin' => false,
5989				'prefs' => ['feature_likePages'],
5990				'scope' => 'object',
5991			],
5992			[
5993				'name' => 'tiki_p_view_backlink',
5994				'description' => tra('View page backlinks'),
5995				'level' => 'basic',
5996				'type' => 'wiki',
5997				'admin' => false,
5998				'prefs' => ['feature_backlinks'],
5999				'scope' => 'object',
6000			],
6001			[
6002				'name' => 'tiki_p_wiki_view_latest',
6003				'description' => tra('Can view unapproved revisions of pages'),
6004				'level' => 'registered',
6005				'type' => 'wiki',
6006				'admin' => false,
6007				'prefs' => ['flaggedrev_approval'],
6008				'scope' => 'object',
6009			],
6010			[
6011				'name' => 'tiki_p_wiki_approve',
6012				'description' => tra('Can approve revisions of pages'),
6013				'level' => 'editors',
6014				'type' => 'wiki',
6015				'admin' => false,
6016				'prefs' => ['flaggedrev_approval'],
6017				'scope' => 'object',
6018			],
6019			[
6020				'name' => 'tiki_p_page_contribution_view',
6021				'description' => tra('Can view contributions to a page'),
6022				'level' => 'basic',
6023				'type' => 'wiki',
6024				'admin' => false,
6025				'prefs' => ['feature_page_contribution'],
6026				'scope' => 'global',
6027			],
6028			[
6029				'name' => 'tiki_p_use_references',
6030				'description' => tra('Can use reference library items'),
6031				'level' => 'editors',
6032				'type' => 'wiki',
6033				'admin' => false,
6034				'prefs' => ['feature_references'],
6035				'scope' => 'object',
6036			],
6037			[
6038				'name' => 'tiki_p_edit_references',
6039				'description' => tra('Can add to, edit and remove reference library items'),
6040				'level' => 'editors',
6041				'type' => 'wiki',
6042				'admin' => false,
6043				'prefs' => ['feature_references'],
6044				'scope' => 'object',
6045			],
6046			[
6047				'name' => 'tiki_p_admin_structures',
6048				'description' => tra('Can administer structures'),
6049				'level' => 'admin',
6050				'type' => 'wiki structure',		// NB "wiki structure" objects use the perms set on the top "wiki page"
6051				'admin' => true,
6052				'prefs' => ['feature_wiki_structure'],
6053				'scope' => 'object',
6054			],
6055			[
6056				'name' => 'tiki_p_edit_structures',
6057				'description' => tra('Can create and edit structures'),
6058				'level' => 'editors',
6059				'type' => 'wiki structure',		// NB "wiki structure" objects use the perms set on the top "wiki page"
6060				'admin' => false,
6061				'prefs' => ['feature_wiki_structure'],
6062				'scope' => 'object',
6063			],
6064			[
6065				'name' => 'tiki_p_lock_structures',
6066				'description' => tra('Can lock structures'),
6067				'level' => 'editors',
6068				'type' => 'wiki structure',		// NB "wiki structure" objects use the perms set on the top "wiki page"
6069				'admin' => false,
6070				'prefs' => ['feature_wiki_structure', 'lock_wiki_structures'],
6071				'scope' => 'object',
6072			],
6073			[
6074				'name' => 'tiki_p_watch_structure',
6075				'description' => tra('Can watch structures'),
6076				'level' => 'registered',
6077				'type' => 'wiki structure',		// NB "wiki structure" objects use the perms set on the top "wiki page"
6078				'admin' => false,
6079				'prefs' => ['feature_wiki_structure'],
6080				'scope' => 'global',
6081			],
6082			[
6083				'name' => 'tiki_p_admin',
6084				'description' => tra('Administrator can manage users, groups and permissions and all features'),
6085				'level' => 'admin',
6086				'type' => 'tiki',
6087				'admin' => true,
6088				'prefs' => [],
6089				'scope' => 'global',
6090			],
6091			[
6092				'name' => 'tiki_p_edit_grouplimitedinfo',
6093				'description' => tra('Can edit the name and description of a group.'),
6094				'level' => 'admin',
6095				'type' => 'group',
6096				'admin' => false,
6097				'prefs' => [],
6098				'scope' => 'object',
6099			],
6100			[
6101				'name' => 'tiki_p_access_closed_site',
6102				'description' => tra('Can access site when closed'),
6103				'level' => 'admin',
6104				'type' => 'tiki',
6105				'admin' => false,
6106				'prefs' => [],
6107				'scope' => 'global',
6108			],
6109			[
6110				'name' => 'tiki_p_admin_banners',
6111				'description' => tra('Administrator can admin banners'),
6112				'level' => 'admin',
6113				'type' => 'tiki',
6114				'admin' => false,
6115				'prefs' => ['feature_banners'],
6116				'scope' => 'global',
6117			],
6118			[
6119				'name' => 'tiki_p_admin_banning',
6120				'description' => tra('Can ban users or IP addresses'),
6121				'level' => 'admin',
6122				'type' => 'tiki',
6123				'admin' => false,
6124				'prefs' => ['feature_banning'],
6125				'scope' => 'global',
6126			],
6127			[
6128				'name' => 'tiki_p_admin_dynamic',
6129				'description' => tra('Can admin the dynamic content system'),
6130				'level' => 'editors',
6131				'type' => 'tiki',
6132				'admin' => false,
6133				'prefs' => ['feature_dynamic_content'],
6134				'scope' => 'global',
6135			],
6136			[
6137				'name' => 'tiki_p_admin_integrator',
6138				'description' => tra('Can admin integrator repositories and rules'),
6139				'level' => 'admin',
6140				'type' => 'tiki',
6141				'admin' => false,
6142				'prefs' => ['feature_integrator'],
6143				'scope' => 'global',
6144			],
6145			[
6146				'name' => 'tiki_p_send_mailin',
6147				'description' => tra('Can send email to a mail-in accounts, and have the email integrated. Only applies when the mail-in setting "anonymous" = n'),
6148				'level' => 'basic',
6149				'type' => 'tiki',
6150				'admin' => false,
6151				'prefs' => ['feature_mailin', 'feature_wiki'],
6152				'scope' => 'global',
6153			],
6154			[
6155				'name' => 'tiki_p_admin_mailin',
6156				'description' => tra('Can admin mail-in accounts'),
6157				'level' => 'admin',
6158				'type' => 'tiki',
6159				'admin' => false,
6160				'prefs' => ['feature_mailin'],
6161				'scope' => 'global',
6162			],
6163			[
6164				'name' => 'tiki_p_admin_objects',
6165				'description' => tra('Can edit object permissions'),
6166				'level' => 'admin',
6167				'type' => 'tiki',
6168				'admin' => false,
6169				'prefs' => [],
6170				'scope' => 'global',
6171			],
6172			[
6173				'name' => 'tiki_p_admin_rssmodules',
6174				'description' => tra('Can admin external feeds'),
6175				'level' => 'admin',
6176				'type' => 'tiki',
6177				'admin' => false,
6178				'prefs' => [],
6179				'scope' => 'global',
6180			],
6181			[
6182				'name' => 'tiki_p_clean_cache',
6183				'description' => tra('Can clean cache'),
6184				'level' => 'editors',
6185				'type' => 'tiki',
6186				'admin' => false,
6187				'prefs' => [],
6188				'scope' => 'global',
6189			],
6190			[
6191				'name' => 'tiki_p_create_css',
6192				'description' => tra('Can create a new CSS file (style sheet) appended with -user'),
6193				'level' => 'registered',
6194				'type' => 'tiki',
6195				'admin' => false,
6196				'prefs' => ['feature_editcss'],
6197				'scope' => 'global',
6198			],
6199			[
6200				'name' => 'tiki_p_detach_translation',
6201				'description' => tra('Can remove the association between two pages in a translation set'),
6202				'level' => 'editors',
6203				'type' => 'tiki',
6204				'admin' => false,
6205				'prefs' => ['feature_multilingual'],
6206				'scope' => 'object',
6207				'apply_to' => ['wiki', 'trackers', 'articles'],
6208			],
6209			[
6210				'name' => 'tiki_p_edit_cookies',
6211				'description' => tra('Can admin cookies'),
6212				'level' => 'editors',
6213				'type' => 'tiki',
6214				'admin' => false,
6215				'prefs' => [],
6216				'scope' => 'global',
6217			],
6218			[
6219				'name' => 'tiki_p_edit_languages',
6220				'description' => tra('Can edit translations and create new languages'),
6221				'level' => 'editors',
6222				'type' => 'tiki',
6223				'admin' => false,
6224				'prefs' => [],
6225				'scope' => 'global',
6226			],
6227			[
6228				'name' => 'tiki_p_edit_menu',
6229				'description' => tra('Can edit menus'),
6230				'level' => 'admin',
6231				'type' => 'tiki',
6232				'admin' => false,
6233				'prefs' => [],
6234				'scope' => 'global',
6235			],
6236			[
6237				'name' => 'tiki_p_edit_menu_option',
6238				'description' => tra('Can edit menu options'),
6239				'level' => 'admin',
6240				'type' => 'tiki',
6241				'admin' => false,
6242				'prefs' => [],
6243				'scope' => 'global',
6244			],
6245			[
6246				'name' => 'tiki_p_edit_templates',
6247				'description' => tra('Can edit site templates'),
6248				'level' => 'admin',
6249				'type' => 'tiki',
6250				'admin' => false,
6251				'prefs' => ['feature_edit_templates'],
6252				'scope' => 'global',
6253			],
6254			[
6255				'name' => 'tiki_p_search',
6256				'description' => tra('Can search'),
6257				'level' => 'basic',
6258				'type' => 'tiki',
6259				'admin' => false,
6260				'prefs' => [], // This could depend on feature_search when FULLTEXT search (feature_search_fulltext) is removed
6261				'scope' => 'global',
6262			],
6263			[
6264				'name' => 'tiki_p_site_report',
6265				'description' => tra('Can report a link to the webmaster'),
6266				'level' => 'basic',
6267				'type' => 'tiki',
6268				'admin' => false,
6269				'prefs' => ['feature_site_report'],
6270				'scope' => 'object',
6271			],
6272			[
6273				'name' => 'tiki_p_share',
6274				'description' => tra('Can share a page (email, Twitter, Facebook, message, forums)'),
6275				'level' => 'basic',
6276				'type' => 'tiki',
6277				'admin' => false,
6278				'prefs' => ['feature_share'],
6279				'scope' => 'global',
6280			],
6281			[
6282				'name' => 'tiki_p_use_HTML',
6283				'description' => tra('Can use HTML in pages'),
6284				'level' => 'editors',
6285				'type' => 'tiki',
6286				'admin' => false,
6287				'prefs' => ['feature_wiki_allowhtml', 'feature_articles'],
6288				'scope' => 'global',
6289			],
6290			[
6291				'name' => 'tiki_p_view_actionlog',
6292				'description' => tra('Can view action log'),
6293				'level' => 'registered',
6294				'type' => 'tiki',
6295				'admin' => false,
6296				'prefs' => ['feature_actionlog'],
6297				'scope' => 'global',
6298			],
6299			[
6300				'name' => 'tiki_p_view_actionlog_owngroups',
6301				'description' => tra('Can view the action log for users of his or her groups'),
6302				'level' => 'registered',
6303				'type' => 'tiki',
6304				'admin' => false,
6305				'prefs' => ['feature_actionlog'],
6306				'scope' => 'global',
6307			],
6308			[
6309				'name' => 'tiki_p_view_integrator',
6310				'description' => tra('Can view integrated repositories'),
6311				'level' => 'basic',
6312				'type' => 'tiki',
6313				'admin' => false,
6314				'prefs' => ['feature_integrator'],
6315				'scope' => 'global',
6316			],
6317			[
6318				'name' => 'tiki_p_ratings_view_results',
6319				'description' => tra('Can view results from user ratings'),
6320				'level' => 'basic',
6321				'type' => 'tiki',
6322				'admin' => false,
6323				'prefs' => [],
6324				'scope' => 'object',
6325				'apply_to' => ['wiki', 'trackers', 'articles', 'comments', 'forums'],
6326			],
6327			[
6328				'name' => 'tiki_p_view_referer_stats',
6329				'description' => tra('Can view referrer stats'),
6330				'level' => 'editors',
6331				'type' => 'tiki',
6332				'admin' => false,
6333				'prefs' => ['feature_referer_stats'],
6334				'scope' => 'global',
6335			],
6336			[
6337				'name' => 'tiki_p_view_stats',
6338				'description' => tra('Can view site stats'),
6339				'level' => 'basic',
6340				'type' => 'tiki',
6341				'admin' => false,
6342				'prefs' => ['feature_stats'],
6343				'scope' => 'global',
6344			],
6345			[
6346				'name' => 'tiki_p_view_templates',
6347				'description' => tra('Can view site templates'),
6348				'level' => 'admin',
6349				'type' => 'tiki',
6350				'admin' => false,
6351				'prefs' => ['feature_edit_templates'],
6352				'scope' => 'global',
6353			],
6354			[
6355				'name' => 'tiki_p_view_webservices',
6356				'description' => tra('Can view results from webservice requests'),
6357				'level' => 'basic',
6358				'type' => 'tiki',
6359				'admin' => false,
6360				'prefs' => ['feature_webservices'],
6361				'scope' => 'global',
6362			],
6363			[
6364				'name' => 'tiki_p_admin_webservices',
6365				'description' => tra('Can administer webservices'),
6366				'level' => 'admin',
6367				'type' => 'tiki',
6368				'admin' => false,
6369				'prefs' => ['feature_webservices'],
6370				'scope' => 'global',
6371			],
6372			[
6373				'name' => 'tiki_p_admin_toolbars',
6374				'description' => tra('Can admin toolbars'),
6375				'level' => 'admin',
6376				'type' => 'tiki',
6377				'admin' => false,
6378				'prefs' => [],
6379				'scope' => 'global',
6380			],
6381			[
6382				'name' => 'tiki_p_trust_input',
6383				'description' => tra('Trust all user inputs including plugins (no security checks)'),
6384				'level' => 'admin',
6385				'type' => 'tiki',
6386				'admin' => false,
6387				'prefs' => ['tiki_allow_trust_input'],
6388				'scope' => 'global',
6389			],
6390			[
6391				'name' => 'tiki_p_plugin_viewdetail',
6392				'description' => tra('Can view unapproved plugin details'),
6393				'level' => 'registered',
6394				'type' => 'tiki',
6395				'admin' => false,
6396				'prefs' => ['feature_wiki'],
6397				'scope' => 'global',
6398			],
6399			[
6400				'name' => 'tiki_p_plugin_preview',
6401				'description' => tra('Can execute unapproved plugin registered'),
6402				'level' => 'admin',
6403				'type' => 'tiki',
6404				'admin' => false,
6405				'prefs' => ['feature_wiki'],
6406				'scope' => 'global',
6407			],
6408			[
6409				'name' => 'tiki_p_plugin_approve',
6410				'description' => tra('Can approve plugin execution'),
6411				'level' => 'editors',
6412				'type' => 'tiki',
6413				'admin' => false,
6414				'prefs' => ['feature_wiki'],
6415				'scope' => 'global',
6416			],
6417			[
6418				'name' => 'tiki_p_admin_notifications',
6419				'description' => tra('Can admin mail notifications'),
6420				'level' => 'editors',
6421				'type' => 'tiki',
6422				'admin' => false,
6423				'prefs' => [],
6424				'scope' => 'global',
6425			],
6426			[
6427				'name' => 'tiki_p_admin_importer',
6428				'description' => tra('Can use the importer'),
6429				'level' => 'admin',
6430				'type' => 'tiki',
6431				'admin' => false,
6432				'prefs' => [],
6433				'scope' => 'global',
6434			],
6435			[
6436				'name' => 'tiki_p_modify_object_categories',
6437				'description' => tra('Can change the categories of an object'),
6438				'level' => 'editors',
6439				'type' => 'tiki',
6440				'admin' => false,
6441				'prefs' => ['feature_categories'],
6442				'scope' => 'object',
6443				'apply_to' => ['wiki', 'trackers'],
6444			],
6445			[
6446				'name' => 'tiki_p_admin_modules',
6447				'description' => tra('User can administer modules'),
6448				'level' => 'admin',
6449				'type' => 'tiki',
6450				'admin' => false,
6451				'prefs' => [],
6452				'scope' => 'global',
6453			],
6454			[
6455				'name' => 'tiki_p_edit_switch_mode',
6456				'description' => tra('Can switch between wiki and WYSIWYG modes while editing'),
6457				'level' => 'editors',
6458				'type' => 'tiki',
6459				'admin' => false,
6460				'prefs' => ['feature_wysiwyg'],
6461				'scope' => 'global',
6462			],
6463			[
6464				'name' => 'tiki_p_workspace_instantiate',
6465				'description' => tra('Can create a new workspace for a given template'),
6466				'level' => 'admin',
6467				'type' => 'workspace',
6468				'admin' => false,
6469				'prefs' => ['workspace_ui'],
6470				'scope' => 'object',
6471			],
6472			[
6473				'name' => 'tiki_p_goal_admin',
6474				'description' => tr('Can manage all aspects of a goal'),
6475				'level' => 'admin',
6476				'type' => 'goal',
6477				'admin' => true,
6478				'prefs' => ['goal_enabled'],
6479				'scope' => 'object',
6480			],
6481			[
6482				'name' => 'tiki_p_goal_modify_eligible',
6483				'description' => tr('Can manage who is eligible for a goal'),
6484				'level' => 'admin',
6485				'type' => 'goal',
6486				'admin' => false,
6487				'prefs' => ['goal_enabled'],
6488				'scope' => 'object',
6489			],
6490		];
6491
6492		$permissions = $this->getSortingPermissions($permissions);
6493
6494		$cachelib->cacheItem('rawpermissions' . $prefs['language'], serialize($permissions));
6495		return $permissions;
6496	}
6497
6498	function get_permissions($offset = 0, $maxRecords = -1, $sort_mode = 'permName_asc', $find = '', $type = 'all', $group = '', $enabledOnly = false)
6499	{
6500		if ($enabledOnly) {
6501			$raw = $this->get_enabled_permissions();
6502		} else {
6503			$raw = $this->get_raw_permissions();
6504		}
6505
6506		$ret = [];
6507
6508		foreach ($raw as $permission) {
6509			if ($find && stripos($permission['name'], $find) === false) {
6510				continue;
6511			}
6512
6513			if ($type === 'global' || $type == 'all') {
6514				$ret[] = $this->permission_compatibility($permission);
6515			} elseif ($type == $permission['type'] && $permission['scope'] == 'object') {
6516				$ret[] = $this->permission_compatibility($permission);
6517			} elseif ($type == 'category' && $permission['scope'] != 'global') {
6518				$ret[] = $this->permission_compatibility($permission);
6519			} elseif ($permission['scope'] == 'object' && isset($permission['apply_to']) && in_array($type, $permission['apply_to'])) {
6520				$ret[] = $this->permission_compatibility($permission);
6521			}
6522		}
6523
6524		if ($group) {
6525			if (is_string($group)) {
6526				foreach ($ret as &$res) {
6527					if ($this->group_has_permission($group, $res['permName'])) {
6528						$res['hasPerm'] = 'y';
6529					} else {
6530						$res['hasPerm'] = 'n';
6531					}
6532				}
6533			} elseif (is_array($group)) {
6534				foreach ($ret as &$res) {
6535					foreach ($group as $groupName) {
6536						if ($this->group_has_permission($groupName, $res['permName'])) {
6537							$res[$groupName . '_hasPerm'] = 'y';
6538						} else {
6539							$res[$groupName . '_hasPerm'] = 'n';
6540						}
6541					}
6542				}
6543			}
6544		}
6545
6546		return [
6547			'data' => $ret,
6548			'cant' => count($ret),
6549		];
6550	}
6551
6552	private function permission_compatibility($newFormat)
6553	{
6554		$newFormat['shortName'] = substr($newFormat['name'], strlen('tiki_p_'));
6555		$newFormat['permName'] = $newFormat['name'];
6556		$newFormat['permDesc'] = $newFormat['description'];
6557		$newFormat['feature_checks'] = implode(',', $newFormat['prefs']);
6558
6559		return $newFormat;
6560	}
6561
6562	function get_permission_types()
6563	{
6564		$ret = [];
6565
6566		foreach ($this->get_raw_permissions() as $perm) {
6567			if (! isset($ret[$perm['type']])) {
6568				$ret[$perm['type']] = true;
6569			}
6570		}
6571
6572		return array_keys($ret);
6573	}
6574
6575	function get_group_permissions($group)
6576	{
6577		$cachelib = TikiLib::lib('cache');
6578		if (! $ret = $cachelib->getSerialized("groupperms_$group")) {
6579			$query = 'select `permName` from `users_grouppermissions` where `groupName`=?';
6580			$result = $this->query($query, [$group]);
6581			$ret = [];
6582
6583			while ($res = $result->fetchRow()) {
6584				$ret[] = $res['permName'];
6585			}
6586
6587			$cachelib->cacheItem("groupperms_$group", serialize($ret));
6588		}
6589
6590		return $ret;
6591	}
6592
6593	function assign_permission_to_group($perm, $group)
6594	{
6595		$query = 'delete from `users_grouppermissions` where `groupName` = ? and `permName` = ?';
6596		$result = $this->query($query, [$group, $perm]);
6597
6598		$query = 'insert into `users_grouppermissions`(`groupName`, `permName`) values(?, ?)';
6599		$result = $this->query($query, [$group, $perm]);
6600
6601		$cachelib = TikiLib::lib('cache');
6602		$cachelib->empty_type_cache('fgals_perms');
6603		$cachelib->invalidate("groupperms_$group");
6604
6605		$menulib = TikiLib::lib('menu');
6606		$menulib->empty_menu_cache();
6607
6608		return true;
6609	}
6610
6611	function get_user_permissions($user)
6612	{
6613		$groups = $this->get_user_groups($user);
6614
6615		$ret = [];
6616		foreach ($groups as $group) {
6617			$perms = $this->get_group_permissions($group);
6618
6619			foreach ($perms as $perm) {
6620				$ret[] = $perm;
6621			}
6622		}
6623
6624		return $ret;
6625	}
6626
6627	function user_has_permission($user, $perm)
6628	{
6629		// Get user_groups ?
6630		$groups = $this->get_user_groups($user);
6631
6632		foreach ($groups as $group) {
6633			if ($this->group_has_permission($group, $perm) || $this->group_has_permission($group, 'tiki_p_admin')) {
6634				return true;
6635			}
6636		}
6637
6638		return false;
6639	}
6640
6641	function group_has_permission($group, $perm)
6642	{
6643		if (empty($perm) || empty($group)) {
6644			return 0;
6645		}
6646
6647		$engroup = urlencode($group);
6648		if (! isset($this->groupperm_cache[$engroup])) {
6649			$this->groupperm_cache[$engroup] = [];
6650			$groupperms = $this->get_group_permissions($group);
6651			foreach ($groupperms as $gp) {
6652				$this->groupperm_cache[$engroup][$gp] = 1;
6653			}
6654		}
6655
6656		return isset($this->groupperm_cache[$engroup][$perm]) ? 1 : 0;
6657	}
6658
6659	function remove_permission_from_group($perm, $group)
6660	{
6661		$query = "delete from `users_grouppermissions` where `permName` = ? and `groupName` = ?";
6662		$result = $this->query($query, [$perm, $group]);
6663
6664		$cachelib = TikiLib::lib('cache');
6665		$cachelib->empty_type_cache("fgals_perms");
6666		$cachelib->invalidate("groupperms_$group");
6667
6668		$menulib = TikiLib::lib('menu');
6669		$menulib->empty_menu_cache();
6670
6671		return true;
6672	}
6673
6674	function get_group_info($group, $sort_mode = 'groupName_asc')
6675	{
6676		$ret = [];
6677		if (is_array($group)) {
6678			if (count($group) > 0) {
6679				$query = 'select * from `users_groups` where `groupName` in (' .
6680					implode(',', array_fill(0, count($group), '?')) .
6681					') order by ' . $this->convertSortMode($sort_mode);
6682				$ret = $this->fetchAll($query, $group);
6683			}
6684		} else {
6685			$query = 'select * from `users_groups` where `groupName`=?';
6686			$result = $this->query($query, [$group]);
6687			$ret = $result->fetchRow();
6688			$perms = $this->get_group_permissions($group);
6689			$ret['perms'] = $perms;
6690		}
6691		return $ret;
6692	}
6693
6694	function get_groupId_info($groupId)
6695	{
6696		$query = 'select * from `users_groups` where `id`=?';
6697
6698		$result = $this->query($query, [$groupId]);
6699		$res = $result->fetchRow();
6700		$perms = $this->get_group_permissions($res['groupName']);
6701		$res['perms'] = $perms;
6702
6703		return $res;
6704	}
6705
6706	function assign_user_to_group($user, $group, $bulk = false)
6707	{
6708		if (! $this->group_exists($group)) {
6709			throw new Exception(tr('Cannot add user %0 to nonexistent group %1', $user, $group));
6710		}
6711		if (! $this->user_exists($user)) {
6712			throw new Exception(tr('Cannot add nonexistent user %0 to group %1', $user, $group));
6713		}
6714
6715		$groupInfo = $this->get_group_info($group);
6716		if ($groupInfo["isRole"] == "y") {
6717			throw new Exception(tr('Role groups can\'t have users.'));
6718		}
6719
6720		global $prefs, $tiki_p_admin, $page;
6721		$cachelib = TikiLib::lib('cache');
6722		$tikilib = TikiLib::lib('tiki');
6723		$access = TikiLib::lib('access');
6724
6725		if ($this->is_user_banned_from_group($user, $group)) {
6726			$msg = tr('User "%0" is banned from the group "%1".', $user, $group);
6727			if ($tiki_p_admin === 'y') {
6728				$access->check_authenticity($msg . ' ' . tra('Do you want to unban them and continue?'));
6729				$this->unban_user_from_group($user, $group);
6730			} else {
6731				$access->display_error($page, $msg);
6732			}
6733		}
6734
6735		$cachelib->invalidate('user_details_' . $user);
6736		$tikilib->invalidate_usergroups_cache($user);
6737		$this->invalidate_usergroups_cache($user); // this is needed as cache is present in this instance too
6738
6739		$group_ret = false;
6740		$userid = $this->get_user_id($user);
6741
6742		if ($userid > 0) {
6743			$query = "insert ignore into `users_usergroups`(`userId`,`groupName`, `created`) values(?,?,?)";
6744			$result = $this->query($query, [$userid, $group, $tikilib->now], -1, -1, false);
6745			$group_ret = true;
6746		}
6747		$this->update_group_expiries();
6748
6749		if ($group_ret) {
6750			$watches = $tikilib->get_event_watches('user_joins_group', $group);
6751			if (count($watches)) {
6752				require_once("lib/notifications/notificationemaillib.php");
6753				$smarty = TikiLib::lib('smarty');
6754				$smarty->assign('mail_user', $user);
6755				$smarty->assign('mail_group', $group);
6756				sendEmailNotification($watches, null, 'user_joins_group_notification_subject.tpl', null, 'user_joins_group_notification.tpl');
6757			}
6758			TikiLib::events()->trigger('tiki.user.groupjoin', [
6759				'type' => 'user',
6760				'object' => $user,
6761				'group' => $group,
6762				'bulk_import' => $bulk,
6763			]);
6764		}
6765
6766		return $group_ret;
6767	}
6768
6769	function assign_user_to_groups($user, $groups)
6770	{
6771		$cachelib = TikiLib::lib('cache');
6772		$cachelib->invalidate('user_details_' . $user);
6773
6774		$userid = $this->get_user_id($user);
6775
6776		$query = 'delete from `users_usergroups` where `userId`=?';
6777		$this->query($query, [$userid]);
6778
6779		$lastkey = end($groups);
6780		foreach ($groups as $k => $grp) {
6781			$this->assign_user_to_group($user, $grp, $k != $lastkey);
6782		}
6783	}
6784
6785	function ban_user_from_group($user, $group)
6786	{
6787		TikiLib::lib('relation')->add_relation('tiki.user.banned', 'user', $user, 'group', $group);
6788	}
6789
6790	function unban_user_from_group($user, $group)
6791	{
6792		$relationlib = TikiLib::lib('relation');
6793		$id = $relationlib->get_relation_id('tiki.user.banned', 'user', $user, 'group', $group);
6794		if ($id) {
6795			$relationlib->remove_relation($id);
6796		}
6797	}
6798
6799	function get_group_banned_users($group, $offset = 0, $max = -1, $what = 'login', $sort_mode = 'source_itemId_asc')
6800	{
6801		$res = TikiLib::lib('relation')->get_relations_to('group', $group, 'tiki.user.banned', $sort_mode);
6802		$temp = [];
6803		foreach ($res as $r) {
6804			$temp[] = $r['itemId'];
6805		}
6806		$max = $max > 0 ? $max : null;
6807		$ret['data'] = array_slice($temp, $offset, $max);
6808		$ret['cant'] = count($res);
6809		return $ret;
6810	}
6811
6812	function is_user_banned_from_group($user, $group)
6813	{
6814		return TikiLib::lib('relation')->get_relation_id('tiki.user.banned', 'user', $user, 'group', $group) > 0;
6815	}
6816
6817
6818	function confirm_user($user)
6819	{
6820		$cachelib = TikiLib::lib('cache');
6821
6822		$query = 'update `users_users` set `provpass`=?, valid=?, `email_confirm`=?, `waiting`=?, `registrationDate`=? where `login`=?';
6823		$result = $this->query($query, ['', null, $this->now, null, $this->now, $user]);
6824		$cachelib->invalidate('userslist');
6825		TikiLib::events()->trigger('tiki.user.update', ['type' => 'user', 'object' => $user]);
6826	}
6827
6828	function invalidate_account($user)
6829	{
6830		$cachelib = TikiLib::lib('cache');
6831		$tikilib = TikiLib::lib('tiki');
6832
6833		$query = 'update `users_users` set valid=?, `waiting`=? where `login`=?';
6834		$result = $this->query($query, [md5($tikilib->genPass()), 'u', $user]);
6835		$cachelib->invalidate('userslist');
6836		TikiLib::events()->trigger('tiki.user.update', ['type' => 'user', 'object' => $user]);
6837	}
6838
6839	function change_user_waiting($user, $who)
6840	{
6841		$query = 'update `users_users` set `waiting`=? where `login`=?';
6842		$this->query($query, [$who, $user]);
6843		TikiLib::events()->trigger('tiki.user.update', ['type' => 'user', 'object' => $user]);
6844	}
6845
6846	/**
6847	 * Adds a user in Tiki.
6848	 *
6849	 * @param user: username
6850	 * @param pass: password (may be an empty string)
6851	 * @param email: email
6852	 */
6853	function add_user($user, $pass, $email, $provpass = '', $pass_first_login = false, $valid = null, $openid_url = null, $waiting = null, $groups = [])
6854	{
6855		global $prefs;
6856		$cachelib = TikiLib::lib('cache');
6857		$tikilib = TikiLib::lib('tiki');
6858
6859		$autogenerate_uname = false;
6860		if ($prefs['login_autogenerate'] == 'y' && $user == '') {
6861			// only autogenerate if no username is provided (as many features might want to create real user name)
6862			// need to create as tmp uname first before replacing with user ID based number
6863			$user = "tmp" . md5((string) rand());
6864			$autogenerate_uname = true;
6865		}
6866
6867		$user = trim($user);
6868
6869		if ($this->user_exists($user)
6870				|| empty($user)
6871				|| (! empty($prefs['username_pattern']) && ! preg_match($prefs['username_pattern'], $user))
6872				|| strtolower($user) == 'anonymous'
6873				|| strtolower($user) == 'registered'
6874		) {
6875			return false;
6876		}
6877
6878		if ($prefs['user_unique_email'] == 'y' && $this->get_user_by_email($email)) {
6879			if ($autogenerate_uname) {
6880				// If the user to be added is to be autogenerated and the email already exists it means the user
6881				// is already created, for example in the 2nd pass in the registration process. To silently exit.
6882				return false;
6883			}
6884			$smarty = TikiLib::lib('smarty');
6885			$smarty->assign('errortype', 'login');
6886			$smarty->assign('msg', tra('We were unable to create your account because this email is already in use.'));
6887			$smarty->display('error.tpl');
6888			die;
6889		}
6890
6891		$userexists_cache[$user] = null;
6892
6893		// Generate a unique hash; this is also done below in set_user_fields()
6894		$lastLogin = null;
6895		if (empty($openid_url)) {
6896			$hash = password_hash($pass, PASSWORD_DEFAULT);
6897		} else {
6898			$hash = '';
6899			if (! isset($prefs['validateRegistration']) || $prefs['validateRegistration'] != 'y') {
6900				$lastLogin = $tikilib->now;
6901			}
6902		}
6903
6904		if ($pass_first_login) {
6905			$new_pass_confirm = 0;
6906		} else {
6907			$new_pass_confirm = $this->now;
6908		}
6909		$new_email_confirm = $this->now;
6910		$userTable = $this->table('users_users');
6911		$userId = $userTable->insert(
6912			[
6913				'login' => $user,
6914				'email' => $email,
6915				'provpass' => $provpass,
6916				'registrationDate' => (int) $this->now,
6917				'hash' => $hash,
6918				'pass_confirm' => (int) $new_pass_confirm,
6919				'email_confirm' => (int) $new_email_confirm,
6920				'created' => (int) $this->now,
6921				'valid' => $valid,
6922				'openid_url' => $openid_url,
6923				'lastLogin' => $lastLogin,
6924				'waiting' => $waiting,
6925			]
6926		);
6927
6928		if ($autogenerate_uname) {
6929			// only autogenerate if no username is provided (as many features might want to create real user name)
6930			$user = $this->autogenerate_login($userId);
6931			$userTable->update(
6932				[
6933					'login' => $user,
6934				],
6935				[
6936					'userId' => $userId,
6937				]
6938			);
6939		}
6940
6941		if (empty($groups)) {
6942			$this->assign_user_to_group($user, 'Registered');
6943		} else {
6944			if (is_array($groups)) {
6945				foreach ($groups as $grp) {
6946					$this->assign_user_to_group($user, $grp);
6947				}
6948			} else {
6949				$this->assign_user_to_group($user, 'Registered');
6950			}
6951		}
6952
6953		if ($prefs['eponymousGroups'] == 'y') {
6954			// Create a group just for this user, for permissions
6955			// assignment.
6956			$this->add_group($user, "Personal group for $user.", '', 0, 0, 0, '');
6957
6958			$this->assign_user_to_group($user, $user);
6959		}
6960
6961		$this->set_user_default_preferences($user, false); // do not force
6962
6963		if (! empty($prefs['user_tracker_auto_assign_item_field'])) {
6964			// try to assign the user tracker item if exists
6965			TikiLib::lib('trk')->update_user_item($user, $email, $prefs['user_tracker_auto_assign_item_field']);
6966		}
6967
6968		$cachelib->invalidate('userslist');
6969
6970		TikiLib::events()->trigger('tiki.user.create', [
6971			'type' => 'user',
6972			'object' => $user,
6973			'userId' => $userId,
6974		]);
6975
6976		return $user;
6977	}
6978
6979	function autogenerate_login($userId, $digits = 6)
6980	{
6981		//create unique hash based on $userId, between 0 and 999999 (if digits = 6)
6982		$userHash = $userId * pow(9, $digits) % (pow(10, $digits));
6983		return sprintf('%0' . $digits . 'd', $userHash); //add leading 0's
6984	}
6985
6986	function set_user_default_preferences($user, $force = true)
6987	{
6988		global $prefs;
6989		foreach ($prefs as $pref => $value) {
6990			if (! preg_match('/^users_prefs_/', $pref)) {
6991				continue;
6992			}
6993			if ($pref == 'users_prefs_email_is_public') {
6994				$pref_name = 'email is public';
6995			} else {
6996				$pref_name = substr($pref, 12);
6997			}
6998			if ($force || is_null($this->get_user_preference($user, $pref_name))) {
6999				$this->set_user_preference($user, $pref_name, $value);
7000			}
7001		}
7002
7003		if ($prefs['change_language'] == 'y' && $prefs['site_language'] != $prefs['language']) {
7004			$this->set_user_preference($user, 'language', $prefs['language']);
7005		}
7006	}
7007
7008	function change_user_email_only($user, $email)
7009	{
7010		global $prefs;
7011		if ($prefs['user_unique_email'] == 'y' && $this->other_user_has_email($user, $email)) {
7012			$smarty = TikiLib::lib('smarty');
7013			$smarty->assign('errortype', 'login');
7014			$smarty->assign('msg', tra('Email cannot be set because this email is already in use by another user.'));
7015			$smarty->display('error.tpl');
7016			die;
7017		}
7018		$query = 'update `users_users` set `email`=? where binary `login`=?';
7019		$result = $this->query($query, [$email, $user]);
7020	}
7021
7022	function change_user_email($user, $email, $pass = null)
7023	{
7024		global $prefs;
7025		if ($prefs['user_unique_email'] == 'y' && $this->other_user_has_email($user, $email)) {
7026			$smarty = TikiLib::lib('smarty');
7027			$smarty->assign('errortype', 'login');
7028			$smarty->assign('msg', tra('Email cannot be set because this email is already in use by another user.'));
7029			$smarty->display('error.tpl');
7030			die;
7031		}
7032
7033		// Need to change the email-address for notifications, too
7034		$notificationlib = TikiLib::lib('notification');
7035		$oldMail = $this->get_user_email($user);
7036		$notificationlib->update_mail_address($user, $oldMail, $email);
7037
7038		$this->change_user_email_only($user, $email);
7039
7040		// that block stays here for a time (compatibility)
7041		// lfagundes - only if pass is provided, admin doesn't need it
7042		// is this still necessary?
7043		if (! empty($pass)) {
7044			$hash = password_hash($pass, PASSWORD_DEFAULT);
7045			$query = 'update `users_users` set `hash`=? where binary `login`=?';
7046			$result = $this->query($query, [$hash, $user]);
7047		}
7048
7049		$query = 'update `tiki_user_watches` set `email`=? where binary `user`=?';
7050		$result = $this->query($query, [ $email, $user]);
7051
7052		$query = 'update `tiki_live_support_requests` set `email`=? where binary `user`=?';
7053		$result = $this->query($query, [ $email, $user]);
7054
7055		TikiLib::events()->trigger('tiki.user.update', ['type' => 'user', 'object' => $user]);
7056
7057		return true;
7058	}
7059
7060	function get_user_email($user)
7061	{
7062		global $prefs;
7063
7064		if (($prefs['login_is_email'] == 'y' && $user != 'admin')) {
7065			return $this->user_exists($user) ? $user : '';
7066		} else {
7067			return $this->getOne('select `email` from `users_users` where binary `login`=?', [$user]);
7068		}
7069	}
7070
7071	function get_userId_what($userIds, $what = 'email')
7072	{
7073		$query = "select `$what` from `users_users` where `userId` in (" . implode(',', array_fill(0, count($userIds), '?')) . ')';
7074		$result = $this->query($query, $userIds);
7075		$ret = [];
7076
7077		while ($res = $result->fetchRow()) {
7078			$ret[] = $res[$what];
7079		}
7080
7081		return $ret;
7082	}
7083
7084	/**
7085	 * Returns the contact users' email if set and permitted by Admin->Features settings
7086	 */
7087	function get_admin_email()
7088	{
7089		global $user, $prefs, $tikilib;
7090		if (( ! isset($user) && isset($prefs['contact_anon']) && $prefs['contact_anon'] == 'y' ) ||
7091				( isset($user) && $user != '' && isset($prefs['feature_contact']) && $prefs['feature_contact'] == 'y' )
7092		) {
7093			return isset($prefs['sender_email']) ? $prefs['sender_email'] : $this->get_user_email($prefs['contact_user']);
7094		}
7095	}
7096
7097	function create_user_cookie($user, $secret = false)
7098	{
7099		global $prefs;
7100		if (! $secret) {
7101			$secret = $this->get_cookie_check();
7102		}
7103		if ($prefs['login_multiple_forbidden'] === 'y') {
7104			$this->delete_user_cookie($user);
7105		}
7106
7107		$query = 'insert into `tiki_user_login_cookies`(`userId`, `secret`, `expiration`) values(?, ?, FROM_UNIXTIME(?))';
7108		$result = $this->query($query, [$user, $secret, $this->now + $prefs['remembertime']]);
7109
7110		return $secret;
7111	}
7112
7113	function delete_user_cookie($user, $secret = '')
7114	{
7115		$query = 'delete from `tiki_user_login_cookies` where `userId`=?';
7116		$vars = [(int) $user];
7117		if ($secret) {
7118			$query .= ' and `secret`=?';
7119			$vars[] = $secret;
7120		}
7121		$this->query($query, $vars);
7122	}
7123
7124	function get_cookie_check()
7125	{
7126		// generate random string but remove fullstops as they are used as the delimiter
7127		return str_replace('.', chr(rand(48, 126)), TikiLib::lib('tiki')->generate_unique_sequence(32));
7128	}
7129
7130	function get_user_by_cookie($cookie)
7131	{
7132		list($secret, $userId) = explode('.', $cookie, 2);
7133		$query = 'select `userId` from `tiki_user_login_cookies` where `secret`=? and `userId`=? and `expiration` > NOW()';
7134
7135		if ($userId === $this->getOne($query, [$secret, $userId])) {
7136			return $userId;
7137		} else {
7138			TikiLib::lib('logs')->add_log('login', 'get_user_by_cookie failed', $userId);
7139			return false;
7140		}
7141	}
7142
7143	function get_user_by_email($email)
7144	{
7145		$query = 'select `login` from `users_users` where upper(`email`)=?';
7146		$pass = $this->getOne($query, [TikiLib::strtoupper($email)]);
7147
7148		return $pass;
7149	}
7150
7151	function other_user_has_email($user, $email)
7152	{
7153		$query = 'select `login` from `users_users` where upper(`email`)=? and `login`!=?';
7154		$pass = $this->getOne($query, [TikiLib::strtoupper($email), $user]);
7155
7156		return $pass;
7157	}
7158
7159	function is_due($user, $method = null)
7160	{
7161		global $prefs;
7162		if (empty($method)) {
7163			$method = $prefs['auth_method'];
7164		}
7165		// if CAS auth is enabled, don't check if password is due since CAS does not use local Tiki passwords
7166		if ($method == 'cas' || $method == 'ldap' || $prefs['change_password'] != 'y') {
7167			return false;
7168		}
7169		$confirm = $this->getOne('select `pass_confirm` from `users_users` where binary `login`=?', [$user]);
7170		if (! $confirm) {
7171			return true;
7172		}
7173		if ($prefs['pass_due'] < 0) {
7174			return false;
7175		}
7176		if ($confirm + (60 * 60 * 24 * $prefs['pass_due']) < $this->now) {
7177			return true;
7178		}
7179
7180		return false;
7181	}
7182
7183	function is_email_due($user)
7184	{
7185		global $prefs;
7186
7187		if ($prefs['email_due'] < 0) {
7188			return false;
7189		}
7190
7191		$confirm = $this->getOne('select `email_confirm` from `users_users` where binary `login`=?', [$user]);
7192
7193		if ($confirm + (60 * 60 * 24 * $prefs['email_due']) < $this->now) {
7194			return true;
7195		}
7196
7197		return false;
7198	}
7199
7200	function unsuccessful_logins($user)
7201	{
7202		return $this->getOne('select `unsuccessful_logins` from `users_users` where binary `login`=?', [$user]);
7203	}
7204
7205	function renew_user_password($user)
7206	{
7207		$pass = $this->generate_provisional_password();
7208		// Note that tiki-generated passwords are due inmediatley
7209		// Note: ^ not anymore. old pw is usable until the URL in the password reminder mail is clicked
7210		$query = 'update `users_users` set `provpass` = ? where `login`=?';
7211		$result = $this->query($query, [$pass, $user]);
7212		return $pass;
7213	}
7214
7215	private function generate_provisional_password()
7216	{
7217		$tikilib = TikiLib::lib('tiki');
7218
7219		$site_hash = $tikilib->get_site_hash();
7220
7221		$random_value = \phpseclib\Crypt\Random::string(40);
7222		return base64_encode(sha1($random_value . $site_hash, true));
7223	}
7224
7225	function activate_password($user, $actpass)
7226	{
7227		// move provpass to password and generate new hash, afterwards clean provpass
7228		$query = 'select `provpass` from `users_users` where `login`=?';
7229		$pass = $this->getOne($query, [$user]);
7230		if (($pass <> '') && ($actpass == md5($pass))) {
7231			$hash = password_hash($pass, PASSWORD_DEFAULT);
7232			$query = 'update `users_users` set `hash`=?, `pass_confirm`=? where `login`=?';
7233			$result = $this->query($query, [$hash, (int)$this->now, $user]);
7234			return $pass;
7235		}
7236		return false;
7237	}
7238
7239   /**
7240	* Tests the password against policy enforcement (Admin->Login), namely
7241	* $min_pass_length
7242	* $pass_chr_num
7243	* $pass_ud_chr_num
7244	*
7245	* returns an empty string if password is ok, or the error string otherwise
7246	*/
7247	function check_password_policy($pass)
7248	{
7249		global $prefs, $user;
7250		$errors = [];
7251
7252		// Validate password here
7253		if (( $prefs['auth_method'] != 'cas' || $user == 'admin' ) && strlen($pass) < $prefs['min_pass_length']) {
7254			$errors[] = tr('Password should be at least %0 characters long', $prefs['min_pass_length']);
7255		}
7256
7257		if ($prefs['pass_chr_case'] == 'y') {
7258			if (! preg_match_all('/[a-z]+/', $pass) || ! preg_match_all('/[A-Z]+/', $pass)) {
7259				$errors[] = tra('Password must contain at least one lowercase alphabetical character like "a" and one uppercase character like "A".');
7260			}
7261		}
7262
7263		if ($prefs['pass_repetition'] == 'y') {
7264			$chars = str_split($pass);
7265			$previous = '';
7266			foreach ($chars as $char) {
7267				if ($char == $previous) {
7268					$errors[] = tra('Password must not contain a consecutive repetition of the same character such as "111" or "aab"');
7269					break;
7270				}
7271				$previous = $char;
7272			}
7273		}
7274
7275		$pass = strtolower($pass); // from here on in, we dont check upper case in the password.
7276
7277		// Check this code
7278		if ($prefs['pass_chr_num'] == 'y') {
7279			if (! preg_match_all('/[0-9]+/', $pass) || ! preg_match_all('/[a-z]+/', $pass)) {
7280				$errors[] = tra('Password must contain both letters and numbers');
7281			}
7282		}
7283
7284
7285		if ($prefs['pass_chr_special'] == 'y') {
7286			if (preg_match_all('/^[0-9a-z]+$/', $pass) > 0) {
7287				$errors[] = tra('Password must contain at least one special character in lower case like " / $ % ? & * ( ) _ + ...');
7288			}
7289		}
7290
7291		if ($prefs['pass_diff_username'] == 'y') {
7292			if (strtolower($user) == $pass) {
7293				$errors[] = tra('The password must be different from the user\'s log-in name.');
7294			}
7295		}
7296
7297		if ($prefs['pass_blacklist'] === 'y') {
7298			$query = 'SELECT 1 FROM tiki_password_blacklist WHERE BINARY password=?;';
7299			$result = $this->query($query, [$pass]);
7300			$isCommon = $result->fetchRow();
7301			if ($isCommon[1] == 1) {
7302				$errors[] = tra('The password is blacklisted because it is too common.');
7303			}
7304		}
7305
7306
7307		return empty($errors) ? '' : implode(' ', $errors);
7308	}
7309
7310	function remove_2_factor_secret($user)
7311	{
7312		return $this->update_2_factor_secret($user, '');
7313	}
7314
7315	function generate_2_factor_secret($user)
7316	{
7317		$google2fa = new Google2FA();
7318		$tfaSecret = $google2fa->generateSecretKey();
7319		return $this->update_2_factor_secret($user, $tfaSecret);
7320	}
7321
7322	function update_2_factor_secret($user, $twoFASecret)
7323	{
7324		$query = 'update `users_users` set `twoFactorSecret`=? where binary `login`=?';
7325		$this->query($query, [$twoFASecret, $user]);
7326		return $twoFASecret;
7327	}
7328
7329	function get_2_factor_secret($user)
7330	{
7331		$query = 'select `twoFactorSecret` from `users_users` where `login`=?';
7332		return $this->getOne($query, [$user]);
7333	}
7334
7335	function validate_two_factor($twoFactorSecret, $pin)
7336	{
7337		$google2fa = new Google2FA();
7338		return $google2fa->verifyKey($twoFactorSecret, $pin, 2);
7339	}
7340
7341	function change_user_password($user, $pass, $pass_first_login = false)
7342	{
7343
7344		$hash = password_hash($pass, PASSWORD_DEFAULT);
7345		$new_pass_confirm = $this->now;
7346
7347		if ($pass_first_login) {					// if true, set pass_confirm to force passord change upon next login
7348			if (! empty($pass)) {
7349				$query = 'update `users_users` set `hash`=? , `provpass`=?, `pass_confirm`=? where binary `login`=?';
7350				$this->query($query, [$hash, $pass, 0, $user]);
7351			} else {
7352				$query = 'update `users_users` set `pass_confirm`=? where binary `login`=?';
7353				$this->query($query, [0, $user]);
7354			}
7355		} else {
7356			$query = 'update `users_users` set `hash`=? ,`pass_confirm`=?, `provpass`=? where binary `login`=?';
7357			$this->query($query, [$hash, $new_pass_confirm, '',	$user]);
7358		}
7359		// invalidate the cache so that after a fresh install, the admin (who has no user details at the install) can log in
7360		$cachelib = TikiLib::lib('cache');
7361		$cachelib->invalidate('user_details_' . $user);
7362
7363		TikiLib::events()->trigger('tiki.user.update', ['type' => 'user', 'object' => $user]);
7364
7365		return true;
7366	}
7367
7368	function add_group(
7369		$group,
7370		$desc = '',
7371		$home = '',
7372		$utracker = 0,
7373		$gtracker = 0,
7374		$rufields = '',
7375		$userChoice = '',
7376		$defcat = 0,
7377		$theme = '',
7378		$ufield = 0,
7379		$gfield = 0,
7380		$isexternal = 'n',
7381		$expireAfter = 0,
7382		$emailPattern = '',
7383		$anniversary = '',
7384		$prorateInterval = '',
7385		$color = '',
7386		$isRole = '',
7387		$isTplGroup = '',
7388		$include_groups = []
7389	) {
7390
7391		$tikilib = TikiLib::lib('tiki');
7392		$group = trim($group);
7393
7394		if ($this->group_exists($group)) {
7395			return false;
7396		}
7397
7398		$data = [
7399			'groupName' => $group,
7400			'groupDesc' => $desc,
7401			'groupHome' => $home,
7402			'groupDefCat' => $defcat,
7403			'groupTheme' => $theme,
7404			'groupColor' => $color,
7405			'usersTrackerId' => (int)$utracker,
7406			'groupTrackerId' => (int)$gtracker,
7407			'registrationUsersFieldIds' => $rufields,
7408			'userChoice' => $userChoice,
7409			'usersFieldId' => (int)$ufield,
7410			'groupFieldId' => (int)$gfield,
7411			'isExternal' => $isexternal,
7412			'expireAfter' => $expireAfter,
7413			'emailPattern' => $emailPattern,
7414			'anniversary' => $anniversary,
7415			'prorateInterval' => $prorateInterval,
7416			'isRole' => $isRole,
7417			'isTplGroup' => empty($isTplGroup) ? 'n' : $isTplGroup,
7418		];
7419
7420		$id = $this->table('users_groups')->insert($data);
7421
7422		$this->manage_group($group, $include_groups);
7423
7424
7425		TikiLib::events()->trigger('tiki.group.create', [
7426			'type' => 'group',
7427			'object' => $group,
7428		]);
7429
7430		$cachelib = TikiLib::lib('cache');
7431		$cachelib->invalidate('grouplist');
7432		$cachelib->invalidate('groupIdlist');
7433
7434		return $id;
7435	}
7436
7437	function change_group(
7438		$olgroup,
7439		$group,
7440		$desc,
7441		$home,
7442		$utracker = 0,
7443		$gtracker = 0,
7444		$ufield = 0,
7445		$gfield = 0,
7446		$rufields = '',
7447		$userChoice = '',
7448		$defcat = 0,
7449		$theme = '',
7450		$isexternal = 'n',
7451		$expireAfter = 0,
7452		$emailPattern = '',
7453		$anniversary = '',
7454		$prorateInterval = '',
7455		$color = '',
7456		$isRole = '',
7457		$isTplGroup = '',
7458		$include_groups = []
7459	) {
7460		$isTplGroup = empty($isTplGroup) ? 'n' : $isTplGroup;
7461		$users = $this->get_group_users($group);
7462		if (! empty($users) && $isRole == "y") {
7463			throw new Exception(tr('Role groups can\'t have users.'));
7464		}
7465
7466		if ($olgroup == 'Anonymous' || $olgroup == 'Registered') {
7467			// Changing group name of 'Anonymous' and 'Registered' is not allowed.
7468			if ($group != $olgroup) {
7469				return false;
7470			}
7471		}
7472
7473		if (! $this->group_exists($olgroup)) {
7474			return $this->add_group(
7475				$group,
7476				$desc,
7477				$home,
7478				$utracker,
7479				$gtracker,
7480				$rufields,
7481				$userChoice,
7482				$defcat,
7483				$theme,
7484				$isexternal,
7485				$expireAfter,
7486				$emailPattern,
7487				$anniversary,
7488				$prorateInterval,
7489				$color,
7490				$isRole,
7491				$isTplGroup
7492			);
7493		}
7494
7495		$cachelib = TikiLib::lib('cache');
7496
7497		$tx = TikiDb::get()->begin();
7498
7499		$data = [
7500			'groupName' => $group,
7501			'groupDesc' => $desc,
7502			'groupHome' => $home,
7503			'groupDefCat' => $defcat,
7504			'groupTheme' => $theme,
7505			'groupColor' => $color,
7506			'usersTrackerId' => (int)$utracker,
7507			'groupTrackerId' => (int)$gtracker,
7508			'registrationUsersFieldIds' => $rufields,
7509			'userChoice' => $userChoice,
7510			'usersFieldId' => (int)$ufield,
7511			'groupFieldId' => (int)$gfield,
7512			'isExternal' => $isexternal,
7513			'expireAfter' => $expireAfter,
7514			'emailPattern' => $emailPattern,
7515			'anniversary' => $anniversary,
7516			'prorateInterval' => $prorateInterval,
7517			'isRole' => $isRole,
7518			'isTplGroup' => $isTplGroup,
7519		];
7520
7521		$this->table('users_groups')->update($data, ['groupName' => $olgroup]);
7522
7523		if ($olgroup != $group) {
7524			$query = [];
7525			$query[] = 'update `users_usergroups` set `groupName`=? where `groupName`=?';
7526			$query[] = 'update `users_grouppermissions` set `groupName`=? where `groupName`=?';
7527			$query[] = 'update `users_objectpermissions` set `groupName`=? where `groupName`=?';
7528			$query[] = 'update `tiki_group_inclusion` set `groupName`=? where `groupName`=?';
7529			$query[] = 'update `tiki_group_inclusion` set `includeGroup`=? where `includeGroup`=?';
7530			$query[] = 'update `tiki_newsletter_groups` set `groupName`=? where `groupName`=?';
7531			$query[] = 'update `tiki_group_watches` set `group`=? where `group`=?';
7532
7533			foreach ($query as $q) {
7534				$this->query($q, [$group, $olgroup]);
7535			}
7536
7537			// must unserialize before replacing the groups
7538			$query = 'select `name`, `groups` from `tiki_modules` where `groups` like ?';
7539			$result = $this->query($query, ['%' . $olgroup . '%']);
7540
7541			while ($res = $result->fetchRow()) {
7542				$aux = [];
7543				$aux['name'] = $res['name'];
7544				$aux['groups'] = unserialize($res['groups']);
7545				$aux['groups'] = str_replace($olgroup, $group, $aux['groups']);
7546				$aux['groups'] = serialize($aux['groups']);
7547				$query = 'update `tiki_modules` set `groups`=? where `name`=?';
7548				$this->query($query, [$aux['groups'], $aux['name']]);
7549			}
7550
7551			$query = 'select * from `tiki_tracker_fields` where `visibleBy` like ?';
7552			$result = $this->query($query, ['%"' . $olgroup . '"%']);
7553			$query = 'update `tiki_tracker_fields` set `visibleBy`=? where `visibleBy`=?';
7554			while ($res = $result->fetchRow()) {
7555				$g = unserialize($res['visibleBy']);
7556				$g = str_replace($olgroup, $group, $g);
7557				$g = serialize($g);
7558				$this->query($query, [$g, $res['visibleBy']]);
7559			}
7560
7561			$query = 'select * from `tiki_tracker_fields` where `editableBy` like ?';
7562			$result = $this->query($query, ['%"' . $olgroup . '"%']);
7563
7564			$query = 'update `tiki_tracker_fields` set `editableBy`=? where `editableBy`=?';
7565			while ($res = $result->fetchRow()) {
7566				$g = unserialize($res['editableBy']);
7567				$g = str_replace($olgroup, $group, $g);
7568				$g = serialize($g);
7569				$this->query($query, [$g, $res['editableBy']]);
7570			}
7571
7572			$query = 'update `tiki_tracker_item_fields` ttif' .
7573								' left join `tiki_tracker_fields` ttf on (ttf.`fieldId`=ttif.`fieldId`)' .
7574								' set ttif.`value`=? where ttif.`value`=? and ttf.`type`=?';
7575
7576			$this->query($query, [$group, $olgroup, 'g']);
7577
7578			$cachelib->invalidate('grouplist');
7579			$cachelib->invalidate('group_theme_' . $group);
7580
7581			TikiLib::events()->trigger('tiki.group.delete', [
7582				'type' => 'group',
7583				'object' => $olgroup,
7584			]);
7585		}
7586
7587
7588		$this->manage_group($group, $include_groups);
7589
7590		$cachelib->invalidate('group_theme_' . $olgroup);
7591
7592		TikiLib::events()->trigger('tiki.group.update', [
7593			'type' => 'group',
7594			'object' => $group,
7595		]);
7596
7597		$tx->commit();
7598
7599		return true;
7600	}
7601
7602	function edit_group($id, $name, $description)
7603	{
7604		// Limited editing only, for users with the tiki_p_edit_grouplimitedinfo perm
7605		$groupInfo = $this->get_groupId_info($id);
7606		if (!$groupInfo)
7607			return false;
7608		$includeGroups = $this->get_included_groups($groupInfo["groupName"]);
7609
7610		$this->change_group($groupInfo["groupName"], $name, $description
7611			, $groupInfo["groupHome"], $groupInfo["usersTrackerId"], $groupInfo["groupTrackerId"]
7612			, $groupInfo["groupTrackerId"], $groupInfo["groupFieldId"], $groupInfo["registrationUsersFieldIds"]
7613			, $groupInfo["userChoice"], $groupInfo["groupDefCat"], $groupInfo["groupTheme"]
7614			, $groupInfo["isExternal"], $groupInfo["expireAfter"], $groupInfo["emailPattern"]
7615			, $groupInfo["anniversary"], $groupInfo["prorateInterval"], $groupInfo["groupColor"]
7616			, $groupInfo["isRole"], $groupInfo["isTplGroup"], $includeGroups);
7617		return true;
7618	}
7619
7620	function remove_all_inclusions($group)
7621	{
7622		if (! $this->group_exists($group)) {
7623			return false;
7624		}
7625
7626		$query = 'delete from `tiki_group_inclusion` where `groupName` = ?';
7627		$result = $this->query($query, [$group]);
7628		$cachelib = TikiLib::lib('cache');
7629		$cachelib->empty_type_cache('group_inclusion_' . $group);
7630		$this->groupinclude_cache = [];
7631
7632		return true;
7633	}
7634
7635	function set_user_fields($u)
7636	{
7637		global $prefs;
7638
7639		$q = [];
7640		$bindvars = [];
7641
7642		if (isset($u['email'])) {
7643			if ($prefs['user_unique_email'] == 'y' && $this->other_user_has_email($u['login'], $u['email'])) {
7644				$smarty = TikiLib::lib('smarty');
7645				$smarty->assign('errortype', 'login');
7646				$smarty->assign('msg', tra('Email cannot be set because this email is already in use by another user.'));
7647				$smarty->display('error.tpl');
7648				die;
7649			}
7650			$q[] = '`email` = ?';
7651			$bindvars[] = strip_tags($u['email']);
7652		}
7653
7654		if (isset($u['openid_url'])) {
7655			if (isset($_SESSION['openid_url'])) {
7656				$q[] = '`openid_url` = ?';
7657				$bindvars[] = $u['openid_url'];
7658			}
7659		}
7660
7661		if (count($q) > 0) {
7662			$query = 'update `users_users` set ' . implode(',', $q) . ' where binary `login` = ?';
7663			$bindvars[] = $u['login'];
7664			$result = $this->query($query, $bindvars);
7665		}
7666
7667		$aUserPrefs = ['realName', 'homePage', 'country'];
7668		foreach ($aUserPrefs as $pref) {
7669			if (isset($u[$pref])) {
7670				$this->set_user_preference($u['login'], $pref, $u[$pref]);
7671			}
7672		}
7673
7674		return $result;
7675	}
7676
7677	function count_users($group)
7678	{
7679		static $rv = [];
7680
7681		if (! isset($rv[$group])) {
7682			if ($group == '') {
7683				$query = 'select count(login) from `users_users`';
7684				$result = $this->getOne($query);
7685			} else {
7686				$query = 'select count(userId) from `users_usergroups` where `groupName` = ?';
7687				$result = $this->getOne($query, [$group]);
7688			}
7689			$rv[$group] = $result;
7690		}
7691
7692		return $rv[$group];
7693	}
7694
7695	function count_users_consolidated($groups)
7696	{
7697		$groupset = implode("','", $groups);
7698		$query = "select userId from `users_usergroups` where `groupName` in ('" . $groupset . "')";
7699		$result = $this->fetchAll($query, []);
7700		$resultcons = array_unique(array_column($result, 'userId'));
7701		return count($resultcons);
7702	}
7703
7704	function related_users($user, $max = 10, $type = 'wiki')
7705	{
7706		if (! isset($user) || empty($user)) {
7707			return [];
7708		}
7709
7710		// This query was written using a double join for PHP. If you're trying to eke
7711		// additional performance and are running MySQL 4.X, you might want to try a
7712		// subselect and compare perf numbers.
7713
7714		if ($type == 'wiki') {
7715			$query = 'SELECT u1.`login`, COUNT( p1.`pageName` ) AS quantity
7716				FROM `tiki_history` p1
7717				INNER JOIN `users_users` u1 ON ( u1.`login` = p1.`user` )
7718				INNER JOIN `tiki_history` p2 ON ( p1.`pageName` = p2.`pageName` )
7719				INNER JOIN `users_users` u2 ON ( u2.`login` = p2.`user` )
7720				WHERE u2.`login` = ? AND u1.`login` <> ?
7721				GROUP BY p1.`pageName`, u1.`login`
7722				ORDER BY quantity DESC
7723				';
7724		} else {
7725			return [];
7726		}
7727
7728		$bindvals = [$user, $user];
7729
7730		return $this->fetchAll($query, $bindvals, $max, 0);
7731	}
7732
7733	// Case-sensitivity regression only. used for patching
7734	function get_object_case_permissions($objectId, $objectType)
7735	{
7736		$query = 'select `groupName`, `permName` from `users_objectpermissions` where `objectId` = ? and `objectType` = ?';
7737		return $this->fetchAll($query, [md5($objectType . $objectId),$objectType]);
7738	}
7739
7740	function object_has_one_case_permission($objectId, $objectType)
7741	{
7742		$query = 'select count(*) from `users_objectpermissions` where `objectId`=? and `objectType`=?';
7743		$result = $this->getOne($query, [ md5($objectType . $objectId), $objectType]);
7744		return $result;
7745	}
7746
7747	function remove_object_case_permission($groupName, $objectId, $objectType, $permName)
7748	{
7749		$query = 'delete from `users_objectpermissions`' .
7750							' where `groupName` = ? and `objectId` = ? and `objectType` = ? and `permName` = ?';
7751		$result = $this->query($query, [$groupName, md5($objectType . $objectId), $objectType, $permName]);
7752
7753		return true;
7754	}
7755
7756	function send_validation_email(
7757		$name,
7758		$apass,
7759		$email,
7760		$again = '',
7761		$second = '',
7762		$chosenGroup = '',
7763		$mailTemplate = '',
7764		$pass = ''
7765	) {
7766
7767		global $prefs;
7768		$tikilib = TikiLib::lib('tiki');
7769		$smarty = TikiLib::lib('smarty');
7770
7771		// mail_machine kept for BC, use $validation_url
7772		$machine = TikiLib::tikiUrl('tiki-login_validate.php');
7773		$machine_assignuser = TikiLib::tikiUrl('tiki-assignuser.php');
7774		$machine_userprefs = TikiLib::tikiUrl('tiki-user_preferences.php');
7775		$smarty->assign('mail_machine', $machine);
7776		$smarty->assign('mail_machine_assignuser', $machine_assignuser);
7777		$smarty->assign('mail_machine_userprefs', $machine_userprefs);
7778		$smarty->assign('mail_site', $_SERVER['SERVER_NAME']);
7779		$smarty->assign('mail_user', $name);
7780		$smarty->assign('mail_apass', $apass);
7781		$smarty->assign('mail_email', $email);
7782		$smarty->assign('mail_again', $again);
7783		$smarty->assign(
7784			'validation_url',
7785			TikiLib::tikiUrl(
7786				'tiki-login_validate.php',
7787				[
7788					'user' => $name,
7789					'pass' => $apass,
7790				]
7791			)
7792		);
7793		$smarty->assign(
7794			'assignuser_url',
7795			TikiLib::tikiUrl(
7796				'tiki-assignuser.php',
7797				['assign_user' => $name]
7798			)
7799		);
7800		$smarty->assign(
7801			'userpref_url',
7802			TikiLib::tikiUrl(
7803				'tiki-user_preferences.php',
7804				['view_user' => $name]
7805			)
7806		);
7807
7808		include_once('lib/webmail/tikimaillib.php');
7809
7810		if ($second == 'y') {
7811			$mail_data = $smarty->fetch('mail/confirm_user_email_after_approval.tpl');
7812			$mail = new TikiMail();
7813			$mail->setText($mail_data);
7814			$mail_data = sprintf($smarty->fetch('mail/confirm_user_email_after_approval_subject.tpl'), $_SERVER['SERVER_NAME']);
7815			$mail->setSubject($mail_data);
7816			if (! $mail->send([$email])) {
7817				$smarty->assign('msg', tra("The registration mail can't be sent. Contact the administrator"));
7818				return false;
7819			}
7820		} elseif ($prefs['validateRegistration'] == 'y' && empty($pass) && $mailTemplate != 'user_creation_validation_mail') {
7821			if (! empty($chosenGroup)) {
7822				$smarty->assign_by_ref('chosenGroup', $chosenGroup);
7823				if ($prefs['userTracker'] == 'y') {
7824					$trklib = TikiLib::lib('trk');
7825					$re = $this->get_group_info(isset($chosenGroup) ? $chosenGroup : 'Registered');
7826					$fields = $trklib->list_tracker_fields(
7827						$re['usersTrackerId'],
7828						0,
7829						-1,
7830						'position_asc',
7831						'',
7832						true,
7833						['fieldId' => explode(':', $re['registrationUsersFieldIds'])]
7834					);
7835
7836					$listfields = [];
7837
7838					foreach ($fields['data'] as $field) {
7839						$listfields[$field['fieldId']] = $field;
7840					}
7841
7842					$definition = Tracker_Definition::get($re['usersTrackerId']);
7843					if ($definition) {
7844						$items = $trklib->list_items(
7845							$re['usersTrackerId'],
7846							0,
7847							1,
7848							'',
7849							$listfields,
7850							$definition->getUserField(),
7851							'',
7852							'',
7853							'',
7854							$name,
7855							'',
7856							null,
7857							true,
7858							true
7859						);
7860
7861						if (isset($items['data'][0])) {
7862							$smarty->assign_by_ref('item', $items['data'][0]);
7863						}
7864					} else {
7865						Feedback::error(tr('No user tracker found with id #%0', $re['usersTrackerId']));
7866					}
7867				}
7868			}
7869			$mail_data = $smarty->fetch('mail/moderate_validation_mail.tpl');
7870			$mail_subject = $smarty->fetch('mail/moderate_validation_mail_subject.tpl');
7871
7872			$emails = ! empty($prefs['validator_emails'])
7873								? preg_split('/,/', $prefs['validator_emails'])
7874								: (! empty($prefs['sender_email']) ? [$prefs['sender_email']] : '');
7875
7876			if (empty($emails)) {
7877				if ($prefs['feature_messages'] != 'y') {
7878					$smarty->assign(
7879						'msg',
7880						tra("The registration mail can't be sent because there is no server email address set, and this feature is disabled") .
7881						": feature_messages"
7882					);
7883					return false;
7884				}
7885
7886				TikiLib::lib('message')->post_message(
7887					$prefs['contact_user'],
7888					$prefs['contact_user'],
7889					$prefs['contact_user'],
7890					'',
7891					$mail_subject,
7892					$mail_data,
7893					5
7894				);
7895				$smarty->assign('msg', $smarty->fetch('mail/user_validation_waiting_msg.tpl'));
7896			} else {
7897				$mail = new TikiMail();
7898				$mail->setText($mail_data);
7899				$mail->setSubject($mail_subject);
7900				if (! $mail->send($emails)) {
7901					$smarty->assign('msg', tra("The registration mail can't be sent. Contact the administrator"));
7902					return false;
7903				} elseif (empty($again)) {
7904					$smarty->assign('msg', $smarty->fetch('mail/user_validation_waiting_msg.tpl'));
7905				} else {
7906					$smarty->assign('msg', tra('The administrator has not yet validated your account. Please wait.'));
7907				}
7908			}
7909		} elseif ($prefs['validateUsers'] == 'y' || ! empty($pass) || $mailTemplate == 'user_creation_validation_mail') {
7910			if ($mailTemplate == '') {
7911				$mailTemplate = 'user_validation_mail';
7912			}
7913
7914			$smarty->assign(
7915				'validation_url',
7916				TikiLib::tikiUrl(
7917					'tiki-login_validate.php',
7918					[
7919						'user' => $name,
7920						'pass' => $apass,
7921					]
7922				)
7923			);
7924
7925			$mail_data = $smarty->fetch("mail/$mailTemplate.tpl");
7926			$mail = new TikiMail();
7927			$mail->setText($mail_data);
7928			$mail_data = $smarty->fetch("mail/{$mailTemplate}_subject.tpl");
7929			$mail->setSubject($mail_data);
7930			if (! $mail->send([$email])) {
7931				$smarty->assign('msg', tra("The registration mail can't be sent. Contact the administrator"));
7932				return false;
7933			} elseif (empty($again)) {
7934				$smarty->assign('msg', $smarty->fetch('mail/user_validation_msg.tpl'));
7935			} else {
7936				$smarty->assign('msg', tra('You must validate your account first. An email has been sent to you'));
7937			}
7938		}
7939		return true;
7940	}
7941
7942	function set_registrationChoice($groups, $flag)
7943	{
7944		$bindvars = [];
7945		$bindvars[] = $flag;
7946		if (is_array($groups)) {
7947			$mid = implode(',', array_fill(0, count($groups), '?'));
7948			$bindvars = array_merge($bindvars, $groups);
7949		} else {
7950			$bindvars[] = $groups;
7951			$mid = 'like ?';
7952		}
7953		$query = "update `users_groups` set `registrationChoice`= ? where `groupName` in ($mid)";
7954		$result = $this->query($query, $bindvars);
7955	}
7956
7957	function get_registrationChoice($group)
7958	{
7959		$query = 'select `registrationChoice` from `users_groups` where `groupName` = ?';
7960		return ($this->getOne($query, [$group]));
7961	}
7962
7963	function reset_email_due($user)
7964	{
7965		$query = 'update `users_users` set `email_confirm`=?, `waiting`=? where `login`=?';
7966		$result = $this->query($query, [0, 'u', $user]);
7967		TikiLib::events()->trigger('tiki.user.update', ['type' => 'user', 'object' => $user]);
7968		return $result;
7969	}
7970
7971	function confirm_email($user, $pass)
7972	{
7973		$tikilib = TikiLib::lib('tiki');
7974		$query = 'select `provpass`, `login`, `unsuccessful_logins` from `users_users` where `login`=?';
7975		$result = $this->query($query, [$user]);
7976		if (! ($res = $result->fetchRow())) {
7977			return false;
7978		}
7979
7980		if (md5($res['provpass']) == $pass) {
7981			$this->confirm_user($user);
7982
7983			$query = 'update `users_users`' .
7984							' set `provpass`=?, `email_confirm`=?, `unsuccessful_logins`=?, `registrationDate`=?' .
7985							' where `login`=? and `provpass`=?';
7986
7987			$this->query($query, ['', $tikilib->now, 0, $this->now, $user, $res['provpass']]);
7988			if (! empty($GLOBALS['user'])) {
7989				$logslib = TikiLib::lib('logs');
7990				$logslib->add_log('login', 'confirm email ' . $user);
7991			}
7992			TikiLib::lib('user')->set_unsuccessful_logins($_REQUEST['user'], 0);
7993			return true;
7994		}
7995
7996		return false;
7997	}
7998
7999	function set_unsuccessful_logins($user, $nb)
8000	{
8001		 $query = 'update `users_users` set `unsuccessful_logins`=? where `login` = ?';
8002		$this->query($query, [$nb, $user]);
8003	}
8004
8005	function send_confirm_email($user, $tpl = 'confirm_user_email')
8006	{
8007		global $prefs;
8008		$tikilib = TikiLib::lib('tiki');
8009		$smarty = TikiLib::lib('smarty');
8010
8011		include_once('lib/webmail/tikimaillib.php');
8012		$languageEmail = $this->get_user_preference($_REQUEST['username'], 'language', $prefs['site_language']);
8013		$apass = $this->renew_user_password($user);
8014		$apass = md5($apass);
8015		$smarty->assign('mail_apass', $apass);
8016		$smarty->assign('mail_ip', $tikilib->get_ip_address());
8017		$smarty->assign('user', $user);
8018		$mail = new TikiMail();
8019		$mail_data = $smarty->fetchLang($languageEmail, "mail/$tpl" . '_subject.tpl');
8020		$mail_data = sprintf($mail_data, $_SERVER['SERVER_NAME']);
8021		$mail->setSubject($mail_data);
8022		$foo = parse_url($_SERVER['REQUEST_URI']);
8023		$mail_machine = TikiLib::tikiUrl('tiki-confirm_user_email.php'); // for BC
8024		$smarty->assign('mail_machine', $mail_machine);
8025		$mail_data = $smarty->fetchLang($languageEmail, "mail/$tpl.tpl");
8026		$mail->setText($mail_data);
8027
8028		if (! ($email = $this->get_user_email($user)) || ! $mail->send([$email])) {
8029			$smarty->assign('msg', tra("The user email confirmation can't be sent. Contact the administrator"));
8030			return false;
8031		} else {
8032			$smarty->assign('msg', 'It is time to confirm your email. You will receive an mail with the instruction to follow');
8033			return true;
8034		}
8035	}
8036
8037	function assign_openid($username, $openid)
8038	{
8039		// This won't update the database unless the openid is different
8040		$this->query(
8041			"UPDATE `users_users` SET openid_url = ? WHERE login = ? AND ( openid_url <> ? OR openid_url IS NULL )",
8042			[$openid, $username, $openid]
8043		);
8044	}
8045
8046	function intervalidate($remote, $user, $pass, $get_info = false)
8047	{
8048		global $prefs;
8049		$hashkey = $this->get_cookie_check() . '.' . ($this->now + $prefs['remembertime']);
8050		$remote['path'] = preg_replace('/^\/?/', '/', $remote['path']);
8051		$client = new XML_RPC_Client($remote['path'], $remote['host'], $remote['port']);
8052		$client->setDebug(0);
8053
8054		$msg = new XML_RPC_Message(
8055			'intertiki.validate',
8056			[
8057				new XML_RPC_Value($prefs['tiki_key'], 'string'),
8058				new XML_RPC_Value($user, 'string'),
8059				new XML_RPC_Value($pass, 'string'),
8060				new XML_RPC_Value($get_info, 'boolean'),
8061				new XML_RPC_Value($hashkey, 'string')
8062			]
8063		);
8064		$result = $client->send($msg);
8065
8066		return $result;
8067	}
8068
8069	/* send request + interpret email/login */
8070	function interGetUserInfo($remote, $user, $email)
8071	{
8072		global $prefs;
8073		$remote['path'] = preg_replace('/^\/?/', '/', $remote['path']);
8074		$client = new XML_RPC_Client($remote['path'], $remote['host'], $remote['port']);
8075		$client->setDebug(0);
8076		$params = [];
8077		$params[] = new XML_RPC_Value($prefs['tiki_key'], 'string');
8078		$params[] = new XML_RPC_Value($user, 'string');
8079		$params[] = new XML_RPC_Value($email, 'string');
8080		$msg = new XML_RPC_Message('intertiki.getUserInfo', $params);
8081		$rpcauth = $client->send($msg);
8082
8083		if (! $rpcauth || $rpcauth->faultCode()) {
8084			return false;
8085		}
8086
8087		$response_value = $rpcauth->value();
8088
8089		for (;;) {
8090			list($key, $value) = $response_value->structeach();
8091			if ($key == '') {
8092				break;
8093			} elseif ($key == 'login') {
8094				$u['login'] = $value->scalarval();
8095			} elseif ($key == 'email') {
8096				$u['email'] = $value->scalarval();
8097			}
8098		}
8099
8100		return $u;
8101	}
8102
8103	/* send via XML_RPC user info to the main */
8104	function interSendUserInfo($remote, $user)
8105	{
8106		global $prefs;
8107		$userlib = TikiLib::lib('user');
8108		$remote['path'] = preg_replace('/^\/?/', '/', $remote['path']);
8109		$client = new XML_RPC_Client($remote['path'], $remote['host'], $remote['port']);
8110		$client->setDebug(0);
8111		$params = [];
8112		$params[] = new XML_RPC_Value($prefs['tiki_key'], 'string');
8113		$params[] = new XML_RPC_Value($user, 'string');
8114		$user_details = $userlib->get_user_details($user);
8115		$user_info = $userlib->get_user_info($user);
8116		$ret['avatarData'] = new XML_RPC_Value($user_info['avatarData'], 'base64');
8117		$ret['user_details'] = new XML_RPC_Value(serialize($user_details), 'string');
8118		$params[] = new XML_RPC_Value($ret, 'struct');
8119		$msg = new XML_RPC_Message('intertiki.setUserInfo', $params);
8120		$result = $client->send($msg);
8121
8122		return $result;
8123	}
8124
8125	/* interpret the XML_RPC answer about user info */
8126	function interSetUserInfo($user, $response_value)
8127	{
8128		$userlib = TikiLib::lib('user');
8129		$tikilib = TikiLib::lib('tiki');
8130
8131		if ($response_value->kindOf() == 'struct') {
8132			for (;;) {
8133				list($key, $value) = $response_value->structeach();
8134				if ($key == '') {
8135					break;
8136				} elseif ($key == 'user_details') {
8137					$user_details = unserialize($value->scalarval());
8138				} elseif ($key == 'avatarData') {
8139					$avatarData = $value->scalarval();
8140				}
8141			}
8142		} else {
8143			$user_details = unserialize($response_value->scalarval());
8144		}
8145
8146		$userlib->set_user_fields($user_details['info']);
8147		$tikilib->set_user_preferences($user, $user_details['preferences']);
8148
8149		if (! empty($avatarData)) {
8150			$userprefslib = TikiLib::lib('userprefs');
8151			$userprefslib->set_user_avatar(
8152				$user,
8153				'u',
8154				'',
8155				$user_details['info']['avatarName'],
8156				$user_details['info']['avatarSize'],
8157				$user_details['info']['avatarFileType'],
8158				$avatarData,
8159				false
8160			);
8161		}
8162	}
8163
8164	function get_remote_user_by_cookie($hash)
8165	{
8166		global $prefs;
8167
8168		$remote = $prefs['interlist'][$prefs['feature_intertiki_mymaster']];
8169		$client = new XML_RPC_Client($remote['path'], $remote['host'], $remote['port']);
8170		$client->setDebug(0);
8171
8172		$msg = new XML_RPC_Message(
8173			'intertiki.cookiecheck',
8174			[
8175				new XML_RPC_Value($prefs['tiki_key'], 'string'),
8176				new XML_RPC_Value($hash, 'string')
8177			]
8178		);
8179		$er = error_reporting(); // suppress PHP 7.2 warnings from xmlrpc lib
8180		error_reporting(E_ALL ^ (E_NOTICE | E_WARNING | E_DEPRECATED));
8181		$result = $client->send($msg);
8182		error_reporting($er);
8183
8184		return $result;
8185	}
8186
8187	function update_expired_groups()
8188	{
8189		$tikilib = TikiLib::lib('tiki');
8190		$this->update_anniversary_expiry();
8191		$query = 'SELECT uu.* FROM `users_usergroups` uu' .
8192						' LEFT JOIN `users_groups` ug ON (uu.`groupName`= ug.`groupName`)' .
8193						' WHERE ( ug.`expireAfter` > ? AND uu.`created` IS NOT NULL AND uu.`expire` is NULL AND uu.`created` + ug.`expireAfter`*24*60*60 < ?)' .
8194						' OR ((ug.`expireAfter` IS NOT NULL OR ug.`anniversary` > ?) AND uu.`expire` < ?)';
8195
8196		$result = $this->query($query, [0, $tikilib->now, 0, $tikilib->now]);
8197
8198		while ($res = $result->fetchRow()) {
8199			$this->remove_user_from_group($this->get_user_login($res['userId']), $res['groupName']);
8200		}
8201	}
8202
8203	function update_anniversary_expiry()
8204	{
8205		$query = 'SELECT uu.* FROM `users_usergroups` uu' .
8206						' LEFT JOIN `users_groups` ug ON (uu.`groupName`= ug.`groupName`)' .
8207						' WHERE ( ug.`anniversary` > ? AND uu.`created` IS NOT NULL AND uu.`expire` is NULL )';
8208
8209		$result = $this->query($query, ['']);
8210
8211		$query = 'UPDATE `users_usergroups` SET `expire` = ? WHERE `groupName`=? AND `userId`=?';
8212
8213		while ($res = $result->fetchRow()) {
8214			$extend_until_info = $this->get_extend_until_info($res['login'], $res['groupName']);
8215			$this->query($query, [$extend_until_info['timestamp'], $res['groupName'], $res['userId']]);
8216		}
8217	}
8218
8219	function update_group_expiries()
8220	{
8221		$query = 'SELECT uu.* FROM `users_usergroups` uu' .
8222			' LEFT JOIN `users_groups` ug ON (uu.`groupName`= ug.`groupName`)' .
8223			' WHERE ( uu.`created` IS NOT NULL AND uu.`expire` is NULL )' .
8224			' AND (ug.`anniversary` > ? OR ug.`expireAfter` > ?)';
8225
8226		$result = $this->query($query, ['', 0]);
8227
8228		$query = 'UPDATE `users_usergroups` SET `expire` = ? WHERE `groupName`=? AND `userId`=?';
8229
8230		while ($res = $result->fetchRow()) {
8231			$uinfo = $this->get_userid_info($res['userId']);
8232			$extend_until_info = $this->get_extend_until_info($uinfo['login'], $res['groupName']);
8233			$this->query($query, [$extend_until_info['timestamp'], $res['groupName'], $res['userId']]);
8234		}
8235	}
8236
8237
8238	function extend_membership($user, $group, $periods = 1, $date = null)
8239	{
8240		$tikilib = TikiLib::lib('tiki');
8241		$this->update_expired_groups();
8242
8243		if (! $this->user_is_in_group($user, $group)) {
8244			$this->assign_user_to_group($user, $group);
8245			if ($periods > 1) {
8246				$periods--;
8247			} elseif (empty($date)) {
8248				return;
8249			}
8250		}
8251
8252		$info = $this->get_group_info($group);
8253		$userInfo = $this->get_user_info($user);
8254		if (empty($date)) {
8255			$extend_until_info = $this->get_extend_until_info($user, $group, $periods);
8256		} else {
8257			$extend_until_info['timestamp'] = $date;
8258		}
8259
8260		$this->query(
8261			'UPDATE `users_usergroups` SET `expire` = ? WHERE `userId` = ? AND `groupName` = ?',
8262			[$extend_until_info['timestamp'], $userInfo['userId'], $group]
8263		);
8264	}
8265
8266	function get_extend_until_info($user, $group, $periods = 1)
8267	{
8268		//use these functions to get current expiry dates for existing members - they are calculated in some cases
8269		//so just grabbing the "expire" field from the users_usergroups table doesn't always work
8270		$userInfo = $this->get_user_info($user);
8271		$usergroupdates = $this->get_user_groups_date($userInfo['userId']);
8272
8273		$info = $this->get_group_info($group);
8274		//set the start date as now for new memberships and as expiry of current membership for existing members
8275		if (array_key_exists($group, $usergroupdates)) {
8276			if (! empty($usergroupdates[$group]['expire'])) {
8277				$date = $usergroupdates[$group]['expire'];
8278			} elseif ($info['expireAfter'] > 0) {
8279				$date = $usergroupdates[$group]['created'];
8280			}
8281		}
8282		if (! isset($date) || ! $date) {
8283			$date = $this->now;
8284			//this is a new membership
8285			$new = true;
8286		} else {
8287			$new = false;
8288		}
8289		//convert start date to object
8290		$rawstartutc = new DateTimeImmutable('@' . $date);
8291		global $prefs;
8292		$tz = TikiDate::TimezoneIsValidId($prefs['server_timezone']) ? $prefs['server_timezone'] : 'UTC';
8293		$timezone = new DateTimeZone($tz);
8294		$startlocal = $rawstartutc->setTimezone($timezone);
8295
8296		//anniversary memberships
8297		if (! empty($info['anniversary'])) {
8298			//set time to 1 second after midnight so that all times are set to same times for interval calculations
8299			$startlocal = $startlocal->setTime(0, 0, 1);
8300			// annual anniversaries
8301			if (strlen($info['anniversary']) == 4) {
8302				$ann_month = substr($info['anniversary'], 0, 2);
8303				$ann_day = substr($info['anniversary'], 2, 2);
8304				$startyear = $startlocal->format('Y');
8305				//increment the year if past the annual anniversary
8306				if ($startlocal->format('m') > $ann_month || ($startlocal->format('m') == $ann_month
8307						&& $startlocal->format('d') >= $ann_day)) {
8308					$startyear++;
8309				}
8310				//first extension is always to next anniversary
8311				$next_ann = $startlocal->setDate($startyear, $ann_month, $ann_day);
8312				//extend past next anniversary if more than one period
8313				$extendto = $next_ann->modify('+' . $periods - 1 . ' years');
8314				//previous anniversary for proration
8315				$prev_ann = $next_ann->modify('-1 years');
8316				// monthly anniversaries
8317				//using modify('+1 month') can result in "skipping" months so fix the day of the previous/next month
8318			} elseif (strlen($info['anniversary']) == 2) {
8319				$ann_day = $info['anniversary'];
8320				$lastday = date('d', strtotime('last day of ' . $startlocal->format('Y') . '-'
8321					. $startlocal->format('m')));
8322				$mod_ann_day = $ann_day > $lastday ? $lastday : $ann_day;
8323				if ($startlocal->format('d') < $mod_ann_day) {
8324					$mod = $mod_ann_day - $startlocal->format('d');
8325					$next_ann = $startlocal->modify('+' . $mod . ' days');
8326					$prev_mo_lastday = $startlocal->modify('last day of last month');
8327					if ($ann_day >= $prev_mo_lastday->format('d')) {
8328						$prev_ann = $prev_mo_lastday;
8329					} else {
8330						$prev_ann = $startlocal->setDate(
8331							$prev_mo_lastday->format('Y'),
8332							$prev_mo_lastday->format('m'),
8333							$mod_ann_day
8334						);
8335					}
8336				} else {
8337					//check if last day of month
8338					$next_mo_lastday = $startlocal->modify('last day of next month');
8339					if ($mod_ann_day >= $next_mo_lastday->format('d')) {
8340						$next_ann = $next_mo_lastday;
8341					} else {
8342						$next_ann = $startlocal->setDate(
8343							$next_mo_lastday->format('Y'),
8344							$next_mo_lastday->format('m'),
8345							$mod_ann_day
8346						);
8347					}
8348					$mod = $startlocal->format('d') - $mod_ann_day;
8349					$prev_ann = $startlocal->modify('-' . $mod . ' days');
8350				}
8351				if ($periods - 1 > 0) {
8352					$yrsplus = floor(($periods - 1) / 12);
8353					$yr = $next_ann->format('Y') + $yrsplus;
8354					$moplus = ($periods - 1) - ($yrsplus * 12);
8355					if ($moplus + $next_ann->format('m') < 12) {
8356						$mo = $moplus + $next_ann->format('m');
8357					} else {
8358						$yr++;
8359						$mo = $moplus + $next_ann->format('m') - 12;
8360					}
8361					if ($ann_day >= date('d', strtotime('last day of ' . $yr . '-' . $mo))) {
8362						$d = date('d', strtotime('last day of ' . $yr . '-' . $mo));
8363					} else {
8364						$d = $ann_day;
8365					}
8366					$extendto = $next_ann->setDate($yr, $mo, $d);
8367				} else {
8368					$extendto = $next_ann;
8369				}
8370			}
8371			//calculate interval of membership term
8372			$interval = $startlocal->diff($extendto);
8373			//set prorate interval
8374			$prorateInterval = in_array($info['prorateInterval'], ['year', 'month', 'day']) ? $info['prorateInterval']
8375				: 'day';
8376			//prorate
8377			if ($prorateInterval == 'year' && strlen($info['anniversary']) == 4) {
8378				$ratio = $interval->y;
8379				$ratio += $interval->m > 0 || $interval->d > 0 ? 1 : 0;
8380			} elseif ($prorateInterval == 'month'
8381				|| ($prorateInterval == 'year' && strlen($info['anniversary']) == 2)) {
8382				$round = $interval->d > 0 ? 1 : 0;
8383				$ratio = (($interval->y * 12) + $interval->m + $round);
8384				if (strlen($info['anniversary']) == 4) {
8385					$ratio = $ratio / 12;
8386				}
8387			} elseif ($prorateInterval == 'day') {
8388				$ann_interval = $prev_ann->diff($next_ann);
8389				$stub_interval = $startlocal->diff($next_ann);
8390				$ratio = ($stub_interval->days / $ann_interval->days) + ($periods - 1);
8391			}
8392			$remainder = $ratio > 1 ? $ratio - floor($ratio) : $ratio;
8393		//memberships based on number of days
8394		} else {
8395			$remainder = 1;
8396			$ratio = 1;
8397			$extendto = $startlocal->modify('+' . $info['expireAfter'] * $periods . ' days');
8398			$interval = $startlocal->diff($extendto);
8399		}
8400		$timestamp = $extendto != null ? $extendto->format('U') : null;
8401
8402		return [
8403			'timestamp' => $timestamp,
8404			'ratio_prorated_first_period' => $remainder,
8405			'ratio' => $ratio,
8406			'interval' => $interval,
8407			'new' => $new];
8408	}
8409
8410	function get_users_created_group($group, $user = null, $with_expire = false)
8411	{
8412		if (! empty($user)) {
8413			$query = 'SELECT uug.`created`,uug.`expire` FROM `users_usergroups` uug' .
8414								' LEFT JOIN `users_users` on (`users_users`.`userId`=uug.`userId`)' .
8415								' WHERE `groupName`=? AND `login`=?';
8416
8417			$bindvars = [$group, $user];
8418		} else {
8419			$query = 'SELECT `login`, uug.`created`,uug.`expire` FROM `users_usergroups` uug' .
8420								' LEFT JOIN `users_users` on (`users_users`.`userId`=uug.`userId`)' .
8421								' WHERE `groupName`=?';
8422
8423			$bindvars = [$group];
8424		}
8425		$result = $this->query($query, $bindvars);
8426		$ret = [];
8427
8428		while ($res = $result->fetchRow()) {
8429			if ($with_expire) {
8430				$ret[$res['login']]['created'] = $res['created'];
8431				if (empty($res['expire'])) {
8432					$re = $this->get_group_info($group);
8433				}
8434				if ($re['expireAfter'] > 0) {
8435					$res['expire'] = $res['created'] + ($re['expireAfter'] * 24 * 60 * 60);
8436				}
8437				$ret[$res['login']]['expire'] = $res['expire'];
8438			} else {
8439				$ret[$res['login']] = $res['created'];
8440			}
8441		}
8442
8443		return $ret;
8444	}
8445
8446	function nb_users_in_group($group = null)
8447	{
8448		if (! empty($group)) {
8449			$query = 'SELECT count(*) FROM `users_usergroups` WHERE `groupName`=?';
8450			return $this->getOne($query, [$group]);
8451		} else {
8452			$query = 'SELECT count(*) FROM `users_users`';
8453			return $this->getOne($query, []);
8454		}
8455	}
8456
8457	function find_best_user($usrs, $group = '', $key = 'login')
8458	{
8459		$finalusers = [];
8460		foreach ($usrs as $u) {
8461			$u = trim($u);
8462			if (! $u) {
8463				continue;
8464			}
8465			if ($u == 'admin') {
8466				$finalusers[] = $u;
8467			} elseif ($key == 'userId' && preg_match('/\(([0-9]+)\)$/', $u, $matches)) {
8468				$finalusers[] = $this->get_user_login($matches[1]);
8469			} elseif ($key == 'login' && preg_match('/\((.+)\)$/', $u, $matches)) {
8470				$finalusers[] = $matches[1];
8471			} else {
8472				$possibleusers = $this->get_users_light(0, -1, 'login_asc', '', $group);
8473				$unames = array_keys($possibleusers, $u);
8474				if (count($unames) == 1 && $unames[0]) {
8475					$finalusers[] = $unames[0];
8476				}
8477			}
8478		}
8479
8480		return $finalusers;
8481	}
8482
8483	function clean_user($u, $force_check_realnames = false, $login_fallback = true)
8484	{
8485		global $prefs;
8486		$tikilib = TikiLib::lib('tiki');
8487		if ($prefs['user_show_realnames'] == 'y' || $force_check_realnames) {
8488			// need to trim to prevent mustMatch failure
8489			$realname = trim($tikilib->get_user_preference($u, 'realName', ''));
8490		}
8491		if (! empty($realname)) {
8492			$u = $realname;
8493		} elseif ($prefs['login_is_email_obscure'] == 'y' && $atsign = strpos($u, '@')) {
8494			$u = substr($u, 0, $atsign);
8495			if (! $login_fallback) {
8496				$u = tra('Anonymous');
8497			}
8498		}
8499
8500		return $u;
8501	}
8502
8503	private function categorize_user_tracker_item($user, $group)
8504	{
8505		$tikilib = TikiLib::lib('tiki');
8506		$userid = $this->get_user_id($user);
8507		$tracker = $this->get_usertracker($userid);
8508		if ($tracker && $tracker['usersTrackerId']) {
8509			$trklib = TikiLib::lib('trk');
8510			$categlib = TikiLib::lib('categ');
8511			$itemid = $trklib->get_item_id($tracker['usersTrackerId'], $tracker['usersFieldId'], $user);
8512			$cat = $categlib->get_object_categories('trackeritem', $itemid);
8513			$categId = $categlib->get_category_id($group);
8514			if (! $categId) {
8515				return false;
8516			}
8517			$cat[] = $categId;
8518			$cat = array_unique($cat);
8519
8520			// using override_perms=true because if user adding himself to group may not have perms yet
8521			$trklib->categorized_item($tracker["usersTrackerId"], $itemid, '', $cat, [], true);
8522			require_once('lib/search/refresh-functions.php');
8523			refresh_index('trackeritem', $itemid);
8524		}
8525	}
8526
8527	private function uncategorize_user_tracker_item($user, $group)
8528	{
8529		$tikilib = TikiLib::lib('tiki');
8530		$userid = $this->get_user_id($user);
8531		$tracker = $this->get_usertracker($userid);
8532
8533		if ($tracker && $tracker['usersTrackerId']) {
8534			$trklib = TikiLib::lib('trk');
8535			$categlib = TikiLib::lib('categ');
8536			$itemid = $trklib->get_item_id($tracker['usersTrackerId'], $tracker['usersFieldId'], $user);
8537			$cat = $categlib->get_object_categories('trackeritem', $itemid);
8538			$categId = $categlib->get_category_id($group);
8539			if (! $categId) {
8540				return false;
8541			}
8542			$cat = array_diff($cat, [$categId]);
8543			$trklib->categorized_item($tracker["usersTrackerId"], $itemid, '', $cat, [], true);
8544			require_once('lib/search/refresh-functions.php');
8545			refresh_index('trackeritem', $itemid);
8546		}
8547	}
8548
8549	/**
8550	 * Remove the link between a Tiki user account
8551	 * and an OpenID account
8552	 *
8553	 * @param int $userId
8554	 * @return TikiDb_Pdo_Result|TikiDb_Adodb_Result
8555	 */
8556	function remove_openid_link($userId)
8557	{
8558		$query = "UPDATE `users_users` SET `openid_url` = NULL WHERE `userId` = ?";
8559		$bindvars = [$userId];
8560		return $this->query($query, $bindvars);
8561	}
8562
8563	function get_lost_groups()
8564	{
8565		$query = 'SELECT ugp.`groupName` FROM `users_grouppermissions` ugp' .
8566							' LEFT JOIN `users_groups` ug ON ( ug.`groupName` = ugp.`groupName` )' .
8567							' WHERE ug.`groupName` IS NULL';
8568
8569		$groups = $this->fetchAll($query);
8570		$ret = [];
8571
8572		foreach ($groups as $res) {
8573			if (! in_array($res['groupName'], $ret)) {
8574				$ret[] = $res['groupName'];
8575			}
8576		}
8577
8578		$query = 'SELECT ugp.`groupName` FROM `users_objectpermissions` ugp' .
8579							' LEFT JOIN `users_groups` ug ON ( ug.`groupName` = ugp.`groupName` )' .
8580							' WHERE ug.`groupName` IS NULL';
8581
8582		$groups = $this->fetchAll($query);
8583
8584		foreach ($groups as $res) {
8585			if (! in_array($res['groupName'], $ret)) {
8586				$ret[] = $res['groupName'];
8587			}
8588		}
8589
8590		return $ret;
8591	}
8592
8593	function remove_lost_groups()
8594	{
8595		$groups = $this->get_lost_groups();
8596		if (empty($groups)) {
8597			return;
8598		}
8599		$query = 'delete FROM `users_grouppermissions` where `groupName` in (' . implode(',', array_fill(0, count($groups), '?')) . ')';
8600		$this->query($query, $groups);
8601		$query = 'delete FROM `users_objectpermissions` where `groupName` in (' . implode(',', array_fill(0, count($groups), '?')) . ')';
8602
8603		$this->query($query, $groups);
8604	}
8605
8606	function get_user_groups_date($userId)
8607	{
8608		$query = 'select * from `users_usergroups` where `userId`=?';
8609		$result = $this->query($query, [$userId]);
8610		$ret = [];
8611		while ($res = $result->fetchRow()) {
8612			$g = $res['groupName'];
8613			$ret[$g]['created'] = $res['created'];
8614			$ret[$g]['expire'] = $res['expire'];
8615		}
8616		return $ret;
8617	}
8618
8619	/**
8620	 * This is a function to automatically login a user programatically
8621	 * @param string $uname The user account name to log the user in as
8622	 * @return bool true means that successfully logged in or already logged in. false means no such user.
8623	 */
8624	function autologin_user($uname)
8625	{
8626		global $user;
8627		if ($user) {
8628			// already logged in
8629			return true;
8630		}
8631		if (! $this->user_exists($uname)) {
8632			// no such user
8633			return false;
8634		}
8635		// Conduct login
8636		global $user_cookie_site;
8637		$_SESSION[$user_cookie_site] = $uname;
8638		$this->update_expired_groups();
8639		$this->update_lastlogin($uname);
8640		return true;
8641	}
8642
8643	/**
8644	 * This is a function to invite users to temporarily access the site via a token
8645	 * @param array $emails Emails to send the invite to
8646	 * @param array $groups Groups that the temporary user should have (Registered is not included unless explicitly added)
8647	 * @param int $timeout How long the invitation is valid for, in seconds.
8648	 * @param string $prefix Username of the created users will be the token ID prefixed with this
8649	 * @param string $path Users will have to autologin using this path on the site using the token
8650	 * @throws Exception
8651	 */
8652	function invite_tempuser($emails, $groups, $timeout, $prefix = 'guest', $path = 'index.php')
8653	{
8654		global $user, $prefs;
8655		$smarty = TikiLib::lib('smarty');
8656		include_once('lib/webmail/tikimaillib.php');
8657		$referer = Services_Utilities::noJsPath();
8658
8659		$mail = new TikiMail();
8660		foreach ($emails as $email) {
8661			if (! validate_email($email)) {
8662				$mes = empty($email) ? tr('Email address is required.') : tr('Invalid email address "%0"', $email);
8663				Feedback::error($mes);
8664				Services_Utilities::sendFeedback($referer);
8665			}
8666		}
8667		$foo = parse_url($_SERVER['REQUEST_URI']);
8668		$machine = $this->httpPrefix(true) . dirname($foo['path']);
8669		$machine = preg_replace('!/$!', '', $machine); // just in case
8670		$smarty->assign_by_ref('mail_machine', $machine);
8671		$smarty->assign('mail_sender', $user);
8672		$smarty->assign('expiry', $user);
8673		$mail->setBcc($this->get_user_email($user));
8674		$smarty->assign('token_expiry', $this->get_long_datetime($this->now + $timeout));
8675		require_once 'lib/auth/tokens.php';
8676
8677		foreach ($emails as $email) {
8678			$tokenlib = AuthTokens::build($prefs);
8679			$token_url = $tokenlib->includeToken($machine . "/$path", $groups, $email, $timeout, -1, true, $prefix);
8680			include_once('tiki-sefurl.php');
8681			$token_url = filter_out_sefurl($token_url);
8682			$smarty->assign('token_url', $token_url);
8683			$mail->setUser($user);
8684			$mail->setSubject($smarty->fetch('mail/invite_tempuser_subject.tpl'));
8685			$mail->setHtml($smarty->fetch('mail/invite_tempuser.tpl'));
8686
8687			if (! $mail->send($email)) {
8688				$errormsg = tr('Unable to send mail to invite "%0"', $email);
8689				if (Perms::get()->admin) {
8690					$mailerrors = print_r($mail->errors, true);
8691					$errormsg .= $mailerrors;
8692				}
8693				Feedback::error($errormsg);
8694				Services_Utilities::sendFeedback($referer);
8695			}
8696			$smarty->assign_by_ref('user', $user);
8697		}
8698	}
8699
8700	/**
8701	 * @param string $uname The username of the temporary user to remove (or disable depending on the pref)
8702	 *
8703	 */
8704	function remove_temporary_user($uname)
8705	{
8706		global $prefs;
8707		if ($prefs['auth_token_preserve_tempusers'] == 'y') {
8708			$this->remove_user_from_all_groups($uname);
8709		} else {
8710			$this->remove_user($uname);
8711		}
8712	}
8713}
8714
8715
8716
8717/* For the emacs weenies in the crowd.
8718Local Variables:
8719   c-basic-offset: 4
8720End:
8721*/
8722