Accounts::auto_add()   F
last analyzed

Complexity

Conditions 20
Paths 10240

Size

Total Lines 76
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 46
nc 10240
nop 2
dl 0
loc 76
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * EGroupware API - Accounts
4
 *
5
 * @link http://www.egroupware.org
6
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de> complete rewrite in 6/2006 and earlier modifications
7
 *
8
 * Implements the (now depricated) interfaces on the former accounts class written by
9
 * Joseph Engo <[email protected]> and Bettina Gille <[email protected]>
10
 * Copyright (C) 2000 - 2002 Joseph Engo, Copyright (C) 2003 Joseph Engo, Bettina Gille
11
 *
12
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
13
 * @package api
14
 * @subpackage accounts
15
 */
16
17
namespace EGroupware\Api;
18
19
/**
20
 * API - accounts
21
 *
22
 * This class uses a backend class (at them moment SQL or LDAP) and implements some
23
 * caching on to top of the backend functions:
24
 *
25
 * a) instance-wide account-data cache queried by account_id including also members(hips)
26
 *    implemented by self::cache_read($account_id) and self::cache_invalidate($account_ids)
27
 *
28
 * b) session based cache for search, split_accounts and name2id
29
 *    implemented by self::setup_cache() and self::cache_invalidate()
30
 *
31
 * The backend only implements the read, save, delete, name2id and the {set_}members{hips} methods.
32
 * The account class implements all other (eg. name2id, id2name) functions on top of these.
33
 *
34
 * read and search return timestamps (account_(created|modified|lastlogin) in server-time!
35
 */
36
class Accounts
37
{
38
	/**
39
	 * Enables the session-cache, currently switched on independent of the backend
40
	 *
41
	 * @var boolean
42
	 */
43
	static $use_session_cache = true;
44
45
	/**
46
	 * Cache, stored in sesssion
47
	 *
48
	 * @var array
49
	 */
50
	static $cache;
51
52
	/**
53
	 * Keys for which both versions with 'account_' prefix and without (depricated!) can be used, if requested.
54
	 * Migrate your code to always use the 'account_' prefix!!!
55
	 *
56
	 * @var array
57
	 */
58
	var $depricated_names = array('firstname','lastname','fullname','email','type',
59
		'status','expires','lastlogin','lastloginfrom','lastpasswd_change');
60
61
	/**
62
	 * List of all config vars accounts depend on and therefore should be passed in when calling contructor with array syntax
63
	 *
64
	 * @var array
65
	 */
66
	static public $config_vars = array(
67
		'account_repository', 'auth_type',	// auth_type if fallback if account_repository is not set
68
		'install_id',	// instance-specific caching
69
		'auto_create_expire', 'default_group_lid',	// auto-creation of accounts
70
		'ldap_host','ldap_root_dn','ldap_root_pw','ldap_context','ldap_group_context','ldap_search_filter',	// ldap backend
71
		'ads_domain', 'ads_host', 'ads_admin_user', 'ads_admin_passwd', 'ads_connection', 'ads_context',	// ads backend
72
	);
73
74
	/**
75
	 * Querytypes for the account-search
76
	 *
77
	 * @var array
78
	 */
79
	var $query_types = array(
80
		'all' => 'all fields',
81
		'firstname' => 'firstname',
82
		'lastname' => 'lastname',
83
		'lid' => 'LoginID',
84
		'email' => 'email',
85
		'start' => 'start with',
86
		'exact' => 'exact',
87
	);
88
89
	/**
90
	 * Backend to use
91
	 *
92
	 * @var Accounts\Sql|Accounts\Ldap|Accounts\Ads|Accounts\Univention
93
	 */
94
	var $backend;
95
96
	/**
97
	 * total number of found entries
98
	 *
99
	 * @var int
100
	 */
101
	var $total;
102
103
	/**
104
	 * Current configuration
105
	 *
106
	 * @var array
107
	 */
108
	var $config;
109
110
	/**
111
	 * hold an instance of the accounts class
112
	 *
113
	 * @var Accounts the instance of the accounts class
114
	 */
115
	private static $_instance = NULL;
116
117
	/**
118
	 * Singleton
119
	 *
120
	 * @return Accounts
121
	 */
122
	public static function getInstance()
123
	{
124
		if (self::$_instance === NULL)
125
		{
126
			self::$_instance = new Accounts();
127
		}
128
		return self::$_instance;
129
	}
130
131
	/**
132
	 * Constructor
133
	 *
134
	 * @param string|array $backend =null string with backend 'sql'|'ldap', or whole config array, default read from global egw_info
135
	 */
136
	public function __construct($backend=null)
137
	{
138
		if (is_array($backend))
139
		{
140
			$this->config = $backend;
141
			$backend = null;
142
			self::$_instance = $this;	// also set instance returned by singleton
143
			self::$cache = array();		// and empty our internal (session) cache
144
		}
145
		else
146
		{
147
			$this->config =& $GLOBALS['egw_info']['server'];
148
149
			if (!isset(self::$_instance)) self::$_instance = $this;
150
		}
151
		if (is_null($backend))
152
		{
153
			if (empty($this->config['account_repository']))
154
			{
155
				if (!empty($this->config['auth_type']))
156
				{
157
					$this->config['account_repository'] = $this->config['auth_type'];
158
				}
159
				else
160
				{
161
					$this->config['account_repository'] = 'sql';
162
				}
163
			}
164
			$backend = $this->config['account_repository'];
165
		}
166
		$backend_class = 'EGroupware\\Api\\Accounts\\'.ucfirst($backend);
167
168
		$this->backend = new $backend_class($this);
169
	}
170
171
	/**
172
	 * Searches / lists accounts: users and/or groups
173
	 *
174
	 * @param array with the following keys:
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\with was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
175
	 * @param $param['type'] string|int 'accounts', 'groups', 'owngroups' (groups the user is a member of), 'both',
176
	 * 	'groupmembers' (members of groups the user is a member of), 'groupmembers+memberships' (incl. memberships too)
177
	 *	or integer group-id for a list of members of that group
178
	 * @param $param['start'] int first account to return (returns offset or max_matches entries) or all if not set
179
	 * @param $param['order'] string column to sort after, default account_lid if unset
180
	 * @param $param['sort'] string 'ASC' or 'DESC', default 'ASC' if not set
181
	 * @param $param['query'] string to search for, no search if unset or empty
182
	 * @param $param['query_type'] string:
183
	 *	'all'   - query all fields for containing $param[query]
184
	 *	'start' - query all fields starting with $param[query]
185
	 *	'exact' - query all fields for exact $param[query]
186
	 *	'lid','firstname','lastname','email' - query only the given field for containing $param[query]
187
	 * @param $param['app'] string with an app-name, to limit result on accounts with run-right for that app
188
	 * @param $param['offset'] int - number of matches to return if start given, default use the value in the prefs
189
	 * @param $param['active']=true boolean - true: return only acctive accounts, false: return expired or deactivated too
190
	 * @return array with account_id => data pairs, data is an array with account_id, account_lid, account_firstname,
191
	 *	account_lastname, person_id (id of the linked addressbook entry), account_status, account_expires, account_primary_group
192
	 */
193
	function search($param)
194
	{
195
		//error_log(__METHOD__.'('.array2string($param).') '.function_backtrace());
196
		if (!isset($param['active'])) $param['active'] = true;	// default is true = only return active accounts
197
198
		// Check for lang(Group) in search - if there, we search all groups
199
		$group_index = array_search(strtolower(lang('Group')), array_map('strtolower', $query = explode(' ',$param['query'])));
200
		if($group_index !== FALSE && !(
201
				in_array($param['type'], array('accounts', 'groupmembers')) || is_int($param['type'])
202
		))
203
		{
204
			// do not return any groups for account-selection == "none"
205
			if ($GLOBALS['egw_info']['user']['preferences']['common']['account_selection'] === 'none' &&
206
				!isset($GLOBALS['egw_info']['user']['apps']['admin']))
207
			{
208
				$this->total = 0;
209
				return array();
210
			}
211
			// only return own memberships for account-selection == "groupmembers"
212
			$param['type'] = $GLOBALS['egw_info']['user']['preferences']['common']['account_selection'] === 'groupmembers' &&
213
				!isset($GLOBALS['egw_info']['user']['apps']['admin']) ? 'owngroups' : 'groups';
214
			// Remove the 'group' from the query, but only one (eg: Group NoGroup -> NoGroup)
215
			unset($query[$group_index]);
216
			$param['query'] = implode(' ', $query);
217
		}
218
		self::setup_cache();
219
		$account_search = &self::$cache['account_search'];
220
		$serial = serialize($param);
221
222
		if (isset($account_search[$serial]))
223
		{
224
			$this->total = $account_search[$serial]['total'];
225
		}
226
		// no backend understands $param['app'], only sql understands type owngroups or groupmemember[+memberships]
227
		// --> do an full search first and then filter and limit that search
228
		elseif($param['app'] || $this->config['account_repository'] != 'sql' &&
229
			in_array($param['type'], array('owngroups','groupmembers','groupmembers+memberships')))
230
		{
231
			$app = $param['app'];
232
			unset($param['app']);
233
			$start = $param['start'];
234
			unset($param['start']);
235
			$offset = $param['offset'] ? $param['offset'] : $GLOBALS['egw_info']['user']['preferences']['common']['maxmatchs'];
236
			unset($param['offset']);
237
			$stop = $start + $offset;
238
239
			if ($param['type'] == 'owngroups')
240
			{
241
				$members = $this->memberships($GLOBALS['egw_info']['user']['account_id'],true);
242
				$param['type'] = 'groups';
243
			}
244
			elseif(in_array($param['type'],array('groupmembers','groupmembers+memberships')))
245
			{
246
				$members = array();
247
				foreach((array)$this->memberships($GLOBALS['egw_info']['user']['account_id'],true) as $grp)
248
				{
249
					$members = array_unique(array_merge($members, (array)$this->members($grp,true,$param['active'])));
250
					if ($param['type'] == 'groupmembers+memberships') $members[] = $grp;
251
				}
252
				$param['type'] = $param['type'] == 'groupmembers+memberships' ? 'both' : 'accounts';
253
			}
254
			// call ourself recursive to get (evtl. cached) full search
255
			$full_search = $this->search($param);
256
257
			// filter search now on accounts with run-rights for app or a group
258
			$valid = array();
259
			if ($app)
260
			{
261
				// we want the result merged, whatever it takes, as we only care for the ids
262
				$valid = $this->split_accounts($app,!in_array($param['type'],array('accounts','groups')) ? 'merge' : $param['type'],$param['active']);
263
			}
264
			if (isset($members))
265
			{
266
				//error_log(__METHOD__.'() members='.array2string($members));
267
				if (!$members) $members = array();
268
				$valid = !$app ? $members : array_intersect($valid,$members);	// use the intersection
269
			}
270
			//error_log(__METHOD__."() limiting result to app='$app' and/or group=$group valid-ids=".array2string($valid));
271
			$n = 0;
272
			$account_search[$serial]['data'] = array();
273
			foreach ($full_search as $id => $data)
274
			{
275
				if (!in_array($id,$valid))
276
				{
277
					$this->total--;
278
					continue;
279
				}
280
				// now we have a valid entry
281
				if (!is_int($start) || $start <= $n && $n < $stop)
282
				{
283
					$account_search[$serial]['data'][$id] = $data;
284
				}
285
				$n++;
286
			}
287
			$account_search[$serial]['total'] = $this->total;
288
		}
289
		// direct search via backend
290
		else
291
		{
292
			$account_search[$serial]['data'] = $this->backend->search($param);
293
			if ($param['type'] !== 'accounts')
294
			{
295
				foreach($account_search[$serial]['data'] as &$account)
296
				{
297
					// add default description for Admins and Default group
298
					if ($account['account_type'] === 'g')
299
					{
300
						self::add_default_group_description($account);
301
					}
302
				}
303
			}
304
			$account_search[$serial]['total'] = $this->total = $this->backend->total;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->backend->total can also be of type boolean. However, the property $total is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
305
		}
306
		return $account_search[$serial]['data'];
307
	}
308
309
	/**
310
	 * Query for accounts
311
	 *
312
	 * @param string|array $pattern
313
	 * @param array $options
314
	 *  $options['filter']['group'] only return members of that group
315
	 *  $options['account_type'] "accounts", "groups", "both" or "groupmembers"
316
	 * @return array with id - title pairs of the matching entries
317
	 */
318
	public static function link_query($pattern, array &$options = array())
319
	{
320
		if (isset($options['filter']) && !is_array($options['filter']))
321
		{
322
			$options['filter'] = (array)$options['filter'];
323
		}
324
		switch($GLOBALS['egw_info']['user']['preferences']['common']['account_display'])
325
		{
326
			case 'firstname':
327
			case 'firstall':
328
			case 'firstgroup':
329
				$order = 'account_firstname,account_lastname';
330
				break;
331
			case 'lastname':
332
			case 'lastall':
333
			case 'firstgroup':
334
				$order = 'account_lastname,account_firstname';
335
				break;
336
			default:
337
				$order = 'account_lid';
338
				break;
339
		}
340
		$only_own = $GLOBALS['egw_info']['user']['preferences']['common']['account_selection'] === 'groupmembers' &&
341
			!isset($GLOBALS['egw_info']['user']['apps']['admin']);
342
		switch($options['account_type'])
343
		{
344
			case 'accounts':
345
				$type = $only_own ? 'groupmembers' : 'accounts';
346
				break;
347
			case 'groups':
348
				$type = $only_own ? 'owngroups' : 'groups';
349
				break;
350
			case 'memberships':
351
				$type = 'owngroups';
352
				break;
353
			case 'owngroups':
354
			case 'groupmembers':
355
				$type = $options['account_type'];
356
				break;
357
			case 'both':
358
			default:
359
				$type = $only_own ? 'groupmembers+memberships' : 'both';
360
				break;
361
		}
362
		$accounts = array();
363
		foreach(self::getInstance()->search(array(
364
			'type' => $options['filter']['group'] < 0 ? $options['filter']['group'] : $type,
365
			'query' => $pattern,
366
			'query_type' => 'all',
367
			'order' => $order,
368
		)) as $account)
369
		{
370
			$accounts[$account['account_id']] = self::format_username($account['account_lid'],
371
				$account['account_firstname'],$account['account_lastname'],$account['account_id']);
372
		}
373
		return $accounts;
374
	}
375
376
	/**
377
	 * Reads the data of one account
378
	 *
379
	 * All key of the returned array use the 'account_' prefix.
380
	 * For backward compatibility some values are additionaly availible without the prefix, using them is depricated!
381
	 *
382
	 * @param int|string $id numeric account_id or string with account_lid
383
	 * @param boolean $set_depricated_names =false set _additionaly_ the depricated keys without 'account_' prefix
384
	 * @return array/boolean array with account data (keys: account_id, account_lid, ...) or false if account not found
0 ignored issues
show
Documentation Bug introduced by
The doc comment array/boolean at position 0 could not be parsed: Unknown type name 'array/boolean' at position 0 in array/boolean.
Loading history...
385
	 */
386
	function read($id, $set_depricated_names=false)
387
	{
388
		if (!is_int($id) && !is_numeric($id))
389
		{
390
			$id = $this->name2id($id);
391
		}
392
		if (!$id) return false;
393
394
		$data = self::cache_read($id);
395
396
		// add default description for Admins and Default group
397
		if ($data['account_type'] === 'g')
398
		{
399
			self::add_default_group_description($data);
400
		}
401
402
		if ($set_depricated_names && $data)
403
		{
404
			foreach($this->depricated_names as $name)
405
			{
406
				$data[$name] =& $data['account_'.$name];
407
			}
408
		}
409
		return $data;
410
	}
411
412
	/**
413
	 * Get an account as json, returns only whitelisted fields:
414
	 * - 'account_id','account_lid','person_id','account_status',
415
	 * - 'account_firstname','account_lastname','account_email','account_fullname','account_phone'
416
	 *
417
	 * @param int|string $id
418
	 * @return string|boolean json or false if not found
419
	 */
420
	function json($id)
421
	{
422
		static $keys = array(
423
			'account_id','account_lid','person_id','account_status',
424
			'account_firstname','account_lastname','account_email','account_fullname','account_phone',
425
		);
426
		if (($account = $this->read($id)))
427
		{
428
			$account = array_intersect_key($account, array_flip($keys));
429
		}
430
		// for current user, add the apps available to him
431
		if ($id == $GLOBALS['egw_info']['user']['account_id'])
432
		{
433
			foreach((array)$GLOBALS['egw_info']['user']['apps'] as $app => $data)
434
			{
435
				unset($data['table_defs']);	// no need for that on the client
436
				$account['apps'][$app] = $data;
437
			}
438
		}
439
		return json_encode($account, JSON_PARTIAL_OUTPUT_ON_ERROR|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
440
	}
441
442
	/**
443
	 * Format lid, firstname, lastname according to use preferences
444
	 *
445
	 * @param $lid ='' account loginid
0 ignored issues
show
Documentation Bug introduced by
The doc comment ='' at position 0 could not be parsed: Unknown type name '=''' at position 0 in =''.
Loading history...
446
	 * @param $firstname ='' firstname
447
	 * @param $lastname ='' lastname
448
	 * @param $accountid =0 id, to check if it's a user or group, otherwise the lid will be used
449
	 */
450
	static function format_username($lid = '', $firstname = '', $lastname = '', $accountid=0)
451
	{
452
		if (!$lid && !$firstname && !$lastname)
453
		{
454
			$lid       = $GLOBALS['egw_info']['user']['account_lid'];
455
			$firstname = $GLOBALS['egw_info']['user']['account_firstname'];
456
			$lastname  = $GLOBALS['egw_info']['user']['account_lastname'];
457
		}
458
		$is_group = $GLOBALS['egw']->accounts->get_type($accountid ? $accountid : $lid) == 'g';
459
460
		if (empty($firstname)) $firstname = $lid;
461
		if (empty($lastname) || $is_group)
462
		{
463
			$lastname  = $is_group ? lang('Group') : lang('User');
464
		}
465
		$display = $GLOBALS['egw_info']['user']['preferences']['common']['account_display'];
466
467
		if ($firstname && $lastname)
468
		{
469
			$delimiter = $is_group ? ' ' : ', ';
470
		}
471
		else
472
		{
473
			$delimiter = '';
474
		}
475
476
		$name = '';
477
		switch($display)
478
		{
479
			case 'firstname':
480
				$name = $firstname . ' ' . $lastname;
481
				break;
482
			case 'lastname':
483
				$name = $lastname . $delimiter . $firstname;
484
				break;
485
			case 'username':
486
				$name = $lid;
487
				break;
488
			case 'firstall':
489
				$name = $firstname . ' ' . $lastname . ' ['.$lid.']';
490
				break;
491
			case 'lastall':
492
				$name = $lastname . $delimiter . $firstname . ' ['.$lid.']';
493
				break;
494
			case 'allfirst':
495
				$name = '['.$lid.'] ' . $firstname . ' ' . $lastname;
496
				break;
497
			case 'firstgroup':
498
				$group = Accounts::id2name($lid, 'account_primary_group');
499
				$name = $firstname . ' ' . $lastname . ($is_group ? '' : ' ('.Accounts::id2name($group).')');
500
				break;
501
			case 'lastgroup':
502
				$group = Accounts::id2name($lid, 'account_primary_group');
503
				$name = $lastname . $delimiter . $firstname . ($is_group ? '' : ' ('.Accounts::id2name($group).')');
504
				break;
505
			case 'all':
506
				/* fall through */
507
			default:
508
				$name = '['.$lid.'] ' . $lastname . $delimiter . $firstname;
509
		}
510
		return $name;
511
	}
512
513
	/**
514
	 * Return formatted username for a given account_id
515
	 *
516
	 * @param string $account_id =null account id
517
	 * @return string full name of user or "#$accountid" if user not found
518
	 */
519
	static function username($account_id=null)
520
	{
521
		if ($account_id && !($account = self::cache_read((int)$account_id)))
522
		{
523
			return '#'.$account_id;
524
		}
525
		return self::format_username($account['account_lid'],
526
			$account['account_firstname'] , $account['account_lastname'], $account_id);
527
	}
528
529
	/**
530
	 * Format an email address according to the system standard
531
	 *
532
	 * Convert all european special chars to ascii and fallback to the accountname, if nothing left eg. chiniese
533
	 *
534
	 * @param string $first firstname
535
	 * @param string $last lastname
536
	 * @param string $account account-name (lid)
537
	 * @param string $domain =null domain-name or null to use eGW's default domain $GLOBALS['egw_info']['server']['mail_suffix]
538
	 * @return string with email address
539
	 */
540
	static function email($first,$last,$account,$domain=null)
541
	{
542
		if ($GLOBALS['egw_info']['server']['email_address_format'] === 'none')
543
		{
544
			return null;
545
		}
546
		foreach (array('first','last','account') as $name)
547
		{
548
			$$name = Translation::to_ascii($$name);
549
		}
550
		//echo " --> ('$first', '$last', '$account')";
551
		if (!$first && !$last)	// fallback to the account-name, if real names contain only special chars
552
		{
553
			$first = '';
554
			$last = $account;
555
		}
556
		if (!$first || !$last)
557
		{
558
			$dot = $underscore = '';
559
		}
560
		else
561
		{
562
			$dot = '.';
563
			$underscore = '_';
564
		}
565
		if (!$domain) $domain = $GLOBALS['egw_info']['server']['mail_suffix'];
566
		if (!$domain) $domain = $_SERVER['SERVER_NAME'];
567
568
		$email = str_replace(array('first','last','initial','account','dot','underscore','-'),
569
			array($first,$last,substr($first,0,1),$account,$dot,$underscore,''),
570
			$GLOBALS['egw_info']['server']['email_address_format'] ? $GLOBALS['egw_info']['server']['email_address_format'] : 'first-dot-last').
571
			($domain ? '@'.$domain : '');
572
573
		if (!empty($GLOBALS['egw_info']['server']['email_address_lowercase']))
574
		{
575
			$email = strtolower($email);
576
		}
577
		//echo " = '$email'</p>\n";
578
		return $email;
579
	}
580
581
	/**
582
	 * Add a default description for stock groups: Admins, Default, NoGroup
583
	 *
584
	 * @param array &$data
585
	 */
586
	protected static function add_default_group_description(array &$data)
587
	{
588
		if (empty($data['account_description']))
589
		{
590
			switch($data['account_lid'])
591
			{
592
				case 'Default':
593
					$data['account_description'] = lang('EGroupware all users group, do NOT delete');
594
					break;
595
				case 'Admins':
596
					$data['account_description'] = lang('EGroupware administrators group, do NOT delete');
597
					break;
598
				case 'NoGroup':
599
					$data['account_description'] = lang('EGroupware anonymous users group, do NOT delete');
600
					break;
601
			}
602
		}
603
		else
604
		{
605
			$data['account_description'] = lang($data['account_description']);
606
		}
607
	}
608
609
	/**
610
	 * Saves / adds the data of one account
611
	 *
612
	 * If no account_id is set in data the account is added and the new id is set in $data.
613
	 *
614
	 * @param array $data array with account-data
615
	 * @param boolean $check_depricated_names =false check _additionaly_ the depricated keys without 'account_' prefix
616
	 * @return int|boolean the account_id or false on error
617
	 */
618
	function save(&$data,$check_depricated_names=false)
619
	{
620
		if ($check_depricated_names)
621
		{
622
			foreach($this->depricated_names as $name)
623
			{
624
				if (isset($data[$name]) && !isset($data['account_'.$name]))
625
				{
626
					$data['account_'.$name] =& $data[$name];
627
				}
628
			}
629
		}
630
		// add default description for Admins and Default group
631
		if ($data['account_type'] === 'g' && empty($data['account_description']))
632
		{
633
			self::add_default_group_description($data);
634
		}
635
		if (($id = $this->backend->save($data)) && $data['account_type'] != 'g')
636
		{
637
			// if we are not on a pure LDAP system, we have to write the account-date via the contacts class now
638
			if (($this->config['account_repository'] == 'sql' || $this->config['contact_repository'] == 'sql-ldap') &&
639
				(!($old = $this->read($data['account_id'])) ||	// only for new account or changed contact-data
640
				$old['account_firstname'] != $data['account_firstname'] ||
641
				$old['account_lastname'] != $data['account_lastname'] ||
642
				$old['account_email'] != $data['account_email']))
643
			{
644
				if (!$data['person_id']) $data['person_id'] = $old['person_id'];
645
646
				// Include previous contact information to avoid blank history rows
647
				$contact = (array)$GLOBALS['egw']->contacts->read($data['person_id']) + array(
648
					'n_given'    => $data['account_firstname'],
649
					'n_family'   => $data['account_lastname'],
650
					'email'      => $data['account_email'],
651
					'account_id' => $data['account_id'],
652
					'id'         => $data['person_id'],
653
					'owner'      => 0,
654
				);
655
				$GLOBALS['egw']->contacts->save($contact,true);		// true = ignore addressbook acl
656
			}
657
			// save primary group if necessary
658
			if ($data['account_primary_group'] && (!($memberships = $this->memberships($id,true)) ||
659
				!in_array($data['account_primary_group'],$memberships)))
660
			{
661
				$memberships[] = $data['account_primary_group'];
662
				$this->set_memberships($memberships, $id);	// invalidates cache for account_id and primary group
663
			}
664
		}
665
		// as some backends set (group-)members in save, we need to invalidate their members too!
666
		$invalidate = isset($data['account_members']) ? $data['account_members'] : array();
667
		$invalidate[] = $data['account_id'];
668
		self::cache_invalidate($invalidate);
669
670
		return $id;
671
	}
672
673
	/**
674
	 * Delete one account, deletes also all acl-entries for that account
675
	 *
676
	 * @param int|string $id numeric account_id or string with account_lid
677
	 * @return boolean true on success, false otherwise
678
	 */
679
	function delete($id)
680
	{
681
		if (!is_int($id) && !is_numeric($id))
682
		{
683
			$id = $this->name2id($id);
684
		}
685
		if (!$id) return false;
686
687
		if ($this->get_type($id) == 'u')
688
		{
689
			$invalidate = $this->memberships($id, true);
690
		}
691
		else
692
		{
693
			$invalidate = $this->members($id, true, false);
694
		}
695
		$invalidate[] = $id;
696
697
		$this->backend->delete($id);
698
699
		self::cache_invalidate($invalidate);
700
701
		// delete all acl_entries belonging to that user or group
702
		$GLOBALS['egw']->acl->delete_account($id);
703
704
		// delete all categories belonging to that user or group
705
		Categories::delete_account($id);
706
707
		return true;
708
	}
709
710
	/**
711
	 * Test if given an account is expired
712
	 *
713
	 * @param int|string|array $data account_(l)id or array with account-data
714
	 * @return boolean true=expired (no more login possible), false otherwise
715
	 */
716
	static function is_expired($data)
717
	{
718
		if (is_null($data))
0 ignored issues
show
introduced by
The condition is_null($data) is always false.
Loading history...
719
		{
720
			throw new Exception\WrongParameter('Missing parameter to Accounts::is_active()');
721
		}
722
		if (!is_array($data)) $data = self::getInstance()->read($data);
723
724
		$expires = isset($data['account_expires']) ? $data['account_expires'] : $data['expires'];
725
726
		return $expires != -1 && $expires < time();
727
	}
728
729
	/**
730
	 * Test if an account is active - NOT deactivated or expired
731
	 *
732
	 * @param int|string|array $data account_(l)id or array with account-data
733
	 * @return boolean false if account does not exist, is expired or decativated, true otherwise
734
	 */
735
	static function is_active($data)
736
	{
737
		if (!is_array($data)) $data = self::getInstance()->read($data);
738
739
		return $data && !(self::is_expired($data) || $data['account_status'] != 'A');
740
	}
741
742
	/**
743
	 * convert an alphanumeric account-value (account_lid, account_email) to the account_id
744
	 *
745
	 * Please note:
746
	 * - if a group and an user have the same account_lid the group will be returned (LDAP only)
747
	 * - if multiple user have the same email address, the returned user is undefined
748
	 *
749
	 * @param string $name value to convert
750
	 * @param string $which ='account_lid' type of $name: account_lid (default), account_email, person_id, account_fullname
751
	 * @param string $account_type =null u = user or g = group, or default null = try both
752
	 * @return int|false numeric account_id or false on error ($name not found)
753
	 */
754
	function name2id($name,$which='account_lid',$account_type=null)
755
	{
756
		// Don't bother searching for empty or non-scalar account_lid
757
		if(empty($name) || !is_scalar($name))
758
		{
759
			return False;
760
		}
761
762
		self::setup_cache();
763
		$name_list = &self::$cache['name_list'];
764
765
		if(@isset($name_list[$which][$name]) && $name_list[$which][$name])
766
		{
767
			return $name_list[$which][$name];
768
		}
769
770
		return $name_list[$which][$name] = $this->backend->name2id($name,$which,$account_type);
771
	}
772
773
	/**
774
	 * Convert an numeric account_id to any other value of that account (account_lid, account_email, ...)
775
	 *
776
	 * Uses the read method to fetch all data.
777
	 *
778
	 * @param int|string $account_id numeric account_id or account_lid
779
	 * @param string $which ='account_lid' type to convert to: account_lid (default), account_email, ...
780
	 * @param boolean $generate_email =false true: generate an email address, if user has none
781
	 * @return string|boolean converted value or false on error ($account_id not found)
782
	 */
783
	static function id2name($account_id, $which='account_lid', $generate_email=false)
784
	{
785
		if (!is_numeric($account_id) && !($account_id = self::getInstance()->name2id($account_id)))
786
		{
787
			return false;
788
		}
789
		try {
790
			if (!($data = self::cache_read($account_id))) return false;
791
		}
792
		catch (Exception $e) {
793
			unset($e);
794
			return false;
795
		}
796
		if ($generate_email && $which === 'account_email' && empty($data[$which]))
797
		{
798
			return self::email($data['account_firstname'], $data['account_lastname'], $data['account_lid']);
799
		}
800
		return $data[$which];
801
	}
802
803
	/**
804
	 * get the type of an account: 'u' = user, 'g' = group
805
	 *
806
	 * @param int|string $account_id numeric account-id or alphanum. account-lid,
807
	 *	if !$accountid account of the user of this session
808
	 * @return string/false 'u' = user, 'g' = group or false on error ($accountid not found)
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/false at position 0 could not be parsed: Unknown type name 'string/false' at position 0 in string/false.
Loading history...
809
	 */
810
	function get_type($account_id)
811
	{
812
		if (!is_int($account_id) && !is_numeric($account_id))
813
		{
814
			$account_id = $this->name2id($account_id);
815
		}
816
		return $account_id > 0 ? 'u' : ($account_id < 0 ? 'g' : false);
817
	}
818
819
	/**
820
	 * check if an account exists and if it is an user or group
821
	 *
822
	 * @param int|string $account_id numeric account_id or account_lid
823
	 * @return int 0 = acount does not exist, 1 = user, 2 = group
824
	 */
825
	function exists($account_id)
826
	{
827
		if (!$account_id || !($data = $this->read($account_id)))
828
		{
829
			// non sql backends might NOT show EGw all users, but backend->id2name/name2id does
830
			if (is_a($this->backend, __CLASS__.'\\Univention'))
831
			{
832
				if (!is_numeric($account_id) ?
833
					($account_id = $this->backend->name2id($account_id)) :
834
					$this->backend->id2name($account_id))
835
				{
836
					return $account_id > 0 ? 1 : 2;
837
				}
838
			}
839
			return 0;
840
		}
841
		return $data['account_type'] == 'u' ? 1 : 2;
842
	}
843
844
	/**
845
	 * Checks if a given account is visible to current user
846
	 *
847
	 * Not all existing accounts are visible because off account_selection preference: 'none' or 'groupmembers'
848
	 *
849
	 * @param int|string $account_id nummeric account_id or account_lid
850
	 * @return boolean true = account is visible, false = account not visible, null = account does not exist
851
	 */
852
	function visible($account_id)
853
	{
854
		if (!is_numeric($account_id))	// account_lid given
855
		{
856
			$account_lid = $account_id;
857
			if (!($account_id = $this->name2id($account_lid))) return null;
858
		}
859
		else
860
		{
861
			if (!($account_lid = $this->id2name($account_id))) return null;
862
		}
863
		if (!isset($GLOBALS['egw_info']['user']['apps']['admin']) &&
864
			// do NOT allow other user, if account-selection is none
865
			($GLOBALS['egw_info']['user']['preferences']['common']['account_selection'] == 'none' &&
866
				$account_lid != $GLOBALS['egw_info']['user']['account_lid'] ||
867
			// only allow group-members for account-selection is groupmembers
868
			$GLOBALS['egw_info']['user']['preferences']['common']['account_selection'] == 'groupmembers' &&
869
				!array_intersect((array)$this->memberships($account_id,true),
870
					(array)$this->memberships($GLOBALS['egw_info']['user']['account_id'],true))))
871
		{
872
			//error_log(__METHOD__."($account_id='$account_lid') returning FALSE");
873
			return false;	// user is not allowed to see given account
874
		}
875
		return true;	// user allowed to see given account
876
	}
877
878
	/**
879
	 * Get all memberships of an account $account_id / groups the account is a member off
880
	 *
881
	 * @param int|string $account_id numeric account-id or alphanum. account-lid
882
	 * @param boolean $just_id =false return just account_id's or account_id => account_lid pairs
883
	 * @return array with account_id's ($just_id) or account_id => account_lid pairs (!$just_id)
884
	 */
885
	function memberships($account_id, $just_id=false)
886
	{
887
		if (!is_int($account_id) && !is_numeric($account_id))
888
		{
889
			$account_id = $this->name2id($account_id,'account_lid','u');
890
		}
891
		if ($account_id && ($data = self::cache_read($account_id)))
892
		{
893
			$ret = $just_id && $data['memberships'] ? array_keys($data['memberships']) : $data['memberships'];
894
		}
895
		//error_log(__METHOD__."($account_id, $just_id) data=".array2string($data)." returning ".array2string($ret));
896
		return $ret;
897
	}
898
899
	/**
900
	 * Sets the memberships of a given account
901
	 *
902
	 * @param array $groups array with gidnumbers
903
	 * @param int $account_id uidnumber
904
	 * @return boolean true: membership changed, false: no change necessary
905
	 */
906
	function set_memberships($groups,$account_id)
907
	{
908
		if (!is_int($account_id) && !is_numeric($account_id))
0 ignored issues
show
introduced by
The condition is_int($account_id) is always true.
Loading history...
909
		{
910
			$account_id = $this->name2id($account_id);
911
		}
912
		if (($old_memberships = $this->memberships($account_id, true)) == $groups)
913
		{
914
			return false;	// nothing changed
915
		}
916
		$this->backend->set_memberships($groups, $account_id);
917
918
		if (!$old_memberships) $old_memberships = array();
0 ignored issues
show
Bug Best Practice introduced by
The expression $old_memberships of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
919
		self::cache_invalidate(array_unique(array_merge(
920
			array($account_id),
921
			array_diff($old_memberships, $groups),
922
			array_diff($groups, $old_memberships)
923
		)));
924
		return true;
925
	}
926
927
	/**
928
	 * Get all members of the group $account_id
929
	 *
930
	 * @param int|string $account_id ='' numeric account-id or alphanum. account-lid,
931
	 *	default account of the user of this session
932
	 * @param boolean $just_id =false return just an array of id's and not id => lid pairs, default false
933
	 * @param boolean $active =false true: return only active (not expired or deactived) members, false: return all accounts
934
	 * @return array with account_id ($just_id) or account_id => account_lid pairs (!$just_id)
935
	 */
936
	function members($account_id, $just_id=false, $active=true)
937
	{
938
		if (!is_int($account_id) && !is_numeric($account_id))
939
		{
940
			$account_id = $this->name2id($account_id);
941
		}
942
		if ($account_id && ($data = self::cache_read($account_id, $active)))
943
		{
944
			$members = $active ? $data['members-active'] : $data['members'];
945
946
			return $just_id && $members ? array_keys($members) : $members;
947
		}
948
		return null;
949
	}
950
951
	/**
952
	 * Set the members of a group
953
	 *
954
	 * @param array $members array with uidnumber or uid's
955
	 * @param int $gid gidnumber of group to set
956
	 */
957
	function set_members($members,$gid)
958
	{
959
		if (($old_members = $this->members($gid, true, false)) != $members)
960
		{
961
			$this->backend->set_members($members, $gid);
962
963
			self::cache_invalidate(array_unique(array_merge(
964
				array($gid),
965
				array_diff($old_members, $members),
966
				array_diff($members, $old_members)
967
			)));
968
		}
969
	}
970
971
	/**
972
	 * splits users and groups from a array of id's or the accounts with run-rights for a given app-name
973
	 *
974
	 * @param array $app_users array of user-id's or app-name (if you use app-name the result gets cached!)
975
	 * @param string $use what should be returned only an array with id's of either 'accounts' or 'groups'.
976
	 *	Or an array with arrays for 'both' under the keys 'groups' and 'accounts' or 'merge' for accounts
977
	 *	and groups merged into one array
978
	 * @param boolean $active =false true: return only active (not expired or deactived) members, false: return all accounts
979
	 * @return array/boolean see $use, false on error (wront $use)
0 ignored issues
show
Documentation Bug introduced by
The doc comment array/boolean at position 0 could not be parsed: Unknown type name 'array/boolean' at position 0 in array/boolean.
Loading history...
980
	 */
981
	function split_accounts($app_users,$use='both',$active=true)
982
	{
983
		if (!is_array($app_users))
0 ignored issues
show
introduced by
The condition is_array($app_users) is always true.
Loading history...
984
		{
985
			self::setup_cache();
986
			$cache = &self::$cache['account_split'][$app_users];
987
988
			if (is_array($cache))
989
			{
990
				return $cache;
991
			}
992
			$app_users = $GLOBALS['egw']->acl->get_ids_for_location('run',1,$app_users);
993
		}
994
		$accounts = array(
995
			'accounts' => array(),
996
			'groups' => array(),
997
		);
998
		foreach($app_users as $id)
999
		{
1000
			$type = $this->get_type($id);
1001
			if($type == 'g')
1002
			{
1003
				$accounts['groups'][$id] = $id;
1004
				if ($use != 'groups')
1005
				{
1006
					foreach((array)$this->members($id, true, $active) as $id)
0 ignored issues
show
Comprehensibility Bug introduced by
$id is overwriting a variable from outer foreach loop.
Loading history...
1007
					{
1008
						$accounts['accounts'][$id] = $id;
1009
					}
1010
				}
1011
			}
1012
			else
1013
			{
1014
				$accounts['accounts'][$id] = $id;
1015
			}
1016
		}
1017
1018
		// not sure why they need to be sorted, but we need to remove the keys anyway
1019
		sort($accounts['groups']);
1020
		sort($accounts['accounts']);
1021
1022
		if (isset($cache))
1023
		{
1024
			$cache = $accounts;
0 ignored issues
show
Unused Code introduced by
The assignment to $cache is dead and can be removed.
Loading history...
1025
		}
1026
1027
		switch($use)
1028
		{
1029
			case 'both':
1030
				return $accounts;
1031
			case 'groups':
1032
				return $accounts['groups'];
1033
			case 'accounts':
1034
				return $accounts['accounts'];
1035
			case 'merge':
1036
				return array_merge($accounts['accounts'],$accounts['groups']);
1037
		}
1038
		return False;
1039
	}
1040
/**
1041
	 * Get a list of how many entries of each app the account has
1042
	 *
1043
	 * @param int $account_id
1044
	 *
1045
	 * @return array app => count
1046
	 */
1047
	public function get_account_entry_counts($account_id)
1048
	{
1049
		$owner_columns = static::get_owner_columns();
0 ignored issues
show
Bug Best Practice introduced by
The method EGroupware\Api\Accounts::get_owner_columns() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1049
		/** @scrutinizer ignore-call */ 
1050
  $owner_columns = static::get_owner_columns();
Loading history...
1050
1051
		$selects = array();
1052
1053
		foreach($owner_columns as $app => $column)
1054
		{
1055
			list($table, $column_name) = explode('.', $column['column']);
1056
			$select = array(
1057
				'table' => $table,
1058
				'cols'  => array(
1059
					"'$app' AS app",
1060
					'"total" AS type',
1061
					'count(' . $column['key'] . ') AS count'
1062
				),
1063
				'where' => array(
1064
					$column['column'] => (int)$account_id
1065
				),
1066
				'app'   => $app
1067
			);
1068
			switch($app)
1069
			{
1070
				case 'infolog':
1071
					$select['cols'][1] = 'info_type AS type';
1072
					$select['append'] = ' GROUP BY info_type';
1073
					break;
1074
			}
1075
			$selects[] = $select;
1076
		}
1077
1078
		$counts = array();
1079
		foreach($GLOBALS['egw']->db->union($selects, __LINE__ , __FILE__) as $row)
1080
		{
1081
			if(!is_array($counts[$row['app']]))
1082
			{
1083
				$counts[$row['app']] = array('total' => 0);
1084
			}
1085
			$counts[$row['app']][$row['type']] = $row['count'];
1086
			if($row['type'] != 'total')
1087
			{
1088
				$counts[$row['app']]['total'] += $row['count'];
1089
			}
1090
		}
1091
1092
		return $counts;
1093
	}
1094
	protected function get_owner_columns()
1095
	{
1096
		$owner_columns = array();
1097
		foreach($GLOBALS['egw_info']['apps'] as $appname => $app)
1098
		{
1099
			// Check hook
1100
			$owner_column = Link::get_registry($appname, 'owner');
1101
1102
			// Try for automatically finding the modification
1103
			if(!is_array($owner_column) && !in_array($appname, array('admin', 'api','etemplate','phpgwapi')))
1104
			{
1105
				$tables = $GLOBALS['egw']->db->get_table_definitions($appname);
1106
				if(!is_array($tables))
1107
				{
1108
					continue;
1109
				}
1110
				foreach($tables as $table_name => $table)
1111
				{
1112
					foreach($table['fd'] as $column_name => $column)
1113
					{
1114
						if((strpos($column_name, 'owner') !== FALSE || strpos($column_name, 'creator') !== FALSE) &&
1115
								($column['meta'] == 'account' || $column['meta'] == 'user')
1116
						)
1117
						{
1118
							$owner_column = array(
1119
								'key'    => $table_name . '.' . $table['pk'][0],
1120
								'column' => $table_name . '.' . $column_name,
1121
								'type' => $column['type']
1122
							);
1123
							break 2;
1124
						}
1125
					}
1126
				}
1127
			}
1128
			if($owner_column)
1129
			{
1130
				$owner_columns[$appname] = $owner_column;
1131
			}
1132
		}
1133
1134
		return $owner_columns;
1135
	}
1136
1137
	/**
1138
	 * Add an account for an authenticated user
1139
	 *
1140
	 * Expiration date and primary group are read from the system configuration.
1141
	 *
1142
	 * @param string $account_lid
1143
	 * @param string $passwd
1144
	 * @param array $GLOBALS['auto_create_acct'] values for 'firstname', 'lastname', 'email' and 'primary_group'
1145
	 * @return int|boolean account_id or false on error
1146
	 */
1147
	function auto_add($account_lid, $passwd)
1148
	{
1149
		$expires = !isset($this->config['auto_create_expire']) ||
1150
			$this->config['auto_create_expire'] == 'never' ? -1 :
1151
			time() + $this->config['auto_create_expire'] + 2;
1152
1153
		$memberships = array();
1154
		$default_group_id = null;
1155
		// check if we have a comma or semicolon delimited list of groups --> add first as primary and rest as memberships
1156
		foreach(preg_split('/[,;] */',$this->config['default_group_lid']) as $group_lid)
1157
		{
1158
			if (($group_id = $this->name2id($group_lid,'account_lid','g')))
1159
			{
1160
				if (!$default_group_id) $default_group_id = $group_id;
1161
				$memberships[] = $group_id;
1162
			}
1163
		}
1164
		if (!$default_group_id && ($default_group_id = $this->name2id('Default','account_lid','g')))
0 ignored issues
show
Bug Best Practice introduced by
The expression $default_group_id of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1165
		{
1166
			$memberships[] = $default_group_id;
1167
		}
1168
1169
		$primary_group = $GLOBALS['auto_create_acct']['primary_group'] &&
1170
			$this->get_type((int)$GLOBALS['auto_create_acct']['primary_group']) === 'g' ?
1171
			(int)$GLOBALS['auto_create_acct']['primary_group'] : $default_group_id;
1172
		if ($primary_group && !in_array($primary_group, $memberships))
1173
		{
1174
			$memberships[] = $primary_group;
1175
		}
1176
		$data = array(
1177
			'account_lid'           => $account_lid,
1178
			'account_type'          => 'u',
1179
			'account_passwd'        => $passwd,
1180
			'account_firstname'     => $GLOBALS['auto_create_acct']['firstname'] ? $GLOBALS['auto_create_acct']['firstname'] : 'New',
1181
			'account_lastname'      => $GLOBALS['auto_create_acct']['lastname'] ? $GLOBALS['auto_create_acct']['lastname'] : 'User',
1182
			'account_email'         => $GLOBALS['auto_create_acct']['email'],
1183
			'account_status'        => 'A',
1184
			'account_expires'       => $expires,
1185
			'account_primary_group' => $primary_group,
1186
		);
1187
		// use given account_id, if it's not already used
1188
		if (isset($GLOBALS['auto_create_acct']['account_id']) &&
1189
			is_numeric($GLOBALS['auto_create_acct']['account_id']) &&
1190
			!$this->id2name($GLOBALS['auto_create_acct']['account_id']))
1191
		{
1192
			$data['account_id'] = $GLOBALS['auto_create_acct']['account_id'];
1193
		}
1194
		if (!($data['account_id'] = $this->save($data)))
1195
		{
1196
			return false;
1197
		}
1198
		// set memberships if given
1199
		if ($memberships)
1200
		{
1201
			$this->set_memberships($memberships,$data['account_id']);
1202
		}
1203
		// set the appropriate value for the can change password flag (assume users can, if the admin requires users to change their password)
1204
		$data['changepassword'] = (bool)$GLOBALS['egw_info']['server']['change_pwd_every_x_days'];
1205
		if(!$data['changepassword'])
1206
		{
1207
			$GLOBALS['egw']->acl->add_repository('preferences','nopasswordchange',$data['account_id'],1);
1208
		}
1209
		else
1210
		{
1211
			$GLOBALS['egw']->acl->delete_repository('preferences','nopasswordchange',$data['account_id']);
1212
		}
1213
		// call hook to notify interested apps about the new account
1214
		$GLOBALS['hook_values'] = $data;
1215
		Hooks::process($data+array(
1216
			'location' => 'addaccount',
1217
			// at login-time only the hooks from the following apps will be called
1218
			'order' => array('felamimail','fudforum'),
1219
		),False,True);  // called for every app now, not only enabled ones
1220
		unset($data['changepassword']);
1221
1222
		return $data['account_id'];
1223
	}
1224
1225
	/**
1226
	 * Update the last login timestamps and the IP
1227
	 *
1228
	 * @param int $account_id
1229
	 * @param string $ip
1230
	 * @return int lastlogin time
1231
	 */
1232
	function update_lastlogin($account_id, $ip)
1233
	{
1234
		return $this->backend->update_lastlogin($account_id, $ip);
1235
	}
1236
1237
	/**
1238
	 * Query if backend allows to change username aka account_lid
1239
	 *
1240
	 * @return boolean false if backend does NOT allow it (AD), true otherwise (SQL, LDAP)
1241
	 */
1242
	function change_account_lid_allowed()
1243
	{
1244
		$change_account_lid = constant(get_class($this->backend).'::CHANGE_ACCOUNT_LID');
1245
		if (!isset($change_account_lid)) $change_account_lid = true;
1246
		return $change_account_lid;
1247
	}
1248
1249
	/**
1250
	 * Query if backend requires password to be set, before allowing to enable an account
1251
	 *
1252
	 * @return boolean true if backend requires a password (AD), false or null otherwise (SQL, LDAP)
1253
	 */
1254
	function require_password_for_enable()
1255
	{
1256
		return constant(get_class($this->backend).'::REQUIRE_PASSWORD_FOR_ENABLE');
1257
	}
1258
1259
	/**
1260
	 * Invalidate cache (or parts of it) after change in $account_ids
1261
	 *
1262
	 * We use now an instance-wide read-cache storing account-data and members(hips).
1263
	 *
1264
	 * @param int|array $account_ids user- or group-id(s) for which cache should be invalidated, default 0 = only search/name2id cache
1265
	 */
1266
	static function cache_invalidate($account_ids=0)
1267
	{
1268
		//error_log(__METHOD__.'('.array2string($account_ids).')');
1269
1270
		// instance-wide cache
1271
		if ($account_ids)
1272
		{
1273
			$instance = self::getInstance();
1274
1275
			foreach((array)$account_ids as $account_id)
1276
			{
1277
				Cache::unsetCache($instance->config['install_id'], __CLASS__, 'account-'.$account_id);
1278
1279
				unset(self::$request_cache[$account_id]);
1280
			}
1281
		}
1282
		else
1283
		{
1284
			self::$request_cache = array();
1285
		}
1286
1287
		// session-cache
1288
		if (self::$cache) self::$cache = array();
0 ignored issues
show
Bug Best Practice introduced by
The expression self::cache of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1289
		Cache::unsetSession('accounts_cache','phpgwapi');
1290
1291
		if (method_exists($GLOBALS['egw'],'invalidate_session_cache'))	// egw object in setup is limited
1292
		{
1293
			Egw::invalidate_session_cache();	// invalidates whole egw-enviroment if stored in the session
1294
		}
1295
	}
1296
1297
	/**
1298
	 * Timeout of instance wide cache for reading account-data and members(hips)
1299
	 */
1300
	const READ_CACHE_TIMEOUT = 43200;
1301
1302
	/**
1303
	 * Local per request cache, to minimize calls to instance cache
1304
	 *
1305
	 * @var array
1306
	 */
1307
	static $request_cache = array();
1308
1309
	/**
1310
	 * Read account incl. members/memberships from cache (or backend and cache it)
1311
	 *
1312
	 * @param int $account_id
1313
	 * @param boolean $need_active =false true = 'members-active' required
1314
	 * @return array
1315
	 * @throws Exception\WrongParameter if no integer was passed as $account_id
1316
	 */
1317
	static function cache_read($account_id, $need_active=false)
1318
	{
1319
		if (!is_numeric($account_id)) throw new Exception\WrongParameter('Not an integer!');
0 ignored issues
show
introduced by
The condition is_numeric($account_id) is always true.
Loading history...
1320
1321
		$account =& self::$request_cache[$account_id];
1322
1323
		if (!isset($account))	// not in request cache --> try instance cache
1324
		{
1325
			$instance = self::getInstance();
1326
1327
			$account = Cache::getCache($instance->config['install_id'], __CLASS__, 'account-'.$account_id);
1328
1329
			if (!isset($account))	// not in instance cache --> read from backend
1330
			{
1331
				if (($account = $instance->backend->read($account_id)))
1332
				{
1333
					if ($instance->get_type($account_id) == 'u')
1334
					{
1335
						if (!isset($account['memberships'])) $account['memberships'] = $instance->backend->memberships($account_id);
1336
					}
1337
					else
1338
					{
1339
						if (!isset($account['members'])) $account['members'] = $instance->backend->members($account_id);
1340
					}
1341
					Cache::setCache($instance->config['install_id'], __CLASS__, 'account-'.$account_id, $account, self::READ_CACHE_TIMEOUT);
1342
				}
1343
				//error_log(__METHOD__."($account_id) read from backend ".array2string($account));
1344
			}
1345
			//else error_log(__METHOD__."($account_id) read from instance cache ".array2string($account));
1346
		}
1347
		// if required and not already set, query active members AND cache them too
1348
		if ($need_active && $account_id < 0 && !isset($account['members-active']))
1349
		{
1350
			$instance = self::getInstance();
1351
			$account['members-active'] = array();
1352
			foreach((array)$account['members'] as $id => $lid)
1353
			{
1354
				if ($instance->is_active($id)) $account['members-active'][$id] = $lid;
1355
			}
1356
			Cache::setCache($instance->config['install_id'], __CLASS__, 'account-'.$account_id, $account, self::READ_CACHE_TIMEOUT);
1357
		}
1358
		//error_log(__METHOD__."($account_id, $need_active) returning ".array2string($account));
1359
		return $account;
1360
	}
1361
1362
	/**
1363
	 * Internal functions not meant to use outside this class!!!
1364
	 */
1365
1366
	/**
1367
	 * Sets up session cache, now only used for search and name2id list
1368
	 *
1369
	 * Other account-data is cached on instance-level
1370
	 *
1371
	 * The cache is shared between all instances of the account-class and it can be save in the session,
1372
	 * if use_session_cache is set to True
1373
	 *
1374
	 * @internal
1375
	 */
1376
	private static function setup_cache()
1377
	{
1378
		if (is_array(self::$cache)) return;	// cache is already setup
0 ignored issues
show
introduced by
The condition is_array(self::cache) is always true.
Loading history...
1379
1380
		if (self::$use_session_cache && is_object($GLOBALS['egw']->session))
1381
		{
1382
			self::$cache =& Cache::getSession('accounts_cache','phpgwapi');
1383
		}
1384
		//error_log(__METHOD__."() use_session_cache=".array2string(self::$use_session_cache).", is_array(self::\$cache)=".array2string(is_array(self::$cache)));
1385
1386
		if (!is_array(self::$cache))
1387
		{
1388
			self::$cache = array();
1389
		}
1390
	}
1391
1392
	public function __destruct() {
1393
		if (self::$_instance === $this)
1394
		{
1395
			self::$_instance = NULL;
1396
		}
1397
	}
1398
}
1399