setup_cmd::save()   A
last analyzed

Complexity

Conditions 4
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 3
nc 2
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * eGgroupWare setup - abstract baseclass for all setup commands, extending admin_cmd
4
 *
5
 * @link http://www.egroupware.org
6
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
7
 * @package setup
8
 * @copyright (c) 2007-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
9
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10
 * @version $Id$
11
 */
12
13
use EGroupware\Api;
14
use EGroupware\Api\Egw;
15
16
/**
17
 * setup command: abstract baseclass for all setup commands, extending admin_cmd
18
 */
19
abstract class setup_cmd extends admin_cmd
20
{
21
	/**
22
	 * Defaults set for empty options while running the command
23
	 *
24
	 * @var array
25
	 */
26
	public $set_defaults = array();
27
28
	/**
29
	 * Should be called by every command usually requiring header admin rights
30
	 *
31
	 * @throws Api\Exception\NoPermission(lang('Wrong credentials to access the header.inc.php file!'),2);
32
	 */
33
	protected function _check_header_access()
34
	{
35
		if (!$this->header_secret && $this->header_admin_user)	// no secret specified but header_admin_user/password
0 ignored issues
show
Bug Best Practice introduced by
The property header_admin_user does not exist on setup_cmd. Since you implemented __get, consider adding a @property annotation.
Loading history...
36
		{
37
			if (!$this->uid) $this->uid = true;
0 ignored issues
show
Bug introduced by
The property uid is declared read-only in admin_cmd.
Loading history...
Documentation Bug introduced by
The property $uid was declared of type string, but true is of type true. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
38
			$this->set_header_secret($this->header_admin_user,$this->header_admin_password);
0 ignored issues
show
Bug Best Practice introduced by
The property header_admin_password does not exist on setup_cmd. Since you implemented __get, consider adding a @property annotation.
Loading history...
39
		}
40
		$secret = $this->_calc_header_secret($GLOBALS['egw_info']['server']['header_admin_user'],
41
				$GLOBALS['egw_info']['server']['header_admin_password']);
42
		if ($this->uid === true) unset($this->uid);
0 ignored issues
show
introduced by
The condition $this->uid === true is always false.
Loading history...
43
44
		if ($this->header_secret != $secret)
45
		{
46
			//echo "_check_header_access: header_secret='$this->header_secret' != '$secret'=_calc_header_secret({$GLOBALS['egw_info']['server']['header_admin_user']},{$GLOBALS['egw_info']['server']['header_admin_password']})\n";
47
			throw new Api\Exception\NoPermission(lang('Wrong credentials to access the header.inc.php file!'),5);
48
		}
49
50
	}
51
52
	/**
53
	 * Set the user and pw required for any operation on the header file
54
	 *
55
	 * @param string $user
56
	 * @param string $pw password or md5 hash of it
57
	 */
58
	public function set_header_secret($user,$pw)
59
	{
60
		if ($this->uid || parent::save(false))	// we need to save first, to get the uid
61
		{
62
			$this->header_secret = $this->_calc_header_secret($user,$pw);
0 ignored issues
show
Bug Best Practice introduced by
The property header_secret does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
63
		}
64
		else
65
		{
66
			throw new Exception ('failed to set header_secret!');
67
		}
68
	}
69
70
	/**
71
	 * Calculate the header_secret used to access the header from this command
72
	 *
73
	 * It's an md5 over the uid, header-admin-user and -password.
74
	 *
75
	 * @param string $header_admin_user
76
	 * @param string $header_admin_password
77
	 * @return string
78
	 */
79
	private function _calc_header_secret($header_admin_user=null,$header_admin_password=null)
80
	{
81
		if (!self::is_md5($header_admin_password)) $header_admin_password = md5($header_admin_password);
82
83
		$secret = md5($this->uid.$header_admin_user.$header_admin_password);
84
		//echo "header_secret='$secret' = md5('$this->uid'.'$header_admin_user'.'$header_admin_password')\n";
85
		return $secret;
86
	}
87
88
	/**
89
	 * Saving the object to the database, reimplemented to not do it in setup context
90
	 *
91
	 * @param boolean $set_modifier =true set the current user as modifier or 0 (= run by the system)
92
	 * @return boolean true on success, false otherwise
93
	 */
94
	function save($set_modifier=true)
95
	{
96
		if (isset($GLOBALS['egw']->db) && is_object($GLOBALS['egw']->db) && $GLOBALS['egw']->db->Database)
97
		{
98
			return parent::save($set_modifier);
99
		}
100
		return true;
101
	}
102
103
	/**
104
	 * Reference to the setup object, after calling check_setup_auth method
105
	 *
106
	 * @var setup
107
	 */
108
	static protected $egw_setup;
109
110
	static private $egw_accounts_backup;
111
112
	/**
113
	 * Create the setup enviroment (for running within setup or EGw)
114
	 */
115
	static protected function _setup_enviroment($domain=null)
116
	{
117
		if (!is_object($GLOBALS['egw_setup']))
118
		{
119
			require_once(EGW_INCLUDE_ROOT.'/setup/inc/class.setup.inc.php');
120
			$GLOBALS['egw_setup'] = new setup(true,true);
121
		}
122
		self::$egw_setup = $GLOBALS['egw_setup'];
123
		self::$egw_setup->ConfigDomain = $domain;
124
125
		if (isset($GLOBALS['egw_info']['server']['header_admin_user']) && !isset($GLOBALS['egw_domain']) &&
126
			is_object($GLOBALS['egw']) && $GLOBALS['egw'] instanceof Egw)
127
		{
128
			// we run inside EGw, not setup --> read egw_domain array from the header via the showheader cmd
129
			$cmd = new setup_cmd_showheader(null);	// null = only header, no db stuff, no hashes
130
			$header = $cmd->run();
131
			$GLOBALS['egw_domain'] = $header['egw_domain'];
132
133
			if (is_object($GLOBALS['egw']->accounts) && is_null(self::$egw_accounts_backup))
134
			{
135
				self::$egw_accounts_backup = $GLOBALS['egw']->accounts;
136
				unset($GLOBALS['egw']->accounts);
137
			}
138
			if ($this->config) self::$egw_setup->setup_account_object($this->config);
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using $this inside a static method is generally not recommended and can lead to errors in newer PHP versions.
Loading history...
Bug Best Practice introduced by
The property config does not exist on setup_cmd. Since you implemented __get, consider adding a @property annotation.
Loading history...
139
		}
140
		if (is_object($GLOBALS['egw']->db) && $domain)
141
		{
142
			$GLOBALS['egw']->db->disconnect();
143
			$GLOBALS['egw']->db = new Api\Db($GLOBALS['egw_domain'][$domain]);
144
145
			// change caching to managed instance
146
			Api\Cache::unset_instance_key();
147
		}
148
	}
149
150
	/**
151
	 * Restore EGw's db connection
152
	 *
153
	 */
154
	static function restore_db()
155
	{
156
		if (is_object($GLOBALS['egw']->db))
157
		{
158
			$GLOBALS['egw']->db->disconnect();
159
			$GLOBALS['egw']->db = new Api\Db($GLOBALS['egw_info']['server']);
160
161
			// change caching back to own instance
162
			Api\Cache::unset_instance_key();
163
164
			if (!is_null(self::$egw_accounts_backup))
165
			{
166
				$GLOBALS['egw']->accounts = self::$egw_accounts_backup;
167
				Api\Accounts::cache_invalidate();
168
				unset(self::$egw_accounts_backup);
169
			}
170
		}
171
	}
172
173
	/**
174
	 * Creates a setup like enviroment and checks for the header user/pw or config_user/pw if domain given
175
	 *
176
	 * @param string $user
177
	 * @param string $pw
178
	 * @param string $domain =null if given we also check agains config user/pw
179
	 * @throws Api\Exception\NoPermission(lang('Access denied: wrong username or password for manage-header !!!'),21);
180
	 * @throws Api\Exception\NoPermission(lang("Access denied: wrong username or password to configure the domain '%1(%2)' !!!",$domain,$GLOBALS['egw_domain'][$domain]['db_type']),40);
181
	 */
182
	static function check_setup_auth($user,$pw,$domain=null)
183
	{
184
		self::_setup_enviroment($domain);
185
186
		// check the authentication if a header_admin_password is set, if not we dont have a header yet and no authentication
187
		if ($GLOBALS['egw_info']['server']['header_admin_password'])	// if that's not given we dont have a header yet
188
		{
189
			if (!self::$egw_setup->check_auth($user,$pw,$GLOBALS['egw_info']['server']['header_admin_user'],
190
					$GLOBALS['egw_info']['server']['header_admin_password']) &&
191
				(is_null($domain) || !isset($GLOBALS['egw_domain'][$domain]) || // if valid domain given check it's config user/pw
192
					!self::$egw_setup->check_auth($user,$pw,$GLOBALS['egw_domain'][$domain]['config_user'],
193
						$GLOBALS['egw_domain'][$domain]['config_passwd'])))
194
			{
195
				if (is_null($domain))
196
				{
197
					throw new Api\Exception\NoPermission(lang('Access denied: wrong username or password for manage-header !!!'),21);
198
				}
199
				else
200
				{
201
					throw new Api\Exception\NoPermission(lang("Access denied: wrong username or password to configure the domain '%1(%2)' !!!",$domain,$GLOBALS['egw_domain'][$domain]['db_type']),40);
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $domain. ( Ignorable by Annotation )

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

201
					throw new Api\Exception\NoPermission(/** @scrutinizer ignore-call */ lang("Access denied: wrong username or password to configure the domain '%1(%2)' !!!",$domain,$GLOBALS['egw_domain'][$domain]['db_type']),40);

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...
202
				}
203
			}
204
		}
205
	}
206
207
	/**
208
	 * Applications which are currently not installed (set after call to check_installed, for the last/only domain only)
209
	 *
210
	 * @var array
211
	 */
212
	static public $apps_to_install=array();
213
	/**
214
	 * Applications which are currently need update (set after call to check_installed, for the last/only domain only)
215
	 *
216
	 * @var array
217
	 */
218
	static public $apps_to_upgrade=array();
219
220
	/**
221
	 * Check if EGw is installed, which versions and if an update is needed
222
	 *
223
	 * Sets self::$apps_to_update and self::$apps_to_install for the last/only domain only!
224
	 *
225
	 * @param string $domain ='' domain to check, default '' = all
226
	 * @param int/array $stop =0 stop checks before given exit-code(s), default 0 = all checks
0 ignored issues
show
Documentation Bug introduced by
The doc comment int/array at position 0 could not be parsed: Unknown type name 'int/array' at position 0 in int/array.
Loading history...
227
	 * @param boolean $verbose =false echo messages as they happen, instead returning them
228
	 * @return array with translated messages
229
	 */
230
	static function check_installed($domain='',$stop=0,$verbose=false)
231
	{
232
		self::_setup_enviroment($domain);
233
234
		global $setup_info;
235
		static $header_checks=true;	// output the header checks only once
236
237
		$messages = array();
238
239
		if ($stop && !is_array($stop)) $stop = array($stop);
240
241
		$versions =& $GLOBALS['egw_info']['server']['versions'];
242
243
		if (!$versions['api'])
244
		{
245
			if (!include(EGW_INCLUDE_ROOT.'/api/setup/setup.inc.php'))
246
			{
247
				throw new Api\Exception\WrongUserinput(lang("EGroupware sources in '%1' are not complete, file '%2' missing !!!",realpath('..'),'api/setup/setup.inc.php'),99);	// should not happen ;-)
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with realpath('..'). ( Ignorable by Annotation )

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

247
				throw new Api\Exception\WrongUserinput(/** @scrutinizer ignore-call */ lang("EGroupware sources in '%1' are not complete, file '%2' missing !!!",realpath('..'),'api/setup/setup.inc.php'),99);	// should not happen ;-)

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...
248
			}
249
			$versions['api'] = $setup_info['api']['version'];
250
			unset($setup_info);
251
		}
252
		if ($header_checks)
253
		{
254
			$messages[] = self::_echo_message($verbose,lang('EGroupware API version %1 found.',$versions['api']));
255
		}
256
		$header_stage = self::$egw_setup->detection->check_header();
257
		if ($stop && in_array($header_stage,$stop)) return true;
258
259
		switch ($header_stage)
260
		{
261
			case 1: throw new Api\Exception\WrongUserinput(lang('EGroupware configuration file (header.inc.php) does NOT exist.')."\n".lang('Use --create-header to create the configuration file (--usage gives more options).'),1);
262
263
//			case 2: throw new Api\Exception\WrongUserinput(lang('EGroupware configuration file (header.inc.php) version %1 exists%2',$versions['header'],'.')."\n".lang('No header admin password set! Use --edit-header <password>[,<user>] to set one (--usage gives more options).'),2);
264
265
			case 3: throw new Api\Exception\WrongUserinput(lang('EGroupware configuration file (header.inc.php) version %1 exists%2',$versions['header'],'.')."\n".lang('No EGroupware domains / database instances exist! Use --edit-header --domain to add one (--usage gives more options).'),3);
266
267
			case 4: throw new Api\Exception\WrongUserinput(lang('EGroupware configuration file (header.inc.php) version %1 exists%2',$versions['header'],'.')."\n".lang('It needs upgrading to version %1! Use --update-header <password>[,<user>] to do so (--usage gives more options).',$versions['current_header']),4);
268
		}
269
		if ($header_checks)
270
		{
271
			$messages[] = self::_echo_message($verbose,lang('EGroupware configuration file (header.inc.php) version %1 exists%2',
272
				$versions['header'],' '.lang('and is up to date')));
273
		}
274
		unset($header_checks);	// no further output of the header checks
275
276
		$domains = $GLOBALS['egw_domain'];
277
		if ($domain)	// domain to check given
278
		{
279
			if (!isset($GLOBALS['egw_domain'][$domain])) throw new Api\Exception\WrongUserinput(lang("Domain '%1' does NOT exist !!!",$domain), 92);
280
281
			$domains = array($domain => $GLOBALS['egw_domain'][$domain]);
282
		}
283
		foreach($domains as $domain => $data)
284
		{
285
			self::$egw_setup->ConfigDomain = $domain;	// set the domain the setup class operates on
286
			if (count($GLOBALS['egw_domain']) > 1)
287
			{
288
				self::_echo_message($verbose);
289
				$messages[] = self::_echo_message($verbose,lang('EGroupware domain/instance %1(%2):',$domain,$data['db_type']));
290
			}
291
			$setup_info = self::$egw_setup->detection->get_versions();
292
			// check if there's already a db-connection and close if, otherwise the db-connection of the previous domain will be used
293
			if (is_object(self::$egw_setup->db))
294
			{
295
				self::$egw_setup->db->disconnect();
296
			}
297
			self::$egw_setup->loaddb();
298
299
			$db = $data['db_type'].'://'.$data['db_user'].':'.$data['db_pass'].'@'.$data['db_host'].'/'.$data['db_name'];
300
301
			$db_stage =& $GLOBALS['egw_info']['setup']['stage']['db'];
302
			if (($db_stage = self::$egw_setup->detection->check_db($setup_info)) != 1)
303
			{
304
				$setup_info = self::$egw_setup->detection->get_db_versions($setup_info);
305
				$db_stage = self::$egw_setup->detection->check_db($setup_info);
306
			}
307
			if ($stop && in_array(10+$db_stage,$stop))
308
			{
309
				return $messages;
310
			}
311
			switch($db_stage)
312
			{
313
				case 1: throw new Api\Exception\WrongUserinput(lang('Your Database is not working!')." $db: ".self::$egw_setup->db->Error,11);
314
315
				case 3: throw new Api\Exception\WrongUserinput(lang('Your database is working, but you dont have any applications installed')." ($db). ".lang("Use --install to install EGroupware."),13);
316
317
				case 4: throw new Api\Exception\WrongUserinput(lang('EGroupware API needs a database (schema) update from version %1 to %2!',$setup_info['api']['currentver'],$versions['api']).' '.lang('Use --update to do so.'),14);
318
319
				case 10:	// also check apps of updates
320
					self::$apps_to_upgrade = self::$apps_to_install = array();
321
					foreach($setup_info as $app => $data)
0 ignored issues
show
Comprehensibility Bug introduced by
$data is overwriting a variable from outer foreach loop.
Loading history...
322
					{
323
						if ($data['currentver'] && $data['version'] && $data['version'] != 'deleted' && $data['version'] != $data['currentver'])
324
						{
325
							self::$apps_to_upgrade[] = $app;
326
						}
327
						if (!isset($data['enabled']) && isset($data['version']))	// jdots eg. is no app, but a template
328
						{
329
							self::$apps_to_install[] = $app;
330
						}
331
					}
332
					// add autodeinstall apps
333
					self::$apps_to_upgrade = array_unique(array_merge(self::$apps_to_upgrade, self::check_autodeinstall()));
334
335
					if (self::$apps_to_install)
336
					{
337
						self::_echo_message($verbose);
338
						$messages[] = self::_echo_message($verbose,lang('The following applications are NOT installed:').' '.implode(', ',self::$apps_to_install));
339
					}
340
					if (self::$apps_to_upgrade)
0 ignored issues
show
Bug Best Practice introduced by
The expression self::apps_to_upgrade 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...
341
					{
342
						$db_stage = 4;
343
						if ($stop && in_array(10+$db_stage,$stop)) return $messages;
344
345
						throw new Api\Exception\WrongUserinput(lang('The following applications need to be upgraded:').' '.implode(', ',self::$apps_to_upgrade).'! '.lang('Use --update to do so.'),14);
346
					}
347
					break;
348
			}
349
			$messages[] = self::_echo_message($verbose,lang("database is version %1 and up to date.",$setup_info['api']['currentver']));
350
351
			self::$egw_setup->detection->check_config();
352
			if ($GLOBALS['egw_info']['setup']['config_errors'] && $stop && !in_array(15,$stop))
353
			{
354
				throw new Api\Exception\WrongUserinput(lang('You need to configure EGroupware:')."\n- ".@implode("\n- ",$GLOBALS['egw_info']['setup']['config_errors']),15);
355
			}
356
		}
357
		return $messages;
358
	}
359
360
	/**
361
	 * Check if there are apps which should be autoinstalled
362
	 *
363
	 * @return array with app-names
364
	 */
365
	static function check_autoinstall()
366
	{
367
		$ret = array_filter(self::$apps_to_install, function($app)
368
		{
369
			global $setup_info;
370
			return $setup_info[$app]['autoinstall'];
371
		});
372
		//error_log(__METHOD__."() apps_to_install=".array2string(self::$apps_to_install).' returning '.array2string($ret));
373
		return $ret;
374
	}
375
376
	/**
377
	 * Check if app should be automatically deinstalled
378
	 *
379
	 * @return array with app-names to automatic deinstall
380
	 */
381
	static function check_autodeinstall()
382
	{
383
		global $setup_info;
384
385
		$ret = array_values(array_filter(array_keys($setup_info), function($app)
386
		{
387
			global $setup_info;
388
			if (empty($setup_info[$app]['autodeinstall']))
389
			{
390
				return false;
391
			}
392
			$autodeinstall = $setup_info[$app]['autodeinstall'];
393
			if (!is_bool($autodeinstall))
394
			{
395
				try {
396
					$autodeinstall = (bool)$GLOBALS['egw_setup']->db->query($autodeinstall, __LINE__, __FILE__)->fetchColumn();
397
				}
398
				catch (\Exception $e) {
399
					_egw_log_exception($e);
400
					$autodeinstall = false;
401
				}
402
			}
403
			return $autodeinstall;
404
		}));
405
		//error_log(__METHOD__."() apps=".json_encode(array_keys($setup_info)).' returning '.json_encode($ret));
406
		return $ret;
407
	}
408
409
	/**
410
	 * Echo the given message, if $verbose
411
	 *
412
	 * @param boolean $verbose
413
	 * @param string $msg
414
	 * @return string $msg
415
	 */
416
	static function _echo_message($verbose,$msg='')
417
	{
418
		if ($verbose) echo $msg."\n";
419
420
		return $msg;
421
	}
422
}
423