Issues (4868)

setup/inc/class.setup_detection.inc.php (5 issues)

1
<?php
2
/**
3
 * EGroupware Setup
4
 *
5
 * @link http://www.egroupware.org
6
 * @package setup
7
 * @author Dan Kuykendall <[email protected]>
8
 * @author Miles Lott <[email protected]>
9
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
10
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
11
 * @version $Id$
12
 */
13
14
use EGroupware\Api;
15
16
/**
17
 * Class detecting the current installation status of EGroupware
18
 */
19
class setup_detection
20
{
21
	/**
22
	 * Get available application versions and data from filesystem
23
	 *
24
	 * @return array $setup_info
25
	 */
26
	function get_versions()
27
	{
28
		$setup_info = array();
29
		$d = dir(EGW_SERVER_ROOT);
30
		while($entry=$d->read())
31
		{
32
			if($entry != ".." && $entry != 'setup' && is_dir(EGW_SERVER_ROOT . '/' . $entry))
33
			{
34
				$f = EGW_SERVER_ROOT . '/' . $entry . '/setup/setup.inc.php';
35
				if (@file_exists ($f))
36
				{
37
					include($f);
38
					$setup_info[$entry]['filename'] = $f;
39
				}
40
			}
41
		}
42
		$d->close();
43
44
		// _debug_array($setup_info);
45
		@ksort($setup_info);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ksort(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

45
		/** @scrutinizer ignore-unhandled */ @ksort($setup_info);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
46
		return $setup_info;
47
	}
48
49
	/**
50
	 * Get versions of installed applications from database
51
	 *
52
	 * @param array $setup_info
53
	 * @return array $setup_info
54
	 */
55
	function get_db_versions($setup_info=null)
56
	{
57
		try {	// catch DB errors
58
			$GLOBALS['egw_setup']->set_table_names();
59
60
			if($GLOBALS['egw_setup']->table_exist(array($GLOBALS['egw_setup']->applications_table),true))
61
			{
62
				/* one of these tables exists. checking for post/pre beta version */
63
				if($GLOBALS['egw_setup']->applications_table != 'applications')
64
				{
65
					foreach($GLOBALS['egw_setup']->db->select($GLOBALS['egw_setup']->applications_table,'*',false,__LINE__,__FILE__) as $row)
66
					{
67
						$app = $row['app_name'];
68
						if (!isset($setup_info[$app]))	// app source no longer there
69
						{
70
							$setup_info[$app] = array(
71
								'name' => $app,
72
								'tables' => $row['app_tables'],
73
								'version' => 'deleted',
74
							);
75
						}
76
						$setup_info[$app]['currentver'] = $row['app_version'];
77
						$setup_info[$app]['enabled'] = $row['app_enabled'];
78
					}
79
					/* This is to catch old setup installs that did not have phpgwapi listed as an app */
80
					$tmp = @$setup_info['phpgwapi']['version']; /* save the file version */
81
					if (isset($setup_info['api']['version']))
82
					{
83
						// new api, dont care about old pre egroupware stuff
84
					}
85
					elseif(!@$setup_info['phpgwapi']['currentver'])
86
					{
87
						$setup_info['phpgwapi']['currentver'] = $setup_info['admin']['currentver'];
88
						$setup_info['phpgwapi']['version'] = $setup_info['admin']['currentver'];
89
						$setup_info['phpgwapi']['enabled'] = $setup_info['admin']['enabled'];
90
						// _debug_array($setup_info['phpgwapi']);exit;
91
						// There seems to be a problem here.  If ['phpgwapi']['currentver'] is set,
92
						// The GLOBALS never gets set.
93
						$GLOBALS['setup_info'] = $setup_info;
94
						$GLOBALS['egw_setup']->register_app('phpgwapi');
95
					}
96
					else
97
					{
98
						$GLOBALS['setup_info'] = $setup_info;
99
					}
100
					$setup_info['phpgwapi']['version'] = $tmp; /* restore the file version */
101
				}
102
				else
103
				{
104
					foreach($GLOBALS['egw_setup']->db->query('select * from applications') as $row)
105
					{
106
						if($row['app_name'] == 'admin')
107
						{
108
							$setup_info['phpgwapi']['currentver'] = $row['app_version'];
109
						}
110
						$setup_info[$row['app_name']]['currentver'] = $row['app_version'];
111
					}
112
				}
113
			}
114
		}
115
		catch (Api\Db\Exception $e) {
116
			unset($e);
117
			// ignore db errors
118
		}
119
		// remove emailadmin, if it is not already installed, it exists only to allow to update to from versions before 16.1
120
		if (isset($setup_info['emailadmin']) && empty($setup_info['emailadmin']['currentver']))
121
		{
122
			unset($setup_info['emailadmin']);
123
		}
124
		// _debug_array($setup_info);
125
		return $setup_info;
126
	}
127
128
	/**
129
	 * Compare versions from filesystem and database and set status:
130
	 * 	U	Upgrade required/available
131
	 * 	R	upgrade in pRogress
132
	 * 	C	upgrade Completed successfully
133
	 * 	D	Dependency failure
134
	 * 	P	Post-install dependency failure
135
	 * 	F	upgrade Failed
136
	 * 	V	Version mismatch at end of upgrade (Not used, proposed only)
137
	 * 	M	Missing files at start of upgrade (Not used, proposed only)
138
	 */
139
	function compare_versions($setup_info,$try_downgrade=false)
140
	{
141
		foreach($setup_info as $key => $value)
142
		{
143
			//echo '<br>'.$value['name'].'STATUS: '.$value['status'];
144
			/* Only set this if it has not already failed to upgrade - Milosch */
145
			if(!( (@$value['status'] == 'F') || (@$value['status'] == 'C') ))
146
			{
147
				//if ($setup_info[$key]['currentver'] > $setup_info[$key]['version'])
148
				if(!$try_downgrade && $GLOBALS['egw_setup']->amorethanb($value['currentver'],@$value['version']) ||
0 ignored issues
show
Consider adding parentheses for clarity. Current Interpretation: (! $try_downgrade && $GL...'version'] == 'deleted', Probably Intended Meaning: ! $try_downgrade && ($GL...version'] == 'deleted')
Loading history...
149
					$value['version'] == 'deleted')
150
				{
151
					$setup_info[$key]['status'] = 'V';
152
				}
153
				elseif(@$value['currentver'] == @$value['version'])
154
				{
155
					$setup_info[$key]['status'] = 'C';
156
				}
157
				elseif($GLOBALS['egw_setup']->alessthanb(@$value['currentver'],@$value['version']))
158
				{
159
					$setup_info[$key]['status'] = 'U';
160
				}
161
				else
162
				{
163
					$setup_info[$key]['status'] = 'U';
164
				}
165
			}
166
		}
167
		// _debug_array($setup_info);
168
		return $setup_info;
169
	}
170
171
	function check_depends($setup_info)
172
	{
173
		/* Run the list of apps */
174
		foreach($setup_info as $key => $value)
175
		{
176
			/* Does this app have any depends */
177
			if(isset($value['depends']))
178
			{
179
				/* If so find out which apps it depends on */
180
				foreach($value['depends'] as $depkey => $depvalue)
181
				{
182
					/* I set this to False until we find a compatible version of this app */
183
					$setup_info['depends'][$depkey]['status'] = False;
184
					/* Now we loop thru the versions looking for a compatible version */
185
186
					foreach($depvalue['versions'] as $depsvalue)
187
					{
188
						$currentver = $setup_info[$depvalue['appname']]['currentver'];
189
						if ($depvalue['appname'] == 'phpgwapi' && substr($currentver,0,6) == '0.9.99')
190
						{
191
							$currentver = '0.9.14.508';
192
						}
193
						$major = $GLOBALS['egw_setup']->get_major($currentver);
194
						if ($major >= $depsvalue || $major == $depsvalue && substr($major,0,strlen($depsvalue)+1) == $depsvalue.'.')
195
						{
196
							$setup_info['depends'][$depkey]['status'] = True;
197
						}
198
						else	// check if majors are equal and minors greater or equal
199
						{
200
							$major_depsvalue = $GLOBALS['egw_setup']->get_major($depsvalue);
201
							$tmp = explode('.',$depsvalue); $minor_depsvalue = array_pop($tmp);
202
							$tmp2 = explode('.',$currentver); $minor = array_pop($tmp2);
203
							if ($major == $major_depsvalue && $minor <= $minor_depsvalue)
204
							{
205
								$setup_info['depends'][$depkey]['status'] = True;
206
							}
207
						}
208
						//echo "<p>app=$key depends on $depvalue[appname](".implode(',',$depvalue['versions']).") current=$currentver, major=$major, depsvalue=$depsvalue, major_depsvalue=$major_depsvalue, minor_depsvalue=$minor_depsvalue, minor=$minor ==> ".(int)$setup_info['depends'][$depkey]['status']."</p>\n";
209
					}
210
				}
211
				/*
212
				 Finally, we loop through the dependencies again to look for apps that still have a failure status
213
				 If we find one, we set the apps overall status as a dependency failure.
214
				*/
215
				foreach($value['depends'] as $depkey => $depvalue)
216
				{
217
					if ($setup_info['depends'][$depkey]['status'] == False)
218
					{
219
						/* Only set this if it has not already failed to upgrade - Milosch */
220
						if($setup_info[$key]['status'] != 'F')//&& $setup_info[$key]['status'] != 'C')
221
						{
222
							/* Added check for status U - uninstalled apps carry this flag (upgrade from nothing == install).
223
							 * This should fix apps showing post-install dep failure when they are not yet installed.
224
							 */
225
							if($setup_info[$key]['status'] == 'C' || $setup_info[$key]['status'] == 'U')
226
							{
227
								$setup_info[$key]['status'] = 'D';
228
							}
229
							else
230
							{
231
								$setup_info[$key]['status'] = 'P';
232
							}
233
						}
234
					}
235
				}
236
			}
237
		}
238
		return $setup_info;
239
	}
240
241
	/**
242
	 * Called during the mass upgrade routine (Stage 1) to check for apps
243
	 * that wish to be excluded from this process.
244
	 */
245
	function upgrade_exclude($setup_info)
246
	{
247
		foreach($setup_info as $key => $value)
248
		{
249
			if(isset($value['no_mass_update']))
250
			{
251
				unset($setup_info[$key]);
252
			}
253
		}
254
		return $setup_info;
255
	}
256
257
	/**
258
	 * Check if header exists and is up to date
259
	 *
260
	 * @return int 1=no header.inc.php, 2=no header admin pw, 3=no instances, 4=need upgrade, 10=ok
261
	 */
262
	function check_header()
263
	{
264
		if(!file_exists(EGW_SERVER_ROOT.'/header.inc.php') || filesize(EGW_SERVER_ROOT.'/header.inc.php') < 200)
265
		{
266
			$GLOBALS['egw_info']['setup']['header_msg'] = 'Stage One';
267
			return '1';
268
		}
269
		else
270
		{
271
			if(!@isset($GLOBALS['egw_info']['server']['header_admin_password']))
272
			{
273
				$GLOBALS['egw_info']['setup']['header_msg'] = 'Stage One (No header admin password set)';
274
				return '2';
275
			}
276
			elseif(!@isset($GLOBALS['egw_domain']))
277
			{
278
				$GLOBALS['egw_info']['setup']['header_msg'] = 'Stage One (Add domains to your header.inc.php)';
279
				return '3';
280
			}
281
			elseif(@$GLOBALS['egw_info']['server']['versions']['header'] != @$GLOBALS['egw_info']['server']['versions']['current_header'])
282
			{
283
				$GLOBALS['egw_info']['setup']['header_msg'] = 'Stage One (Upgrade your header.inc.php)';
284
				return '4';
285
			}
286
		}
287
		/* header.inc.php part settled. Moving to authentication */
288
		$GLOBALS['egw_info']['setup']['header_msg'] = 'Stage One (Completed)';
289
		return '10';
290
	}
291
292
	/**
293
	 * Check if database exists
294
	 *
295
	 * @param array $setup_info =null default use $GLOBALS['setup_info']
296
	 * @return int 1=no database, 3=empty, 4=need upgrade, 10=complete
297
	 */
298
	function check_db($setup_info=null)
299
	{
300
		if (!$setup_info) $setup_info = $GLOBALS['setup_info'];
301
302
		try {	// catch DB errors
303
			if (!$GLOBALS['egw_setup']->db->Link_ID)
304
			{
305
				$old = error_reporting();
306
				error_reporting($old & ~E_WARNING);	// no warnings
307
				$GLOBALS['egw_setup']->db->connect();
308
				error_reporting($old);
309
310
				$GLOBALS['egw_setup']->set_table_names();
311
			}
312
		}
313
		catch(Api\Db\Exception $e) {
314
			// ignore error
315
		}
316
317
		if (!$GLOBALS['egw_setup']->db->Link_ID || !$GLOBALS['egw_setup']->db->Link_ID->_connectionID)
318
		{
319
			$GLOBALS['egw_info']['setup']['header_msg'] = 'Stage 1 (Create Database)';
320
			return 1;
321
		}
322
		if(!isset($setup_info['api']['currentver']))
323
		{
324
			$setup_info = $this->get_db_versions($setup_info);
325
		}
326
		// first check new api installed and up to date
327
		if (isset($setup_info['api']['currentver']))
328
		{
329
			if($setup_info['api']['currentver'] == $setup_info['api']['version'])
330
			{
331
				$GLOBALS['egw_info']['setup']['header_msg'] = 'Stage 1 (Tables Complete)';
332
				return 10;
333
			}
334
			else
335
			{
336
				$GLOBALS['egw_info']['setup']['header_msg'] = 'Stage 1 (Tables need upgrading)';
337
				return 4;
338
			}
339
		}
340
		// then check old phpgwapi
341
		elseif (isset($setup_info['phpgwapi']['currentver']))
342
		{
343
			if($setup_info['phpgwapi']['currentver'] == $setup_info['phpgwapi']['version'])
344
			{
345
				$GLOBALS['egw_info']['setup']['header_msg'] = 'Stage 1 (Tables Complete)';
346
				return 10;
347
			}
348
			else
349
			{
350
				$GLOBALS['egw_info']['setup']['header_msg'] = 'Stage 1 (Tables need upgrading)';
351
				return 4;
352
			}
353
		}
354
		else
355
		{
356
			/* no tables, so checking if we can create them */
357
			try {
358
				$GLOBALS['egw_setup']->db->query('CREATE TABLE egw_testrights ( testfield varchar(5) NOT NULL )');
359
				$GLOBALS['egw_setup']->db->query('DROP TABLE egw_testrights');
360
				$GLOBALS['egw_info']['setup']['header_msg'] = 'Stage 3 (Install Applications)';
361
				return 3;
362
			}
363
			catch (Api\Db\Exception $e) {
364
				unset($e);
365
				$GLOBALS['egw_info']['setup']['header_msg'] = 'Stage 1 (Create Database)';
366
				return 1;
367
			}
368
		}
369
	}
370
371
	/**
372
	 * Check if EGw configuration exists
373
	 *
374
	 * @return int 1 = Needs config, ..., 10 = Config Ok
375
	 */
376
	function check_config()
377
	{
378
		if(@$GLOBALS['egw_info']['setup']['stage']['db'] != 10)
379
		{
380
			return '';
381
		}
382
383
		try {	// catch db errors
384
			foreach($GLOBALS['egw_setup']->db->select($GLOBALS['egw_setup']->config_table,
385
				'config_name,config_value',array('config_app' => 'phpgwapi'),__LINE__,__FILE__) as $row)
386
			{
387
				$config[$row['config_name']] = $row['config_value'];
388
			}
389
		}
390
		catch (Api\Db\Exception $e) {
391
			unset($e);
392
			// ignore db errors
393
		}
394
		$GLOBALS['egw_info']['setup']['header_msg'] = 'Stage 2 (Needs Configuration)';
395
		if(!count($config))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $config seems to be defined by a foreach iteration on line 384. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
396
		{
397
			return 1;
398
		}
399
		$config_errors =& $GLOBALS['egw_info']['setup']['config_errors'];
400
		$GLOBALS['egw_info']['setup']['config_errors'] = array();
401
		$error_msg = null;
402
		if (!$this->check_dir($config['temp_dir'],$error_msg))
403
		{
404
			$config_errors[] = lang("Your temporary directory '%1' %2",$config['temp_dir'],$error_msg);
0 ignored issues
show
The call to lang() has too many arguments starting with $config['temp_dir']. ( Ignorable by Annotation )

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

404
			$config_errors[] = /** @scrutinizer ignore-call */ lang("Your temporary directory '%1' %2",$config['temp_dir'],$error_msg);

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...
405
		}
406
407
		if (!$this->check_dir($config['files_dir'],$error_msg,true))
408
		{
409
			$config_errors[] = lang("Your files directory '%1' %2",$config['files_dir'],$error_msg);
410
		}
411
		// set and create the default backup_dir
412
		if (@is_writeable($config['files_dir']) && empty($config['backup_dir']))
413
		{
414
			$config['backup_dir'] = $config['files_dir'].'/db_backup';
415
			if (!is_dir($config['backup_dir']) && mkdir($config['backup_dir']))
416
			{
417
				$GLOBALS['egw_setup']->db->insert($GLOBALS['egw_setup']->config_table,array(
418
					'config_value' => $config['backup_dir'],
419
				),array(
420
					'config_app'  => 'phpgwapi',
421
					'config_name' => 'backup_dir',
422
				),__LINE__,__FILE__);
423
			}
424
		}
425
		if (isset($config['backup_mincount']))
426
		{
427
			$GLOBALS['egw_setup']->db->insert($GLOBALS['egw_setup']->config_table,array(
428
				'config_value' => $config['backup_mincount'],
429
				),array(
430
				'config_app'  => 'phpgwapi',
431
				'config_name' => 'backup_mincount',
432
			),__LINE__,__FILE__);
433
		}
434
		if (isset($config['backup_files']))
435
		{
436
			$GLOBALS['egw_setup']->db->insert($GLOBALS['egw_setup']->config_table,array(
437
				'config_value' => (int)$config['backup_files'],
438
				),array(
439
				'config_app'  => 'phpgwapi',
440
				'config_name' => 'backup_files',
441
			),__LINE__,__FILE__);
442
		}
443
		if (!$this->check_dir($config['backup_dir'],$error_msg,true))
444
		{
445
			$config_errors[] = lang("Your backup directory '%1' %2",$config['backup_dir'],$error_msg);
446
		}
447
		if ($config_errors)
448
		{
449
			return 2;
450
		}
451
		$GLOBALS['egw_info']['setup']['header_msg'] = 'Stage 2 (Configuration OK)';
452
		return 10;
453
	}
454
455
	/**
456
	 * Verify that all of an app's tables exist in the db
457
	 *
458
	 * @param $appname
459
	 * @param $any		optional, set to True to see if any of the apps tables are installed
0 ignored issues
show
The type optional 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...
460
	 */
461
	function check_app_tables($appname,$any=False)
462
	{
463
		$none = 0;
464
		$setup_info = $GLOBALS['setup_info'];
465
466
		if(@$setup_info[$appname]['tables'])
467
		{
468
			/* Make a copy, else we send some callers into an infinite loop */
469
			$copy = $setup_info;
470
			$tables = Array();
471
			try {
472
				$table_names = $GLOBALS['egw_setup']->db->table_names();
473
				foreach($table_names as $key => $val)
474
				{
475
					$tables[] = $val['table_name'];
476
				}
477
			}
478
			catch (Api\Db\Exception $e) {
479
				unset($e);
480
				// ignore db error
481
			}
482
			foreach($copy[$appname]['tables'] as $key => $val)
483
			{
484
				if($GLOBALS['DEBUG'])
485
				{
486
					echo '<br>check_app_tables(): Checking: ' . $appname . ',table: ' . $val;
487
				}
488
				if(!in_array($val,$tables) && !in_array(strtolower($val),$tables))	// names in tables might be lowercase
489
				{
490
					if($GLOBALS['DEBUG'])
491
					{
492
						echo '<br>check_app_tables(): ' . $val . ' missing!';
493
					}
494
					if(!$any)
495
					{
496
						return False;
497
					}
498
					else
499
					{
500
						$none++;
501
					}
502
				}
503
				else
504
				{
505
					if($any)
506
					{
507
						if($GLOBALS['DEBUG'])
508
						{
509
							echo '<br>check_app_tables(): Some tables installed';
510
						}
511
						return True;
512
					}
513
				}
514
			}
515
		}
516
		if($none && $any)
517
		{
518
			if($GLOBALS['DEBUG'])
519
			{
520
				echo '<br>check_app_tables(): No tables installed';
521
			}
522
			return False;
523
		}
524
		else
525
		{
526
			if($GLOBALS['DEBUG'])
527
			{
528
				echo '<br>check_app_tables(): All tables installed';
529
			}
530
			return True;
531
		}
532
	}
533
534
	/**
535
	 * Checks if a directory exists, is writable by the webserver and optionaly is in the docroot
536
	 *
537
	 * @param string $dir path
538
	 * @param string &$msg error-msg: 'does not exist', 'is not writeable by the webserver' or 'is in the webservers docroot' (run through lang)
539
	 * @param boolean $check_in_docroot =false run an optional in docroot check
540
	 * @return boolean
541
	 */
542
	static function check_dir($dir,&$msg,$check_in_docroot=false)
543
	{
544
		if (!@is_dir($dir) && !@mkdir($dir,0700,true))
545
		{
546
			$msg = lang('does not exist');
547
			return false;
548
		}
549
		if (!@is_writeable($dir) && $_SERVER['HTTP_HOST'])	// only do the check if we run by the webserver
550
		{
551
			$msg = lang('is not writeable by the webserver');
552
			return false;
553
		}
554
		if ($check_in_docroot)
555
		{
556
			$docroots = array(realpath(EGW_SERVER_ROOT),realpath($_SERVER['DOCUMENT_ROOT']));
557
			$dir = realpath($dir);
558
559
			foreach ($docroots as $docroot)
560
			{
561
				$len = strlen($docroot);
562
563
				if ($docroot == substr($dir,0,$len) && $len>0)
564
				{
565
					$rest = substr($dir,$len);
566
567
					if (!strlen($rest) || $rest[0] == DIRECTORY_SEPARATOR)
568
					{
569
						$msg = lang('is in the webservers docroot');
570
						return false;
571
					}
572
				}
573
			}
574
		}
575
		return true;
576
	}
577
}
578