admin_cmd_edit_user   F
last analyzed

Complexity

Total Complexity 70

Size/Duplication

Total Lines 256
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 109
dl 0
loc 256
rs 2.8
c 0
b 0
f 0
wmc 70

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __tostring() 0 4 3
A __construct() 0 14 3
A get_etemplate_name() 0 3 1
A _parse_expired() 0 13 5
F exec() 0 161 57
A get_change_labels() 0 8 1

How to fix   Complexity   

Complex Class

Complex classes like admin_cmd_edit_user often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use admin_cmd_edit_user, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * EGgroupware admin - admin command: edit/add a user
4
 *
5
 * @link http://www.egroupware.org
6
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
7
 * @package admin
8
 * @copyright (c) 2007-18 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
9
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10
 */
11
12
use EGroupware\Api;
13
14
/**
15
 * admin command: edit/add a user
16
 */
17
class admin_cmd_edit_user extends admin_cmd_change_pw
18
{
19
	/**
20
	 * Constructor
21
	 *
22
	 * @param string|int|array $account account name or id (!$account to add a new account), or array with all parameters
23
	 * @param array $set =null array with all data to change
24
	 * @param string $password =null password
25
	 * @param boolean $run_addaccount_hook =null default run addaccount for new Api\Accounts and editaccount for existing ones
26
	 * @param array $old =null array to log old values of $set
27
	 */
28
	function __construct($account, $set=null, $password=null, $run_addaccount_hook=null, array $old=null)
29
	{
30
		if (!is_array($account))
31
		{
32
			//error_log(__METHOD__."(".array2string($account).', '.array2string($set).", ...)");
33
			$account = array(
34
				'account' => $account,
35
				'set' => $set,
36
				'password' => is_null($password) ? $set['account_passwd'] : $password,
37
				'run_addaccount_hook' => $run_addaccount_hook,
38
				'old' => $old,
39
			);
40
		}
41
		admin_cmd::__construct($account);
42
	}
43
44
	/**
45
	 * change the account of a given user
46
	 *
47
	 * @param boolean $check_only =false only run the checks (and throw the exceptions), but not the command itself
48
	 * @return string success message
49
	 * @throws Api\Exception\NoPermission\Admin
50
	 * @throws Api\Exception\WrongUserinput(lang("Unknown account: %1 !!!",$this->account),15);
51
	 * @throws Api\Exception\WrongUserinput(lang('Error changing the password for %1 !!!',$this->account),99);
52
	 */
53
	protected function exec($check_only=false)
54
	{
55
		// check creator is still admin and not explicitly forbidden to edit accounts/groups
56
		if ($this->creator) $this->_check_admin('account_access',$this->account ? 16 : 4);
57
58
		admin_cmd::_instanciate_accounts();
59
60
		$data = $this->set;
61
		$data['account_type'] = 'u';
62
63
		if ($this->account)	// existing account
64
		{
65
			$data['account_id'] = admin_cmd::parse_account($this->account);
66
			//error_log(__METHOD__."($check_only) this->account=".array2string($this->account).', data[account_id]='.array2string($data['account_id']).", ...)");
67
68
			$data['old_loginid'] = admin_cmd::$accounts->id2name($data['account_id']);
69
		}
70
		if (!$data['account_lid'] && (!$this->account || !is_null($data['account_lid'])))
71
		{
72
			throw new Api\Exception\WrongUserinput(lang('You must enter a loginid'),9);
73
		}
74
		// Check if an account already exists as system user, and if it does deny creation
75
		if ($GLOBALS['egw_info']['server']['account_repository'] == 'ldap' &&
76
			!$GLOBALS['egw_info']['server']['ldap_allow_systemusernames'] && !$data['account_id'] &&
77
			function_exists('posix_getpwnam') && posix_getpwnam($data['account_lid']))
78
		{
79
			throw new Api\Exception\WrongUserinput(lang('There already is a system-user with this name. User\'s should not have the same name as a systemuser'),99);
80
		}
81
		if (!$data['account_lastname'] && (!$this->account || !is_null($data['account_lastname'])))
82
		{
83
			throw new Api\Exception\WrongUserinput(lang('You must enter a lastname'), 13);
84
		}
85
		if (!is_null($data['account_lid']) && ($id = admin_cmd::$accounts->name2id($data['account_lid'],'account_lid','u')) &&
86
			(string)$id !== (string)$data['account_id'])
87
		{
88
			throw new Api\Exception\WrongUserinput(lang('That loginid has already been taken'), 11);
89
		}
90
		if (isset($data['account_passwd_2']) && $data['account_passwd'] != $data['account_passwd_2'])
91
		{
92
			throw new Api\Exception\WrongUserinput(lang('The two passwords are not the same'), 12);
93
		}
94
		$expires = self::_parse_expired($data['account_expires'],(boolean)$this->account);
0 ignored issues
show
Bug Best Practice introduced by
The method admin_cmd_edit_user::_parse_expired() 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

94
		/** @scrutinizer ignore-call */ 
95
  $expires = self::_parse_expired($data['account_expires'],(boolean)$this->account);
Loading history...
95
		if ($expires === 0)	// deactivated
96
		{
97
			$data['account_expires'] = -1;
98
			$data['account_status'] = '';
99
		}
100
		else
101
		{
102
			$data['account_expires'] = $expires;
103
			$data['account_status'] = is_null($expires) ? null : ($expires == -1 || $expires > time() ? 'A' : '');
0 ignored issues
show
introduced by
The condition is_null($expires) is always false.
Loading history...
104
		}
105
106
		$data['changepassword'] = admin_cmd::parse_boolean($data['changepassword'],$this->account ? null : true);
107
		$data['anonymous'] = admin_cmd::parse_boolean($data['anonymous'],$this->account ? null : false);
108
		if ($data['mustchangepassword'] && $data['changepassword'])
109
		{
110
			$data['account_lastpwd_change']=0;
111
		}
112
113
		if (!$data['account_primary_group'] && $this->account)
114
		{
115
			$data['account_primary_group'] = null;	// dont change
116
		}
117
		else
118
		{
119
			if (!$data['account_primary_group'] && admin_cmd::$accounts->exists('Default') == 2)
120
			{
121
				$data['account_primary_group'] = 'Default';
122
			}
123
			$data['account_primary_group'] = admin_cmd::parse_account($data['account_primary_group'],false);
124
		}
125
		if (!$data['account_groups'] && $this->account)
126
		{
127
			$data['account_groups'] = null;	// dont change
128
		}
129
		else
130
		{
131
			if (!$data['account_groups'] && admin_cmd::$accounts->exists('Default') == 2)
132
			{
133
				$data['account_groups'] = array('Default');
134
			}
135
			$data['account_groups'] = admin_cmd::parse_accounts($data['account_groups'],false);
136
		}
137
		if ($check_only) return true;
138
139
		if ($this->account)
140
		{
141
			// invalidate account, before reading it, to code with changed to DB or LDAP outside EGw
142
			Api\Accounts::cache_invalidate($data['account_id']);
143
			if (!($old = admin_cmd::$accounts->read($data['account_id'])))
144
			{
145
				throw new Api\Exception\WrongUserinput(lang("Unknown account: %1 !!!",$this->account),15);
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $this->account. ( Ignorable by Annotation )

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

145
				throw new Api\Exception\WrongUserinput(/** @scrutinizer ignore-call */ lang("Unknown account: %1 !!!",$this->account),15);

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...
146
			}
147
			// as the current account class always sets all values, we have to add the not specified ones
148
			foreach($data as $name => &$value)
149
			{
150
				if (is_null($value)) $value = $old[$name];
151
			}
152
		}
153
		else
154
		{
155
			unset($data['account_id']);	// otherwise add will fail under postgres
156
		}
157
		// hook allowing apps to intercept adding/editing Api\Accounts before saving them
158
		Api\Hooks::process($data+array(
159
			'location' => $this->account ? 'pre_editaccount' : 'pre_addaccount',
160
		),False,True);	// called for every app now, not only enabled ones)
161
162
		if (!($data['account_id'] = admin_cmd::$accounts->save($data)))
163
		{
164
			//_debug_array($data);
165
			throw new Api\Db\Exception(lang("Error saving account!"),11);
166
		}
167
		// make new account_id available to caller
168
		$update = (boolean)$this->account;
169
		if (!$this->account) $this->account = $data['account_id'];
170
171
		if ($data['account_groups'])
172
		{
173
			admin_cmd::$accounts->set_memberships($data['account_groups'],$data['account_id']);
174
		}
175
		if (!is_null($data['anonymous']))
176
		{
177
			admin_cmd::_instanciate_acl();
178
			if ($data['anonymous'])
179
			{
180
				admin_cmd::$acl->add_repository('phpgwapi','anonymous',$data['account_id'],1);
181
			}
182
			else
183
			{
184
				admin_cmd::$acl->delete_repository('phpgwapi','anonymous',$data['account_id']);
185
			}
186
		}
187
		if (!is_null($data['changepassword']))
188
		{
189
			if (!$data['changepassword'])
190
			{
191
				admin_cmd::$acl->add_repository('preferences','nopasswordchange',$data['account_id'],1);
192
			}
193
			else
194
			{
195
				admin_cmd::$acl->delete_repository('preferences','nopasswordchange',$data['account_id']);
196
			}
197
		}
198
		// if we have a password and it's not a hash, and auth_type != account_repository
199
		if (!is_null($this->password) &&
0 ignored issues
show
Bug Best Practice introduced by
The property password does not exist on admin_cmd_edit_user. Since you implemented __get, consider adding a @property annotation.
Loading history...
200
			!preg_match('/^\\{[a-z5]{3,5}\\}.+/i',$this->password) &&
201
			!preg_match('/^[0-9a-f]{32}$/',$this->password) &&	// md5 hash
202
			admin_cmd::$accounts->config['auth_type'] != admin_cmd::$accounts->config['account_repository'])
203
		{
204
			admin_cmd_change_pw::exec();		// calling the exec method of the admin_cmd_change_pw
205
		}
206
		$data['account_passwd'] = $this->password;
207
		$GLOBALS['hook_values'] =& $data;
208
		Api\Hooks::process($GLOBALS['hook_values']+array(
209
			'location' => $update && $this->run_addaccount_hook !== true ? 'editaccount' : 'addaccount'
0 ignored issues
show
Bug Best Practice introduced by
The property run_addaccount_hook does not exist on admin_cmd_edit_user. Since you implemented __get, consider adding a @property annotation.
Loading history...
210
		),False,True);	// called for every app now, not only enabled ones)
211
212
		return lang("Account %1 %2", $data['account_lid'] ? $data['account_lid'] : Api\Accounts::id2name($this->account),
213
			$update ? lang('updated') : lang("created with id #%1", $this->account));
214
	}
215
216
	/**
217
	 * Return a title / string representation for a given command, eg. to display it
218
	 *
219
	 * @return string
220
	 */
221
	function __tostring()
222
	{
223
		return lang('%1 user %2',$this->account ? lang('Edit') : lang('Add'),
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $this->account ? lang('Edit') : lang('Add'). ( Ignorable by Annotation )

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

223
		return /** @scrutinizer ignore-call */ lang('%1 user %2',$this->account ? lang('Edit') : lang('Add'),

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...
224
			admin_cmd::display_account($this->account ? $this->account : $this->set['account_lid']));
225
	}
226
227
	/**
228
	 * Get name of eTemplate used to make the change to derive UI for history
229
	 *
230
	 * @return string|null etemplate name
231
	 */
232
	function get_etemplate_name()
233
	{
234
		return 'admin.account';
235
	}
236
237
	/**
238
	 * Return (human readable) labels for keys of changes
239
	 *
240
	 * @return array
241
	 */
242
	function get_change_labels()
243
	{
244
		$labels = parent::get_change_labels();
245
		$labels += array(
246
			'account_lastname' => 'lastname',
247
			'account_firstname' => 'firstname'
248
		);
249
		return $labels;
250
	}
251
252
	/**
253
	 * parse the expired string and return the expired date as timestamp
254
	 *
255
	 * @param string $str date, 'never', 'already' or '' (=dont change, or default of never of new Api\Accounts)
256
	 * @param boolean $existing
257
	 * @return int timestamp, 0 for already, -1 for never or null for dont change
258
	 * @throws Api\Exception\WrongUserinput(lang('Invalid formated date "%1"!',$datein),6);
259
	 */
260
	private function _parse_expired($str,$existing)
261
	{
262
		switch($str)
263
		{
264
			case '':
265
				if ($existing) return null;
266
				// fall through --> default for new Api\Accounts is never
267
			case 'never':
268
				return -1;
269
			case 'already':
270
				return 0;
271
		}
272
		return admin_cmd::parse_date($str);
273
	}
274
}
275