Issues (4868)

api/src/Accounts/Sql.php (15 issues)

1
<?php
2
/**
3
 * API - accounts SQL backend
4
 *
5
 * The SQL backend stores the group memberships via the ACL class (location 'phpgw_group')
6
 *
7
 * The (positive) account_id's of groups are mapped in this class to negative numeric
8
 * account_id's, to conform with the way we handle groups in LDAP!
9
 *
10
 * @link http://www.egroupware.org
11
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de> complete rewrite in 6/2006 and
12
 * 	earlier to use the new DB functions
13
 *
14
 * This class replaces the former accounts_sql class written by
15
 * Joseph Engo <[email protected]>, Dan Kuykendall <[email protected]>
16
 * and Bettina Gille <[email protected]>.
17
 * Copyright (C) 2000 - 2002 Joseph Engo
18
 * Copyright (C) 2003 Lars Kneschke, Bettina Gille
19
 *
20
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
21
 * @package api
22
 * @subpackage accounts
23
 * @version $Id$
24
 */
25
26
namespace EGroupware\Api\Accounts;
27
28
use EGroupware\Api;
29
30
/**
31
 * SQL Backend for accounts
32
 *
33
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
34
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
35
 * @access internal only use the interface provided by the accounts class
36
 */
37
class Sql
38
{
39
	/**
40
	 * instance of the db class
41
	 *
42
	 * @var Api\Db
43
	 */
44
	var $db;
45
	/**
46
	 * table name for the accounts
47
	 *
48
	 * @var string
49
	 */
50
	const TABLE = 'egw_accounts';
51
	var $table = self::TABLE;
52
	/**
53
	 * table name for the contacts
54
	 *
55
	 * @var string
56
	 */
57
	var $contacts_table = 'egw_addressbook';
58
	/**
59
	 * Join with the accounts-table used in contacts::search
60
	 *
61
	 * @var string
62
	 */
63
	var $contacts_join = ' RIGHT JOIN egw_accounts ON egw_accounts.account_id=egw_addressbook.account_id';
64
	/**
65
	 * total number of found entries from get_list method
66
	 *
67
	 * @var int
68
	 */
69
	var $total;
70
71
	/**
72
	 * Reference to our frontend
73
	 *
74
	 * @var accounts
0 ignored issues
show
The type EGroupware\Api\Accounts\accounts 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...
75
	 */
76
	private $frontend;
77
78
	/**
79
	 * Instance of contacts object, NOT automatic instanciated!
80
	 *
81
	 * @var Api\Contacts
82
	 */
83
	private $contacts;
84
85
	/**
86
	 * does backend allow to change account_lid
87
	 */
88
	const CHANGE_ACCOUNT_LID = true;
89
90
	/**
91
	 * does backend requires password to be set, before allowing to enable an account
92
	 */
93
	const REQUIRE_PASSWORD_FOR_ENABLE = false;
94
95
	/**
96
	 * Constructor
97
	 *
98
	 * @param Api\Accounts $frontend reference to the frontend class, to be able to call it's methods if needed
99
	 */
100
	function __construct(Api\Accounts $frontend)
101
	{
102
		$this->frontend = $frontend;
0 ignored issues
show
Documentation Bug introduced by
It seems like $frontend of type EGroupware\Api\Accounts is incompatible with the declared type EGroupware\Api\Accounts\accounts of property $frontend.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
103
104
		if (is_object($GLOBALS['egw_setup']->db))
105
		{
106
			$this->db = $GLOBALS['egw_setup']->db;
107
		}
108
		else
109
		{
110
			$this->db = $GLOBALS['egw']->db;
111
		}
112
	}
113
114
	/**
115
	 * Reads the data of one account
116
	 *
117
	 * For performance reasons and because the contacts-object itself depends on the accounts-object,
118
	 * we directly join with the contacts table for reading!
119
	 *
120
	 * @param int $account_id numeric account-id
121
	 * @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...
122
	 */
123
	function read($account_id)
124
	{
125
		if (!(int)$account_id) return false;
126
127
		if ($account_id > 0)
128
		{
129
			$extra_cols = $this->contacts_table.'.n_given AS account_firstname,'.
130
				$this->contacts_table.'.n_family AS account_lastname,'.
131
				$this->contacts_table.'.contact_email AS account_email,'.
132
				$this->contacts_table.'.n_fn AS account_fullname,'.
133
				$this->contacts_table.'.contact_id AS person_id,'.
134
				$this->contacts_table.'.contact_created AS account_created,'.
135
				$this->contacts_table.'.contact_modified AS account_modified,'.
136
				$this->contacts_table.'.tel_work AS account_phone,';
137
				$join = 'LEFT JOIN '.$this->contacts_table.' ON '.$this->table.'.account_id='.$this->contacts_table.'.account_id';
138
		}
139
		// during setup emailadmin might not yet be installed and running below query
140
		// will abort transaction in PostgreSQL
141
		elseif (!isset($GLOBALS['egw_setup']) || in_array(Api\Mail\Smtp\Sql::TABLE, $this->db->table_names(true)))
142
		{
143
			$extra_cols = Api\Mail\Smtp\Sql::TABLE.'.mail_value AS account_email,';
144
			$join = 'LEFT JOIN '.Api\Mail\Smtp\Sql::TABLE.' ON '.$this->table.'.account_id=-'.Api\Mail\Smtp\Sql::TABLE.'.account_id AND mail_type='.Api\Mail\Smtp\Sql::TYPE_ALIAS;
145
		}
146
		try {
147
			$rs = $this->db->select($this->table, $extra_cols.$this->table.'.*',
148
				$this->table.'.account_id='.abs($account_id),
149
				__LINE__, __FILE__, false, '', false, 0, $join);
150
		}
151
		catch (Api\Db\Exception $e) {
152
			unset($e);
153
		}
154
155
		if (!$rs)	// handle not (yet) existing mailaccounts table
0 ignored issues
show
$rs is of type EGroupware\Api\ADORecordSet, thus it always evaluated to true.
Loading history...
156
		{
157
			$rs = $this->db->select($this->table, $this->table.'.*',
158
				$this->table.'.account_id='.abs($account_id), __LINE__, __FILE__);
159
		}
160
		if (!$rs || !($data = $rs->fetch()))
0 ignored issues
show
$rs is of type EGroupware\Api\ADORecordSet, thus it always evaluated to true.
Loading history...
161
		{
162
			return false;
163
		}
164
		if ($data['account_type'] == 'g')
165
		{
166
			$data['account_id'] = -$data['account_id'];
167
			$data['mailAllowed'] = true;
168
		}
169
		if (!$data['account_firstname']) $data['account_firstname'] = $data['account_lid'];
170
		if (!$data['account_lastname'])
171
		{
172
			$data['account_lastname'] = $data['account_type'] == 'g' ? 'Group' : 'User';
173
			// if we call lang() before the translation-class is correctly setup,
174
			// we can't switch away from english language anymore!
175
			if (Api\Translation::$lang_arr)
0 ignored issues
show
Bug Best Practice introduced by
The expression EGroupware\Api\Translation::lang_arr 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...
176
			{
177
				$data['account_lastname'] = lang($data['account_lastname']);
178
			}
179
		}
180
		if (!$data['account_fullname']) $data['account_fullname'] = $data['account_firstname'].' '.$data['account_lastname'];
181
182
		return $data;
183
	}
184
185
	/**
186
	 * Saves / adds the data of one account
187
	 *
188
	 * If no account_id is set in data the account is added and the new id is set in $data.
189
	 *
190
	 * @param array $data array with account-data
191
	 * @return int/boolean the account_id or false on error
0 ignored issues
show
Documentation Bug introduced by
The doc comment int/boolean at position 0 could not be parsed: Unknown type name 'int/boolean' at position 0 in int/boolean.
Loading history...
192
	 */
193
	function save(&$data)
194
	{
195
		$to_write = $data;
196
		unset($to_write['account_passwd']);
197
		// encrypt password if given or unset it if not
198
		if ($data['account_passwd'])
199
		{
200
			// if password it's not already entcrypted, do so now
201
			if (!preg_match('/^\\{[a-z5]{3,5}\\}.+/i',$data['account_passwd']) &&
202
				!preg_match('/^[0-9a-f]{32}$/',$data['account_passwd']))	// md5 hash
203
			{
204
				$data['account_passwd'] = Api\Auth::encrypt_sql($data['account_passwd']);
205
			}
206
			$to_write['account_pwd'] = $data['account_passwd'];
207
			$to_write['account_lastpwd_change'] = time();
208
		}
209
		if ($data['mustchangepassword'] == 1) $to_write['account_lastpwd_change']=0;
210
		if (!(int)$data['account_id'] || !$this->id2name($data['account_id']))
211
		{
212
			if ($to_write['account_id'] < 0) $to_write['account_id'] *= -1;
213
214
			if (!isset($to_write['account_pwd'])) $to_write['account_pwd'] = '';	// is NOT NULL!
215
			if (!isset($to_write['account_status'])) $to_write['account_status'] = '';	// is NOT NULL!
216
217
			// postgres requires the auto-id field to be unset!
218
			if (isset($to_write['account_id']) && !$to_write['account_id']) unset($to_write['account_id']);
219
220
			if (!in_array($to_write['account_type'],array('u','g')) ||
221
				!$this->db->insert($this->table,$to_write,false,__LINE__,__FILE__)) return false;
222
223
			if (!(int)$data['account_id'])
224
			{
225
				$data['account_id'] = $this->db->get_last_insert_id($this->table,'account_id');
226
				if ($data['account_type'] == 'g') $data['account_id'] *= -1;
227
			}
228
		}
229
		else	// update of existing account
230
		{
231
			unset($to_write['account_id']);
232
			if (!$this->db->update($this->table,$to_write,array('account_id' => abs($data['account_id'])),__LINE__,__FILE__))
233
			{
234
				return false;
235
			}
236
		}
237
		// store group-email in mailaccounts table
238
		if ($data['account_id'] < 0 && class_exists('EGroupware\\Api\\Mail\\Smtp\\Sql', isset($data['account_email'])))
239
		{
240
			try {
241
				if (isset($GLOBALS['egw_setup']) && !in_array(Api\Mail\Smtp\Sql::TABLE, $this->db->table_names(true)))
242
				{
243
					// cant store email, if table not yet exists
244
				}
245
				elseif (empty($data['account_email']))
246
				{
247
					$this->db->delete(Api\Mail\Smtp\Sql::TABLE, array(
248
						'account_id' => $data['account_id'],
249
						'mail_type' => Api\Mail\Smtp\Sql::TYPE_ALIAS,
250
					), __LINE__, __FILE__, Api\Mail\Smtp\Sql::APP);
251
				}
252
				else
253
				{
254
					$this->db->insert(Api\Mail\Smtp\Sql::TABLE, array(
255
						'mail_value' => $data['account_email'],
256
					), array(
257
						'account_id' => $data['account_id'],
258
						'mail_type' => Api\Mail\Smtp\Sql::TYPE_ALIAS,
259
					), __LINE__, __FILE__, Api\Mail\Smtp\Sql::APP);
260
				}
261
			}
262
			// ignore not (yet) existing mailaccounts table (does NOT work in PostgreSQL, because of transaction!)
263
			catch (Api\Db\Exception $e) {
264
				unset($e);
265
			}
266
		}
267
		return $data['account_id'];
268
	}
269
270
	/**
271
	 * Delete one account, deletes also all acl-entries for that account
272
	 *
273
	 * @param int $account_id numeric account_id
274
	 * @return boolean true on success, false otherwise
275
	 */
276
	function delete($account_id)
277
	{
278
		if (!(int)$account_id) return false;
279
280
		$contact_id = $this->id2name($account_id,'person_id');
281
282
		if (!$this->db->delete($this->table,array('account_id' => abs($account_id)),__LINE__,__FILE__))
283
		{
284
			return false;
285
		}
286
		if ($contact_id)
287
		{
288
			if (!isset($this->contacts)) $this->contacts = new Api\Contacts();
289
			$this->contacts->delete($contact_id,false);	// false = allow to delete accounts (!)
290
		}
291
		return true;
292
	}
293
294
	/**
295
	 * Get all memberships of an account $accountid / groups the account is a member off
296
	 *
297
	 * @param int $account_id numeric account-id
298
	 * @return array/boolean array with account_id => account_lid pairs 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...
299
	 */
300
	function memberships($account_id)
301
	{
302
		if (!(int)$account_id) return false;
303
304
		$memberships = array();
305
		if(($gids = $GLOBALS['egw']->acl->get_location_list_for_id('phpgw_group', 1, $account_id)))
306
		{
307
			foreach($gids as $gid)
308
			{
309
				$memberships[(string) $gid] = $this->id2name($gid);
310
			}
311
		}
312
		return $memberships;
313
	}
314
315
	/**
316
	 * Sets the memberships of the account this class is instanciated for
317
	 *
318
	 * @param array $groups array with gidnumbers
319
	 * @param int $account_id numerical account-id
320
	 */
321
	function set_memberships($groups,$account_id)
322
	{
323
		if (!(int)$account_id) return;
324
325
		$acl = new Api\Acl($account_id);
326
		$acl->read_repository();
327
		$acl->delete('phpgw_group',false);
328
329
		foreach($groups as $group)
330
		{
331
			$acl->add('phpgw_group',$group,1);
332
		}
333
		$acl->save_repository();
334
	}
335
336
	/**
337
	 * Get all members of the group $accountid
338
	 *
339
	 * @param int/string $account_id numeric account-id
0 ignored issues
show
Documentation Bug introduced by
The doc comment int/string at position 0 could not be parsed: Unknown type name 'int/string' at position 0 in int/string.
Loading history...
340
	 * @return array with account_id => account_lid pairs
341
	 */
342
	function members($account_id)
343
	{
344
		if (!is_numeric($account_id)) $account_id = $this->name2id($account_id);
345
346
		$members = array();
347
		foreach($this->db->select($this->table, 'account_id,account_lid',
348
			$this->db->expression(Api\Acl::TABLE, array(
349
				'acl_appname'  => 'phpgw_group',
350
				'acl_location' => $account_id,
351
			)), __LINE__, __FILE__, false, '', false, 0,
352
			'JOIN '.Api\Acl::TABLE.' ON account_id=acl_account'
353
		) as $row)
354
		{
355
			$members[$row['account_id']] = $row['account_lid'];
356
		}
357
		return $members;
358
	}
359
360
	/**
361
	 * Set the members of a group
362
	 *
363
	 * @param array $members array with uidnumber or uid's
364
	 * @param int $gid gidnumber of group to set
365
	 */
366
	function set_members($members,$gid)
367
	{
368
		$GLOBALS['egw']->acl->delete_repository('phpgw_group',$gid,false);
369
370
		if (is_array($members))
0 ignored issues
show
The condition is_array($members) is always true.
Loading history...
371
		{
372
			foreach($members as $id)
373
			{
374
				$GLOBALS['egw']->acl->add_repository('phpgw_group',$gid,$id,1);
375
			}
376
		}
377
	}
378
379
	/**
380
	 * Searches / lists accounts: users and/or groups
381
	 *
382
	 * @param array with the following keys:
383
	 * @param $param['type'] string/int 'accounts', 'groups', 'owngroups' (groups the user is a member of), 'both',
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/int at position 0 could not be parsed: Unknown type name 'string/int' at position 0 in string/int.
Loading history...
384
	 * 'groupmember' or 'groupmembers+memberships'
385
	 *	or integer group-id for a list of members of that group
386
	 * @param $param['start'] int first account to return (returns offset or max_matches entries) or all if not set
387
	 * @param $param['order'] string column to sort after, default account_lid if unset
388
	 * @param $param['sort'] string 'ASC' or 'DESC', default 'ASC' if not set
389
	 * @param $param['query'] string to search for, no search if unset or empty
390
	 * @param $param['query_type'] string:
391
	 *	'all'   - query all fields for containing $param[query]
392
	 *	'start' - query all fields starting with $param[query]
393
	 *	'exact' - query all fields for exact $param[query]
394
	 *	'lid','firstname','lastname','email' - query only the given field for containing $param[query]
395
	 * @param $param['offset'] int - number of matches to return if start given, default use the value in the prefs
396
	 * @param $param['objectclass'] boolean return objectclass(es) under key 'objectclass' in each account
397
	 * @return array with account_id => data pairs, data is an array with account_id, account_lid, account_firstname,
398
	 *	account_lastname, person_id (id of the linked addressbook entry), account_status, account_expires, account_primary_group
399
	 */
400
	function search($param)
401
	{
402
		static $order2contact = array(
403
			'account_firstname' => 'n_given',
404
			'account_lastname'  => 'n_family',
405
			'account_email'     => 'contact_email',
406
		);
407
408
		// fetch order of account_fullname from Api\Accounts::format_username
409
		if (strpos($param['order'],'account_fullname') !== false)
410
		{
411
			$param['order'] = str_replace('account_fullname', preg_replace('/[ ,]+/',',',str_replace(array('[',']'),'',
412
				Api\Accounts::format_username('account_lid','account_firstname','account_lastname'))), $param['order']);
413
		}
414
		$order = str_replace(array_keys($order2contact),array_values($order2contact),$param['order']);
415
416
		// allways add 'account_lid'
417
		if (strpos($order, 'account_lid') === false)
418
		{
419
			$order .= ($order?',':'').'account_lid';
420
		}
421
		if ($param['sort']) $order = implode(' '.$param['sort'].',', explode(',', $order)).' '.$param['sort'];
422
423
		$search_cols = array('account_lid','n_family','n_given','email');
424
		$join = $this->contacts_join;
425
		$email_cols = array('email');
426
427
		// Add in group email searching
428
		if (!isset($GLOBALS['egw_setup']) || in_array(Api\Mail\Smtp\Sql::TABLE, $this->db->table_names(true)))
429
		{
430
			$email_cols = array('coalesce('.$this->contacts_table.'.contact_email,'.Api\Mail\Smtp\Sql::TABLE.'.mail_value) as email');
431
			if ($this->db->Type == 'mysql' && !preg_match('/[\x80-\xFF]/', $param['query']))
432
			{
433
				$search_cols[] = Api\Mail\Smtp\Sql::TABLE.'.mail_value';
434
			}
435
			$join .= ' LEFT JOIN '.Api\Mail\Smtp\Sql::TABLE.' ON '.$this->table.'.account_id=-'.Api\Mail\Smtp\Sql::TABLE.'.account_id AND mail_type='.Api\Mail\Smtp\Sql::TYPE_ALIAS;
436
		}
437
438
		$filter = array();
439
		switch($param['type'])
440
		{
441
			case 'accounts':
442
				$filter['owner'] = 0;
443
				break;
444
			case 'groups':
445
				$filter[] = "account_type='g'";
446
				break;
447
			case 'owngroups':
448
				$filter['account_id'] = array_map('abs', $this->frontend->memberships($GLOBALS['egw_info']['user']['account_id'], true));
449
				$filter[] = "account_type='g'";
450
				break;
451
			case 'groupmembers':
452
			case 'groupmembers+memberships':
453
				$members = array();
454
				foreach((array)$this->memberships($GLOBALS['egw_info']['user']['account_id'], true) as $grp => $name)
0 ignored issues
show
The call to EGroupware\Api\Accounts\Sql::memberships() has too many arguments starting with true. ( Ignorable by Annotation )

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

454
				foreach((array)$this->/** @scrutinizer ignore-call */ memberships($GLOBALS['egw_info']['user']['account_id'], true) as $grp => $name)

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
455
				{
456
					unset($name);
457
					$members = array_unique(array_merge($members, array_keys((array)$this->members($grp))));
458
					if ($param['type'] == 'groupmembers+memberships') $members[] = abs($grp);
459
				}
460
				$filter['account_id'] = $members;
461
				break;
462
			default:
463
				if (is_numeric($param['type']))
464
				{
465
					$filter['account_id'] = $this->frontend->members($param['type'], true, $param['active']);
466
					$filter['owner'] = 0;
467
					break;
468
				}
469
				// fall-through
470
			case 'both':
471
				$filter[] = "(egw_addressbook.contact_owner=0 OR egw_addressbook.contact_owner IS NULL)";
472
				break;
473
		}
474
		// fix ambigous account_id (used in accounts and contacts table)
475
		if (array_key_exists('account_id', $filter))
476
		{
477
			if (!$filter['account_id'])	// eg. group without members (would give SQL error)
478
			{
479
				$this->total = 0;
480
				return array();
481
			}
482
			$filter[] = $this->db->expression($this->table, $this->table.'.', array(
483
				'account_id' => $filter['account_id'],
484
			));
485
			unset($filter['account_id']);
486
		}
487
		if ($param['active'])
488
		{
489
			$filter[] = str_replace('UNIX_TIMESTAMP(NOW())',time(),Api\Contacts\Sql::ACOUNT_ACTIVE_FILTER);
490
		}
491
		$criteria = array();
492
		$wildcard = $param['query_type'] == 'start' || $param['query_type'] == 'exact' ? '' : '%';
493
		if (($query = $param['query']))
494
		{
495
			switch($param['query_type'])
496
			{
497
				case 'start':
498
					$query .= '*';
499
					// fall-through
500
				case 'all':
501
				default:
502
				case 'exact':
503
					foreach($search_cols as $col)
504
					{
505
						$criteria[$col] = $query;
506
					}
507
					break;
508
				case 'account_firstname':
509
				case 'firstname':
510
					$criteria['n_given'] = $query;
511
					break;
512
				case 'account_lastname':
513
				case 'lastname':
514
					$criteria['n_family'] = $query;
515
					break;
516
				case 'account_lid':
517
				case 'lid':
518
					$criteria['account_lid'] = $query;
519
					break;
520
				case 'account_email':
521
				case 'email':
522
					$criteria['email'] = $query;
523
					// Group email
524
					if(in_array(Api\Mail\Smtp\Sql::TABLE, $this->db->table_names(true)))
525
					{
526
						$criteria[Api\Mail\Smtp\Sql::TABLE.'.mail_value'] = $query;
527
					}
528
					break;
529
			}
530
		}
531
		if (!isset($this->contacts)) $this->contacts = new Api\Contacts();
532
533
		$accounts = array();
534
		foreach((array) $this->contacts->search($criteria,
535
			array_merge(array(1,'n_given','n_family','id','created','modified',$this->table.'.account_id AS account_id'),$email_cols),
536
			$order, "account_lid,account_type,account_status,account_expires,account_primary_group,account_description".
537
			",account_lastlogin,account_lastloginfrom,account_lastpwd_change",
538
			$wildcard,false,$query[0] == '!' ? 'AND' : 'OR',
539
			$param['offset'] ? array($param['start'], $param['offset']) : (is_null($param['start']) ? false : $param['start']),
540
			$filter,$join) as $contact)
541
		{
542
			if ($contact)
543
			{
544
				$account_id = ($contact['account_type'] == 'g' ? -1 : 1) * $contact['account_id'];
545
				$accounts[$account_id] = array(
546
					'account_id'        => $account_id,
547
					'account_lid'       => $contact['account_lid'],
548
					'account_type'      => $contact['account_type'],
549
					'account_firstname' => $contact['n_given'],
550
					'account_lastname'  => $contact['n_family'],
551
					'account_email'     => $contact['email'],
552
					'person_id'         => $contact['id'],
553
					'account_status'	=> $contact['account_status'],
554
					'account_expires'	=> $contact['account_expires'],
555
					'account_primary_group'	=> $contact['account_primary_group'],
556
					// Api\Contacts::search() returns everything in user-time, need to convert to server-time
557
					'account_created'	=> Api\DateTime::user2server($contact['created']),
558
					'account_modified'	=> Api\DateTime::user2server($contact['modified']),
559
					'account_lastlogin'	=> $contact['account_lastlogin'] ?
560
						Api\DateTime::user2server($contact['account_lastlogin']) : null,
561
					'account_lastloginfrom'	=> $contact['account_lastloginfrom'],
562
					'account_lastpwd_change'	=> $contact['account_lastpwd_change'] ?
563
						Api\DateTime::user2server($contact['account_lastpwd_change']) : null,
564
					'account_description' => $contact['account_description'],
565
				);
566
			}
567
		}
568
		$this->total = $this->contacts->total;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->contacts->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...
569
		//error_log(__METHOD__."(".array2string($param).") returning ".count($accounts).'/'.$this->total);
570
		return $accounts;
571
	}
572
573
	/**
574
	 * convert an alphanumeric account-value (account_lid, account_email, account_fullname) to the account_id
575
	 *
576
	 * Please note:
577
	 * - if a group and an user have the same account_lid the group will be returned (LDAP only)
578
	 * - if multiple user have the same email address, the returned user is undefined
579
	 *
580
	 * @param string $name value to convert
581
	 * @param string $which ='account_lid' type of $name: account_lid (default), account_email, person_id, account_fullname
582
	 * @param string $account_type u = user, g = group, default null = try both
583
	 * @return int/false numeric account_id or false on error ($name not found)
0 ignored issues
show
Documentation Bug introduced by
The doc comment int/false at position 0 could not be parsed: Unknown type name 'int/false' at position 0 in int/false.
Loading history...
584
	 */
585
	function name2id($name,$which='account_lid',$account_type=null)
586
	{
587
		if ($account_type === 'g' && $which != 'account_lid') return false;
588
589
		$where = array();
590
		$cols = 'account_id';
591
		switch($which)
592
		{
593
			case 'account_fullname':
594
				$table = $this->contacts_table;
595
				$where['n_fn'] = $name;
596
				break;
597
			case 'account_email':
598
				$table = $this->contacts_table;
599
				$where['contact_email'] = $name;
600
				break;
601
			case 'person_id':
602
				$table = $this->contacts_table;
603
				$where['contact_id'] = $name;
604
				break;
605
			default:
606
				$table = $this->table;
607
				$cols .= ',account_type';
608
				$where[$which] = $name;
609
				// check if we need to treat username case-insensitive
610
				if ($which == 'account_lid' && !$GLOBALS['egw_info']['server']['case_sensitive_username'])	// = is case sensitiv eg. on postgres, but not on mysql!
611
				{
612
					$where[] = 'account_lid '.$this->db->capabilities[Api\Db::CAPABILITY_CASE_INSENSITIV_LIKE].' '.$this->db->quote($where['account_lid']);
613
					unset($where['account_lid']);
614
				}
615
		}
616
		if ($account_type)
617
		{
618
			$where['account_type'] = $account_type;
619
		}
620
		else
621
		{
622
			$where[] = 'account_id IS NOT NULL'.	// otherwise contacts with eg. the same email hide the accounts!
623
				($table == $this->contacts_table ? " AND contact_tid != 'D'" : '');	// ignore deleted accounts contact-data
624
625
		}
626
		if (!($rs = $this->db->select($table,$cols,$where,__LINE__,__FILE__)) || !($row = $rs->fetch()))
627
		{
628
			//error_log(__METHOD__."('$name', '$which', ".array2string($account_type).") db->select('$table', '$cols', ".array2string($where).") returned ".array2string($rs).' '.function_backtrace());
629
			return false;
630
		}
631
		return ($row['account_type'] == 'g' ? -1 : 1) * $row['account_id'];
632
	}
633
634
	/**
635
	 * Convert an numeric account_id to any other value of that account (account_lid, account_email, ...)
636
	 *
637
	 * Uses the read method to fetch all data.
638
	 *
639
	 * @param int $account_id numerica account_id
640
	 * @param string $which ='account_lid' type to convert to: account_lid (default), account_email, ...
641
	 * @return string/false converted value or false on error ($account_id 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...
642
	 */
643
	function id2name($account_id,$which='account_lid')
644
	{
645
		return $this->frontend->id2name($account_id,$which);
646
	}
647
648
	/**
649
	 * Update the last login timestamps and the IP
650
	 *
651
	 * @param int $account_id
652
	 * @param string $ip
653
	 * @return int lastlogin time
654
	 */
655
	function update_lastlogin($account_id, $ip)
656
	{
657
		$previous_login = $this->db->select($this->table,'account_lastlogin',array('account_id'=>abs($account_id)),__LINE__,__FILE__)->fetchColumn();
658
659
		$this->db->update($this->table,array(
660
			'account_lastloginfrom' => $ip,
661
			'account_lastlogin'     => time(),
662
		),array(
663
			'account_id' => abs($account_id),
664
		),__LINE__,__FILE__);
665
666
		return $previous_login;
667
	}
668
}
669