Completed
Push — master ( 9d38bf...1f84af )
by Ralf
125:16 queued 103:35
created

setup/inc/class.setup.inc.php (1 issue)

1
<?php
2
/**
3
 * Setup
4
 *
5
 * @link http://www.egroupware.org
6
 * @package setup
7
 * @author Joseph Engo <[email protected]>
8
 * @author Dan Kuykendall <[email protected]>
9
 * @author Mark Peters <[email protected]>
10
 * @author Miles Lott <[email protected]>
11
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
12
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
13
 */
14
15
use EGroupware\Api;
16
use EGroupware\Api\Link;
17
use EGroupware\Api\Egw;
18
use EGroupware\Api\Vfs;
19
20
class setup
21
{
22
	var $db;
23
	var $config_table       = 'egw_config';
24
	var $applications_table = 'egw_applications';
25
	var $acl_table          = 'egw_acl';
26
	var $accounts_table     = 'egw_accounts';
27
	var $prefs_table        = 'egw_preferences';
28
	var $lang_table         = 'egw_lang';
29
	var $languages_table    = 'egw_languages';
30
	var $hooks_table        = 'egw_hooks';
31
	var $cats_table         = 'egw_categories';
32
	var $oProc;
33
	var $cookie_domain;
34
35
	/**
36
	 * @var setup_detection
37
	 */
38
	var $detection;
39
	/**
40
	 * @var setup_process
41
	 */
42
	var $process;
43
	/**
44
	 * @var setup_translation
45
	 */
46
	var $translation;
47
	/**
48
	 * @var setup_html
49
	 */
50
	var $html;
51
52
	var $system_charset;
53
	var $lang;
54
55
	var $ConfigDomain;
56
57
	/* table name vars */
58
	var $tbl_apps;
59
	var $tbl_config;
60
	var $tbl_hooks;
61
62
	/**
63
	 * php version required by eGroupware
64
	 *
65
	 * @var float
66
	 */
67
	var $required_php_version = 7.1;
68
	/**
69
	 * PHP Version recommended for eGroupware
70
	 *
71
	 * @var string
72
	 */
73
	var $recommended_php_version = '7.3';
74
75
	function __construct($html=False, $translation=False)
76
	{
77
		// setup us as $GLOBALS['egw_setup'], as this gets used in our sub-objects
78
		$GLOBALS['egw_setup'] =& $this;
79
80
		if (!is_object($GLOBALS['egw']))
81
		{
82
			$GLOBALS['phpgw'] = $GLOBALS['egw'] = new Egw\Base();
83
		}
84
		$this->detection = new setup_detection();
85
		$this->process   = new setup_process();
86
87
		if (preg_match('/^[a-z0-9-]+$/i', $_REQUEST['system_charset'])) $this->system_charset = $_REQUEST['system_charset'];
88
89
		/* The setup application needs these */
90
		if ($html) $this->html = new setup_html();
91
		if ($translation) $this->translation = new setup_translation();
92
	}
93
94
	/**
95
	 * include api db class for the ConfigDomain and connect to the db
96
	 */
97
	function loaddb($connect_and_setcharset=true)
98
	{
99
		if(!isset($this->ConfigDomain) || empty($this->ConfigDomain))
100
		{
101
			$this->ConfigDomain = isset($_REQUEST['ConfigDomain']) ? $_REQUEST['ConfigDomain'] : $_POST['FormDomain'];
102
		}
103
		$GLOBALS['egw_info']['server']['db_type'] = $GLOBALS['egw_domain'][$this->ConfigDomain]['db_type'];
104
105
		if ($GLOBALS['egw_info']['server']['db_type'] == 'pgsql')
106
		{
107
			$GLOBALS['egw_info']['server']['db_persistent'] = False;
108
		}
109
110
		try {
111
			$GLOBALS['egw']->db = $this->db = new Api\Db\Deprecated($GLOBALS['egw_domain'][$this->ConfigDomain]);
112
			$this->db->connect();
113
		}
114
		catch (Exception $e) {
115
			unset($e);
116
			return;
117
		}
118
		$this->db->set_app(Api\Db::API_APPNAME);
119
120
		if ($connect_and_setcharset)
121
		{
122
			try {
123
				$this->set_table_names();		// sets/checks config- and applications-table-name
124
125
				// Set the DB's client charset if a system-charset is set
126
				if (($this->system_charset = $this->db->select($this->config_table,'config_value',array(
127
						'config_app'  => 'phpgwapi',
128
						'config_name' => 'system_charset',
129
					),__LINE__,__FILE__)->fetchColumn()))
130
				{
131
					$this->db_charset_was = $this->db->Link_ID->GetCharSet();	// needed for the update
132
133
					// we can NOT set the DB charset for mysql, if the api version < 1.0.1.019, as it would mess up the DB content!!!
134
					if (substr($this->db->Type,0,5) == 'mysql')	// we need to check the api version
135
					{
136
						$api_version = $this->db->select($this->applications_table,'app_version',array(
137
								'app_name'  => 'phpgwapi',
138
							),__LINE__,__FILE__)->fetchColumn();
139
					}
140
					if (!$api_version || !$this->alessthanb($api_version,'1.0.1.019'))
141
					{
142
						$this->db->Link_ID->SetCharSet($this->system_charset);
143
					}
144
				}
145
			}
146
			catch (Api\Db\Exception $e) {
147
				// table might not be created at that stage
148
			}
149
		}
150
	}
151
152
	/**
153
	* Set the domain used for cookies
154
	*
155
	* @return string domain
156
	*/
157
	static function cookiedomain()
158
	{
159
		// Use HTTP_X_FORWARDED_HOST if set, which is the case behind a none-transparent proxy
160
		$cookie_domain = Api\Header\Http::host();
161
162
		// remove port from HTTP_HOST
163
		$arr = null;
164
		if (preg_match("/^(.*):(.*)$/",$cookie_domain,$arr))
165
		{
166
			$cookie_domain = $arr[1];
167
		}
168
		if (count(explode('.',$cookie_domain)) <= 1)
169
		{
170
			// setcookie dont likes domains without dots, leaving it empty, gets setcookie to fill the domain in
171
			$cookie_domain = '';
172
		}
173
		return $cookie_domain;
174
	}
175
176
	/**
177
	* Set a cookie
178
	*
179
	* @param string $cookiename name of cookie to be set
180
	* @param string $cookievalue value to be used, if unset cookie is cleared (optional)
181
	* @param int $cookietime when cookie should expire, 0 for session only (optional)
182
	*/
183
	function set_cookie($cookiename,$cookievalue='',$cookietime=0)
184
	{
185
		if(!isset($this->cookie_domain))
186
		{
187
			$this->cookie_domain = self::cookiedomain();
188
		}
189
		setcookie($cookiename, $cookievalue, $cookietime, '/', $this->cookie_domain,
190
			// if called via HTTPS, only send cookie for https and only allow cookie access via HTTP (true)
191
			Api\Header\Http::schema() === 'https', true);
192
	}
193
194
	/**
195
	 * Get configuration language from $_POST or $_SESSION and validate it
196
	 *
197
	 * @param boolean $use_accept_language =false true: use Accept-Language header as fallback
198
	 * @return string
199
	 */
200
	static function get_lang($use_accept_language=false)
201
	{
202
		if (isset($_POST['ConfigLang']))
203
		{
204
			$ConfigLang = $_POST['ConfigLang'];
205
		}
206
		else
207
		{
208
			if (!isset($_SESSION)) self::session_start();
209
			$ConfigLang = $_SESSION['ConfigLang'];
210
		}
211
		$matches = null;
212
		if ($use_accept_language && empty($ConfigLang) &&
213
			preg_match_all('/(^|,)([a-z-]+)/', strtolower($_SERVER['HTTP_ACCEPT_LANGUAGE']), $matches))
214
		{
215
			$available = Api\Translation::get_available_langs(false);
216
			foreach($matches[2] as $lang)
217
			{
218
				if (isset($available[$lang]))
219
				{
220
					$ConfigLang = $lang;
221
					break;
222
				}
223
			}
224
		}
225
		if (!preg_match('/^[a-z]{2}(-[a-z]{2})?$/',$ConfigLang))
226
		{
227
			$ConfigLang = null;	// not returning 'en', as it suppresses the language selection in check_install and manageheader
228
		}
229
		//error_log(__METHOD__."() \$_POST['ConfigLang']=".array2string($_POST['ConfigLang']).", \$_SESSION['ConfigLang']=".array2string($_SESSION['ConfigLang']).", HTTP_ACCEPT_LANGUAGE=$_SERVER[HTTP_ACCEPT_LANGUAGE] --> returning ".array2string($ConfigLang));
230
		return $ConfigLang;
231
	}
232
233
	/**
234
	 * Name of session cookie
235
	 */
236
	const SESSIONID = 'sessionid';
237
238
	/**
239
	 * Session timeout in secs (1200 = 20min)
240
	 */
241
	const TIMEOUT = 1200;
242
243
	/**
244
	 * Start session
245
	 */
246
	private static function session_start()
247
	{
248
		// make sure we have session extension available, otherwise fail with exception that we need it
249
		check_load_extension('session', true);
250
251
		switch(session_status())
252
		{
253
			case PHP_SESSION_DISABLED:
254
				throw new \ErrorException('EGroupware requires PHP session extension!');
255
			case PHP_SESSION_NONE:
256
				ini_set('session.use_cookie', true);
257
				session_name(self::SESSIONID);
258
				session_set_cookie_params(0, '/', self::cookiedomain(),
259
					// if called via HTTPS, only send cookie for https and only allow cookie access via HTTP (true)
260
					Api\Header\Http::schema() === 'https', true);
261
262
				if (isset($_COOKIE[self::SESSIONID])) session_id($_COOKIE[self::SESSIONID]);
263
264
				$ok = @session_start();	// suppress notice if session already started or warning in CLI
265
				break;
266
			case PHP_SESSION_ACTIVE:
267
				$ok = true;
268
		}
269
		// need to decrypt session, in case session encryption is switched on in header.inc.php
270
		Api\Session::decrypt();
271
		//error_log(__METHOD__."() returning ".array2string($ok).' _SESSION='.array2string($_SESSION));
272
		return $ok;
273
	}
274
275
	/**
276
	 * authenticate the setup user
277
	 *
278
	 * @param string $_auth_type ='config' 'config' or 'header' (caseinsensitiv)
279
	 */
280
	function auth($_auth_type='config')
281
	{
282
		$auth_type = strtolower($_auth_type);
283
		$GLOBALS['egw_info']['setup']['HeaderLoginMSG'] = $GLOBALS['egw_info']['setup']['ConfigLoginMSG'] = '';
284
285
		if(!$this->checkip(isset($_SERVER['HTTP_X_FORWARDED_FOR']) ?
286
			$_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']))
287
		{
288
			//error_log(__METHOD__."('$auth_type') invalid IP");
289
			return false;
290
		}
291
292
		if (!self::session_start() || isset($_REQUEST['FormLogout']) ||
293
			empty($_SESSION['egw_setup_auth_type']) || $_SESSION['egw_setup_auth_type'] != $auth_type)
294
		{
295
			//error_log(__METHOD__."('$auth_type') \$_COOKIE['".self::SESSIONID."'] = ".array2string($_COOKIE[self::SESSIONID]).", \$_SESSION=".array2string($_SESSION).", \$_POST=".array2string($_POST));
296
			if (isset($_REQUEST['FormLogout']))
297
			{
298
				$this->set_cookie(self::SESSIONID, '', time()-86400);
299
				session_destroy();
300
				if ($_REQUEST['FormLogout'] == 'config')
301
				{
302
					$GLOBALS['egw_info']['setup']['ConfigLoginMSG'] = lang('You have successfully logged out');
303
				}
304
				else
305
				{
306
					$GLOBALS['egw_info']['setup']['HeaderLoginMSG'] = lang('You have successfully logged out');
307
				}
308
				return false;
309
			}
310
			if (!isset($_POST['FormUser']) || !isset($_POST['FormPW']))
311
			{
312
				return false;
313
			}
314
			switch($auth_type)
315
			{
316
				case 'config':
317
					if (!isset($GLOBALS['egw_domain'][$_POST['FormDomain']]) ||
318
						!$this->check_auth($_POST['FormUser'], $_POST['FormPW'],
319
							$GLOBALS['egw_domain'][$_POST['FormDomain']]['config_user'],
320
							$GLOBALS['egw_domain'][$_POST['FormDomain']]['config_passwd']) &&
321
						!$this->check_auth($_POST['FormUser'], $_POST['FormPW'],
322
							$GLOBALS['egw_info']['server']['header_admin_user'],
323
							$GLOBALS['egw_info']['server']['header_admin_password']))
324
					{
325
						//error_log(__METHOD__."() Invalid password: $_POST[FormDomain]: used $_POST[FormUser]/md5($_POST[FormPW])=".md5($_POST['FormPW'])." != {$GLOBALS['egw_info']['server']['header_admin_user']}/{$GLOBALS['egw_info']['server']['header_admin_password']}");
326
						$GLOBALS['egw_info']['setup']['ConfigLoginMSG'] = lang('Invalid password');
327
						return false;
328
					}
329
					$this->ConfigDomain = $_SESSION['egw_setup_domain'] = $_POST['FormDomain'];
330
					break;
331
332
				default:
333
				case 'header':
334
					if (!$this->check_auth($_POST['FormUser'], $_POST['FormPW'],
335
								$GLOBALS['egw_info']['server']['header_admin_user'],
336
								$GLOBALS['egw_info']['server']['header_admin_password']))
337
					{
338
						$GLOBALS['egw_info']['setup']['ConfigLoginMSG'] = lang('Invalid password');
339
						return false;
340
					}
341
					break;
342
			}
343
			$_SESSION['egw_setup_auth_type'] = $auth_type;
344
			$_SESSION['ConfigLang'] = self::get_lang();
345
			$_SESSION['egw_last_action_time'] = time();
346
			session_regenerate_id(true);
347
			$this->set_cookie(self::SESSIONID, session_id(), 0);
348
349
			return true;
350
		}
351
		//error_log(__METHOD__."('$auth_type') \$_COOKIE['".self::SESSIONID."'] = ".array2string($_COOKIE[self::SESSIONID]).", \$_SESSION=".array2string($_SESSION));
352
		if ($_SESSION['egw_last_action_time'] < time() - self::TIMEOUT)
353
		{
354
			$this->set_cookie(self::SESSIONID, '', time()-86400);
355
			session_destroy();
356
			$GLOBALS['egw_info']['setup'][$_SESSION['egw_setup_auth_type'] == 'config' ? 'ConfigLoginMSG' : 'HeaderLoginMSG'] =
357
				lang('Session expired');
358
			return false;
359
		}
360
		if ($auth_type == 'config')
361
		{
362
			$this->ConfigDomain = $_SESSION['egw_setup_domain'];
363
		}
364
		// update session timeout
365
		$_SESSION['egw_last_action_time'] = time();
366
367
		// we have a valid session for $auth_type
368
		return true;
369
	}
370
371
    /**
372
    * check if username and password is valid
373
    *
374
    * this function compares the supplied and stored username and password
375
	* as any of the passwords can be clear text or md5 we convert them to md5
376
	* internal and compare always the md5 hashs
377
    *
378
    * @param string $user the user supplied username
379
    * @param string $pw the user supplied password
380
    * @param string $conf_user the configured username
381
    * @param string $hash hash to check password agains (no {prefix} for plain and md5!)
382
    * @returns bool true on success
383
    */
384
	static function check_auth($user, $pw, $conf_user, $hash)
385
	{
386
		if ($user !== $conf_user)
387
		{
388
			$ret = false; // wrong username
389
		}
390
		else
391
		{
392
			// support for old plain-text passwords without "{plain}" prefix
393
			if ($hash[0] !== '{' && !preg_match('/^[0-9a-f]{32}$/', $hash))
394
			{
395
				$hash = '{plain}'.$hash;
396
			}
397
			$ret = Api\Auth::compare_password($pw, $hash, 'md5');
398
		}
399
		//error_log(__METHOD__."('$user', '$pw', '$conf_user', '$hash') returning ".array2string($ret));
400
		return $ret;
401
	}
402
403
	/**
404
	 * Check for correct IP, if an IP address should be enforced
405
	 *
406
	 * @param string $remoteip
407
	 * @return boolean
408
	 */
409
	function checkip($remoteip='')
410
	{
411
		//echo "<p>setup::checkip($remoteip) against setup_acl='".$GLOBALS['egw_info']['server']['setup_acl']."'</p>\n";
412
		$allowed_ips = explode(',',@$GLOBALS['egw_info']['server']['setup_acl']);
413
		if(empty($GLOBALS['egw_info']['server']['setup_acl']) || !is_array($allowed_ips))
414
		{
415
			return True;	// no test
416
		}
417
		$remotes = explode('.',$remoteip);
418
		foreach($allowed_ips as $value)
419
		{
420
			if (!preg_match('/^[0-9.]+$/',$value))
421
			{
422
				$value = gethostbyname($was=$value);		// resolve domain-name, eg. a dyndns account
423
				//echo "resolving '$was' to '$value'<br>\n";
424
			}
425
			$values = explode('.',$value);
426
			for($i = 0; $i < count($values); ++$i)
427
			{
428
				if ((int) $values[$i] != (int) $remotes[$i])
429
				{
430
					break;
431
				}
432
			}
433
			if ($i == count($values))
434
			{
435
				return True;	// match
436
			}
437
		}
438
		$GLOBALS['egw_info']['setup']['ConfigLoginMSG'] = lang('Invalid IP address').' '.$remoteip;
439
		error_log(__METHOD__.'-> checking IP failed:'.print_r($remoteip,true));
440
		return False;
441
	}
442
443
	/**
444
	 * Return X.X.X major version from X.X.X.X versionstring
445
	 *
446
	 * @param string $versionstring
447
	 */
448
	function get_major($versionstring)
449
	{
450
		if(!$versionstring)
451
		{
452
			return False;
453
		}
454
455
		$version = str_replace('pre','.',$versionstring);
456
		$varray  = explode('.',$version);
457
		$major   = implode('.',array($varray[0],$varray[1],$varray[2]));
458
459
		return $major;
460
	}
461
462
	/**
463
	 * Clear system/user level cache so as to have it rebuilt with the next access
464
	 *
465
	 * @deprecated AFAIK this code is not used anymore -- RalfBecker 2005/11/04
466
	 */
467
	function clear_session_cache()
468
	{
469
	}
470
471
	/**
472
	 * Add an application to the phpgw_applications table
473
	 *
474
	 * @param string $appname Application 'name' with a matching $setup_info[$appname] array slice
475
	 * @param $_enable =99 set to True/False to override setup.inc.php setting
476
	 * @param array $setup_info =null default use $GLOBALS['setup_info']
477
	 */
478
	function register_app($appname, $_enable=99, array $setup_info=null)
479
	{
480
		if (!isset($setup_info)) $setup_info = $GLOBALS['setup_info'];
481
482
		if(!$appname)
483
		{
484
			return False;
485
		}
486
487
		if($_enable == 99)
488
		{
489
			$_enable = $setup_info[$appname]['enable'];
490
		}
491
		$enable = (int)$_enable;
492
493
		if($GLOBALS['DEBUG'])
494
		{
495
			echo '<br>register_app(): ' . $appname . ', version: ' . $setup_info[$appname]['version'] . ', tables: ' . implode(', ',$setup_info[$appname]['tables']) . '<br>';
496
			// _debug_array($setup_info[$appname]);
497
		}
498
499
		if($setup_info[$appname]['version'])
500
		{
501
			if($setup_info[$appname]['tables'])
502
			{
503
				$tables = implode(',',$setup_info[$appname]['tables']);
504
			}
505
			if ($setup_info[$appname]['tables_use_prefix'] == True)
506
			{
507
				if($GLOBALS['DEBUG'])
508
				{
509
					echo "<br>$appname uses tables_use_prefix, storing ". $setup_info[$appname]['tables_prefix']." as prefix for tables\n";
510
				}
511
				$this->db->insert($this->config_table,array(
512
						'config_app'	=> $appname,
513
						'config_name'	=> $appname.'_tables_prefix',
514
						'config_value'	=> $setup_info[$appname]['tables_prefix'],
515
					),False,__LINE__,__FILE__);
516
			}
517
			try {
518
				$this->db->insert($this->applications_table,array(
519
						'app_name'		=> $appname,
520
						'app_enabled'	=> $enable,
521
						'app_order'		=> $setup_info[$appname]['app_order'],
522
						'app_tables'	=> (string)$tables,	// app_tables is NOT NULL
523
						'app_version'	=> $setup_info[$appname]['version'],
524
						'app_index'     => $setup_info[$appname]['index'],
525
						'app_icon'      => $setup_info[$appname]['icon'],
526
						'app_icon_app'  => $setup_info[$appname]['icon_app'],
527
					),False,__LINE__,__FILE__);
528
			}
529
			catch (Api\Db\Exception\InvalidSql $e)
530
			{
531
				// ease update from pre 1.6 eg. 1.4 not having app_index, app_icon, app_icon_app columns
532
				_egw_log_exception($e);
533
534
				$this->db->insert($this->applications_table,array(
535
						'app_name'		=> $appname,
536
						'app_enabled'	=> $enable,
537
						'app_order'		=> $setup_info[$appname]['app_order'],
538
						'app_tables'	=> (string)$tables,	// app_tables is NOT NULL
539
						'app_version'	=> $setup_info[$appname]['version'],
540
					),False,__LINE__,__FILE__);
541
			}
542
			Api\Egw\Applications::invalidate();
543
		}
544
	}
545
546
	/**
547
	 * Check if an application has info in the db
548
	 *
549
	 * @param	$appname	Application 'name' with a matching $setup_info[$appname] array slice
550
	 * @param	$enabled	optional, set to False to not enable this app
551
	 */
552
	function app_registered($appname)
553
	{
554
		if(!$appname)
555
		{
556
			return False;
557
		}
558
559
		if(@$GLOBALS['DEBUG'])
560
		{
561
			echo '<br>app_registered(): checking ' . $appname . ', table: ' . $this->applications_table;
562
			// _debug_array($setup_info[$appname]);
563
		}
564
565
		if ($this->db->select($this->applications_table,'COUNT(*)',array('app_name' => $appname),__LINE__,__FILE__)->fetchColumn())
566
		{
567
			if(@$GLOBALS['DEBUG'])
568
			{
569
				echo '... app previously registered.';
570
			}
571
			return True;
572
		}
573
		if(@$GLOBALS['DEBUG'])
574
		{
575
			echo '... app not registered';
576
		}
577
		return False;
578
	}
579
580
	/**
581
	 * Update application info in the db
582
	 *
583
	 * @param string $appname	Application 'name' with a matching $setup_info[$appname] array slice
584
	 * @param array $setup_info =null default use $GLOBALS['setup_info']
585
	 */
586
	function update_app($appname, array $setup_info=null)
587
	{
588
		if (!isset($setup_info)) $setup_info = $GLOBALS['setup_info'];
589
590
		if(!$appname)
591
		{
592
			return False;
593
		}
594
595
		if($GLOBALS['DEBUG'])
596
		{
597
			echo '<br>update_app(): ' . $appname . ', version: ' . $setup_info[$appname]['currentver'] . ', table: ' . $this->applications_table . '<br>';
598
			// _debug_array($setup_info[$appname]);
599
		}
600
601
		if(!$this->app_registered($appname))
602
		{
603
			return False;
604
		}
605
606
		if($setup_info[$appname]['version'])
607
		{
608
			//echo '<br>' . $setup_info[$appname]['version'];
609
			if($setup_info[$appname]['tables'])
610
			{
611
				$tables = implode(',',$setup_info[$appname]['tables']);
612
			}
613
			$this->db->update($this->applications_table,array(
614
					'app_enabled'	=> $setup_info[$appname]['enable'],
615
					'app_order'		=> $setup_info[$appname]['app_order'],
616
					'app_tables'	=> (string)$tables,	// app_tables is NOT NULL
617
					'app_version'	=> $setup_info[$appname]['version'],
618
					'app_index'     => $setup_info[$appname]['index'],
619
					'app_icon'      => $setup_info[$appname]['icon'],
620
					'app_icon_app'  => $setup_info[$appname]['icon_app'],
621
				),array('app_name'=>$appname),__LINE__,__FILE__);
622
623
			Api\Egw\Applications::invalidate();
624
		}
625
	}
626
627
	/**
628
	 * Update application version in applications table, post upgrade
629
	 *
630
	 * @param	$setup_info		 * Array of application information (multiple apps or single)
631
	 * @param	$appname		 * Application 'name' with a matching $setup_info[$appname] array slice
632
	 * @param	$tableschanged	???
633
	 */
634
	function update_app_version($setup_info, $appname, $tableschanged = True)
635
	{
636
		if(!$appname)
637
		{
638
			return False;
639
		}
640
641
		if($tableschanged == True)
642
		{
643
			$GLOBALS['egw_info']['setup']['tableschanged'] = True;
644
		}
645
		if($setup_info[$appname]['currentver'])
646
		{
647
			$this->db->update($this->applications_table,array(
648
					'app_version'	=> $setup_info[$appname]['currentver'],
649
				),array('app_name'=>$appname),__LINE__,__FILE__);
650
651
			Api\Egw\Applications::invalidate();
652
		}
653
		return $setup_info;
654
	}
655
656
	/**
657
	 * de-Register an application
658
	 *
659
	 * @param	$appname	Application 'name' with a matching $setup_info[$appname] array slice
660
	 */
661
	function deregister_app($appname)
662
	{
663
		if(!$appname)
664
		{
665
			return False;
666
		}
667
668
		// Remove categories
669
		$this->db->delete(Api\Categories::TABLE, array('cat_appname'=>$appname),__LINE__,__FILE__);
670
		Api\Categories::invalidate_cache($appname);
671
672
		// Remove config, if we are not deinstalling old phpgwapi (as that's global api config!)
673
		if ($appname != 'phpgwapi')
674
		{
675
			$this->db->delete(Api\Config::TABLE, array('config_app'=>$appname),__LINE__,__FILE__);
676
		}
677
		//echo 'DELETING application: ' . $appname;
678
		$this->db->delete($this->applications_table,array('app_name'=>$appname),__LINE__,__FILE__);
679
680
		Api\Egw\Applications::invalidate();
681
682
		// Remove links to the app
683
		Link::unlink(0, $appname);
684
	}
685
686
	/**
687
	 * Register an application's hooks
688
	 *
689
	 * @param	$appname	Application 'name' with a matching $setup_info[$appname] array slice
690
	 */
691
	function register_hooks($appname)
692
	{
693
		if(!$appname)
694
		{
695
			return False;
696
		}
697
698
		Api\Hooks::read(true);
699
	}
700
701
	/**
702
	 * Setup default and forced preferences, when an application gets installed
703
	 *
704
	 * @param string $appname
705
	 * @return boolean false app not found or no hook settings, true settings found and defaull & forced prefs stored, if there are any defined
706
	 */
707
	function set_default_preferences($appname)
708
	{
709
		$setup_info = $GLOBALS['setup_info'][$appname];
710
711
		if (!isset($setup_info) || !isset($setup_info['hooks']))
712
		{
713
			return false;	// app not found or no hook
714
		}
715
		$GLOBALS['settings'] = array();
716
		$hook_data = array('location' => 'settings','setup' => true);
717
		if (isset($setup_info['hooks']['settings']))
718
		{
719
			$settings = ExecMethod($setup_info['hooks']['settings'],$hook_data);
720
		}
0 ignored issues
show
Deprecated Code introduced by
The function ExecMethod() has been deprecated: use autoloadable class-names, instanciate and call method or use static methods ( Ignorable by Annotation )

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

720
		}/** @scrutinizer ignore-deprecated */ 

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
721
		elseif(in_array('settings',$setup_info['hooks']) && file_exists($file = EGW_INCLUDE_ROOT.'/'.$appname.'/inc/hook_settings.inc.php'))
722
		{
723
			include_once($file);
724
		}
725
		if (!isset($settings) || !is_array($settings))
726
		{
727
			$settings = $GLOBALS['settings'];	// old file hook or not updated new hook
728
		}
729
		if (!is_array($settings) || !count($settings))
730
		{
731
			return false;
732
		}
733
		// include idots template prefs for (common) preferences
734
		if ($appname == 'preferences' && (file_exists($file = EGW_INCLUDE_ROOT.'/pixelegg/hook_settings.inc.php') ||
735
			file_exists($file = EGW_INCLUDE_ROOT.'/jdots/hook_settings.inc.php') ||
736
			file_exists($file = EGW_INCLUDE_ROOT.'/phpgwapi/templates/idots/hook_settings.inc.php')))
737
		{
738
			$GLOBALS['settings'] = array();
739
			include_once($file);
740
			if ($GLOBALS['settings']) $settings = array_merge($settings,$GLOBALS['settings']);
741
		}
742
		$default = $forced = array();
743
		foreach($settings as $name => $setting)
744
		{
745
			if (isset($setting['default']))
746
			{
747
				$default[$name] = $setting['default'] === false && $setting['type'] === 'check' ? '0' : (string)$setting['default'];
748
			}
749
			if (isset($setting['forced']))
750
			{
751
				$forced[$name] = $setting['forced'] === false && $setting['type'] === 'check' ? '0' : (string)$setting['forced'];
752
			}
753
		}
754
		// store default/forced preferences, if any found
755
		$preferences = new Api\Preferences();
756
		$preferences->read_repository(false);
757
		foreach(array(
758
			'default' => $default,
759
			'forced'  => $forced,
760
		) as $type => $prefs)
761
		{
762
			if ($prefs)
763
			{
764
				foreach($prefs as $name => $value)
765
				{
766
					$preferences->add($appname == 'preferences' ? 'common' : $appname, $name, $value, $type);
767
				}
768
				$preferences->save_repository(false, $type);
769
				//error_log(__METHOD__."('$appname') storing ".($owner==preferences::DEFAULT_ID?'default':'forced')." prefs=".array2string($prefs));
770
			}
771
		}
772
		return true;
773
	}
774
775
	/**
776
	  * call the hooks for a single application
777
	  *
778
	  * @param $location hook location - required
779
	  * @param $appname application name - optional
780
	 */
781
	static function hook($location, $appname='')
782
	{
783
		return Api\Hooks::single($location,$appname,True,True);
784
	}
785
786
	/**
787
	 * egw version checking, is param 1 < param 2 in phpgw versionspeak?
788
	 * @param	$a	phpgw version number to check if less than $b
789
	 * @param	$b	phpgw version number to check $a against
790
	 * @return	True if $a < $b
791
	 */
792
	function alessthanb($a,$b,$DEBUG=False)
793
	{
794
		$num = array('1st','2nd','3rd','4th');
795
796
		if($DEBUG)
797
		{
798
			echo'<br>Input values: '
799
				. 'A="'.$a.'", B="'.$b.'"';
800
		}
801
		$newa = str_replace('pre','.',$a);
802
		$newb = str_replace('pre','.',$b);
803
		$testa = explode('.',$newa);
804
		if(@$testa[1] == '')
805
		{
806
			$testa[1] = 0;
807
		}
808
809
		$testb = explode('.',$newb);
810
		if(@$testb[1] == '')
811
		{
812
			$testb[1] = 0;
813
		}
814
		if(@$testb[3] == '')
815
		{
816
			$testb[3] = 0;
817
		}
818
		$less = 0;
819
820
		for($i=0;$i<count($testa);$i++)
821
		{
822
			if($DEBUG) { echo'<br>Checking if '. (int)$testa[$i] . ' is less than ' . (int)$testb[$i] . ' ...'; }
823
			if((int)$testa[$i] < (int)$testb[$i])
824
			{
825
				if ($DEBUG) { echo ' yes.'; }
826
				$less++;
827
				if($i<3)
828
				{
829
					/* Ensure that this is definitely smaller */
830
					if($DEBUG) { echo"  This is the $num[$i] octet, so A is definitely less than B."; }
831
					$less = 5;
832
					break;
833
				}
834
			}
835
			elseif((int)$testa[$i] > (int)$testb[$i])
836
			{
837
				if($DEBUG) { echo ' no.'; }
838
				$less--;
839
				if($i<2)
840
				{
841
					/* Ensure that this is definitely greater */
842
					if($DEBUG) { echo"  This is the $num[$i] octet, so A is definitely greater than B."; }
843
					$less = -5;
844
					break;
845
				}
846
			}
847
			else
848
			{
849
				if($DEBUG) { echo ' no, they are equal or of different length.'; }
850
				// makes sure eg. '1.0.0' is counted less the '1.0.0.xxx' !
851
				$less = count($testa) < count($testb) ? 1 : 0;
852
			}
853
		}
854
		if($DEBUG) { echo '<br>Check value is: "'.$less.'"'; }
855
		if($less>0)
856
		{
857
			if($DEBUG) { echo '<br>A is less than B'; }
858
			return True;
859
		}
860
		elseif($less<0)
861
		{
862
			if($DEBUG) { echo '<br>A is greater than B'; }
863
			return False;
864
		}
865
		else
866
		{
867
			if($DEBUG) { echo '<br>A is equal to B'; }
868
			return False;
869
		}
870
	}
871
872
	/**
873
	 * egw version checking, is param 1 > param 2 in phpgw versionspeak?
874
	 *
875
	 * @param	$a	phpgw version number to check if more than $b
876
	 * @param	$b	phpgw version number to check $a against
877
	 * @return	True if $a < $b
878
	 */
879
	function amorethanb($a,$b,$DEBUG=False)
880
	{
881
		$num = array('1st','2nd','3rd','4th');
882
883
		if($DEBUG)
884
		{
885
			echo'<br>Input values: '
886
				. 'A="'.$a.'", B="'.$b.'"';
887
		}
888
		$newa = str_replace('pre','.',$a);
889
		$newb = str_replace('pre','.',$b);
890
		$testa = explode('.',$newa);
891
		if($testa[3] == '')
892
		{
893
			$testa[3] = 0;
894
		}
895
		$testb = explode('.',$newb);
896
		if($testb[3] == '')
897
		{
898
			$testb[3] = 0;
899
		}
900
		$less = 0;
901
902
		for($i=0;$i<count($testa);$i++)
903
		{
904
			if($DEBUG) { echo'<br>Checking if '. (int)$testa[$i] . ' is more than ' . (int)$testb[$i] . ' ...'; }
905
			if((int)$testa[$i] > (int)$testb[$i])
906
			{
907
				if($DEBUG) { echo ' yes.'; }
908
				$less++;
909
				if($i<3)
910
				{
911
					/* Ensure that this is definitely greater */
912
					if($DEBUG) { echo"  This is the $num[$i] octet, so A is definitely greater than B."; }
913
					$less = 5;
914
					break;
915
				}
916
			}
917
			elseif((int)$testa[$i] < (int)$testb[$i])
918
			{
919
				if($DEBUG) { echo ' no.'; }
920
				$less--;
921
				if($i<2)
922
				{
923
					/* Ensure that this is definitely smaller */
924
					if($DEBUG) { echo"  This is the $num[$i] octet, so A is definitely less than B."; }
925
					$less = -5;
926
					break;
927
				}
928
			}
929
			else
930
			{
931
				if($DEBUG) { echo ' no, they are equal.'; }
932
				$less = 0;
933
			}
934
		}
935
		if($DEBUG) { echo '<br>Check value is: "'.$less.'"'; }
936
		if($less>0)
937
		{
938
			if($DEBUG) { echo '<br>A is greater than B'; }
939
			return True;
940
		}
941
		elseif($less<0)
942
		{
943
			if($DEBUG) { echo '<br>A is less than B'; }
944
			return False;
945
		}
946
		else
947
		{
948
			if($DEBUG) { echo '<br>A is equal to B'; }
949
			return False;
950
		}
951
	}
952
953
	/**
954
	 * Own instance of the accounts class
955
	 *
956
	 * @var Api\Accounts
957
	 */
958
	var $accounts;
959
960
	function setup_account_object(array $config=array())
961
	{
962
		if (!isset($this->accounts) || $this->accounts->config || $config)
963
		{
964
			if (!is_object($this->db))
965
			{
966
				$this->loaddb();
967
			}
968
			if (!$config)
969
			{
970
				// load the configuration from the database
971
				foreach($this->db->select($this->config_table,'config_name,config_value',
972
					"config_name LIKE 'ads%' OR config_name LIKE 'ldap%' OR config_name LIKE 'account_%' OR config_name LIKE '%encryption%' OR config_name='auth_type'",
973
					__LINE__,__FILE__) as $row)
974
				{
975
					$GLOBALS['egw_info']['server'][$row['config_name']] = $config[$row['config_name']] = $row['config_value'];
976
				}
977
			}
978
			try {
979
				$this->accounts = new Api\Accounts($config);
980
			}
981
			catch (Exception $e) {
982
				echo "<p><b>".$e->getMessage()."</b></p>\n";
983
				return false;
984
			}
985
			if (!isset($GLOBALS['egw']->accounts)) $GLOBALS['egw']->accounts = $this->accounts;
986
			Api\Accounts::cache_invalidate();	// the cache is shared for all instances of the class
987
		}
988
		return true;
989
	}
990
991
	/**
992
	 * Used to store and share password of user "anonymous" between calls to add_account from different app installs
993
	 * @var unknown
994
	 */
995
	protected $anonpw;
996
997
	/**
998
	 * add an user account or a user group
999
	 *
1000
	 * If the $username already exists, only the id is returned, no new user / group gets created,
1001
	 * but if a non-empty password is given it will be changed to that.
1002
	 *
1003
	 * @param string $username alphanumerical username or groupname (account_lid)
1004
	 * @param string $first first name
1005
	 * @param string $last last name
1006
	 * @param string $_passwd cleartext pw or empty or '*unchanged*', to not change pw for existing users
1007
	 * @param string/boolean $primary_group Groupname for users primary group or False for a group, default 'Default'
1008
	 * @param boolean $changepw user has right to change pw, default False = Pw change NOT allowed
1009
	 * @param string $email
1010
	 * @param string &$anonpw=null on return password for anonymous user
1011
	 * @return int the numerical user-id
1012
	 */
1013
	function add_account($username,$first,$last,$_passwd,$primary_group='Default',$changepw=False,$email='',&$anonpw=null)
1014
	{
1015
		$this->setup_account_object();
1016
1017
		$primary_group_id = $primary_group ? $this->accounts->name2id($primary_group) : False;
1018
1019
		$passwd = $_passwd;
1020
		if ($username == 'anonymous')
1021
		{
1022
			if (!isset($this->anonpw)) $this->anonpw = Api\Auth::randomstring(16);
1023
			$passwd = $anonpw = $this->anonpw;
1024
		}
1025
1026
		if(!($accountid = $this->accounts->name2id($username, 'account_lid', $primary_group ? 'u' : 'g')))
1027
		{
1028
			$account = array(
1029
				'account_type'      => $primary_group ? 'u' : 'g',
1030
				'account_lid'       => $username,
1031
				'account_passwd'    => $passwd,
1032
				'account_firstname' => $first,
1033
				'account_lastname'  => $last,
1034
				'account_status'    => 'A',
1035
				'account_primary_group' => $primary_group_id,
1036
				'account_expires'   => -1,
1037
				'account_email'     => $email,
1038
				'account_members'   => array()
1039
			);
1040
1041
			// check if egw_accounts.account_description already exists, as the update otherwise fails
1042
			$meta = $GLOBALS['egw_setup']->db->metadata('egw_accounts', true);
1043
			if (!isset($meta['meta']['account_description']))
1044
			{
1045
				$GLOBALS['egw_setup']->oProc->AddColumn('egw_accounts','account_description',array(
1046
					'type' => 'varchar',
1047
					'precision' => '255',
1048
					'comment' => 'group description'
1049
				));
1050
			}
1051
1052
			if (!($accountid = $this->accounts->save($account)))
1053
			{
1054
				error_log("setup::add_account('$username','$first','$last',\$passwd,'$primary_group',$changepw,'$email') failed! accountid=$accountid");
1055
				return false;
1056
			}
1057
		}
1058
		// set password for existing account, if given and not '*unchanged*'
1059
		elseif($_passwd && $_passwd != '*unchanged*')
1060
		{
1061
			try {
1062
				$auth = new Api\Auth;
1063
				$pw_changed = $auth->change_password(null, $passwd, $accountid);
1064
			}
1065
			catch (Exception $e)
1066
			{
1067
				unset($e);
1068
				$pw_changed = false;
1069
			}
1070
			if (!$pw_changed)
1071
			{
1072
				error_log("setup::add_account('$username','$first','$last',\$passwd,'$primary_group',$changepw,'$email') changin password of exisiting account #$accountid failed!");
1073
				return false;
1074
			}
1075
		}
1076
		// call Vfs\Hooks::add{account|group} hook to create the vfs-home-dirs
1077
		// calling general add{account|group} hook fails, as we are only in setup
1078
		// --> setup_cmd_admin execs "admin/admin-cli.php --edit-user" to run them
1079
		if ($primary_group)
1080
		{
1081
			Vfs\Hooks::addAccount(array(
1082
				'account_id' => $accountid,
1083
				'account_lid' => $username,
1084
			));
1085
		}
1086
		else
1087
		{
1088
			Vfs\Hooks::addGroup(array(
1089
				'account_id' => $accountid,
1090
				'account_lid' => $username,
1091
			));
1092
		}
1093
		if ($primary_group)	// only for users, NOT groups
1094
		{
1095
			$this->set_memberships(array($primary_group_id), $accountid);
1096
1097
			if (!$changepw) $this->add_acl('preferences','nopasswordchange',$accountid);
1098
		}
1099
		//error_log("setup::add_account('$username','$first','$last',\$passwd,'$primary_group',$changepw,'$email') successfull created accountid=$accountid");
1100
		return $accountid;
1101
	}
1102
1103
	/**
1104
	 * Adding memberships to an account (keeping existing ones!)
1105
	 *
1106
	 * @param array $_groups array of group-id's to add
1107
	 * @param int $user account_id
1108
	 */
1109
	function set_memberships($_groups, $user)
1110
	{
1111
		$this->setup_account_object();
1112
1113
		if (!($groups = $this->accounts->memberships($user, true)))
1114
		{
1115
			$groups = $_groups;
1116
		}
1117
		else
1118
		{
1119
			$groups = array_unique(array_merge($groups, $_groups));
1120
		}
1121
		$this->accounts->set_memberships($groups, $user);
1122
	}
1123
1124
	/**
1125
	 * Check if accounts other then the automatically installed anonymous account exist
1126
	 *
1127
	 * We check via the account object, to deal with different account-storages
1128
	 *
1129
	 * @return boolean
1130
	 */
1131
	function accounts_exist()
1132
	{
1133
		try {
1134
			if (!$this->setup_account_object()) return false;
1135
1136
			$this->accounts->search(array(
1137
				'type'   => 'accounts',
1138
				'start'  => 0,
1139
				'offset' => 2	// we only need to check 2 accounts, if we just check for not anonymous
1140
			));
1141
1142
			if ($this->accounts->total != 1)
1143
			{
1144
				return $this->accounts->total > 1;
1145
			}
1146
1147
			// one account, need to check it's not the anonymous one
1148
			$this->accounts->search(array(
1149
				'type'   => 'accounts',
1150
				'start'  => 0,
1151
				'offset' => 0,
1152
				'query_type' => 'lid',
1153
				'query'  => 'anonymous',
1154
			));
1155
		}
1156
		catch (Api\Db\Exception $e) {
1157
			_egw_log_exception($e);
1158
			return false;
1159
		}
1160
		return !$this->accounts->total;
1161
	}
1162
1163
	/**
1164
	 * Add ACL rights
1165
	 *
1166
	 * Dont use it to set group-membership, use set_memberships instead!
1167
	 *
1168
	 * @param string|array $apps app-names
1169
	 * @param string $location eg. "run"
1170
	 * @param int|string $account accountid or account_lid
1171
	 * @param int $rights rights to set, default 1
1172
	 */
1173
	function add_acl($apps,$location,$account,$rights=1)
1174
	{
1175
		//error_log("setup::add_acl(".(is_array($apps) ? "array('".implode("','",$apps)."')" : "'$apps'").",'$location',$account,$rights)");
1176
		if (!is_numeric($account))
1177
		{
1178
			$this->setup_account_object();
1179
			$account = $this->accounts->name2id($account);
1180
		}
1181
		if(!is_object($this->db))
1182
		{
1183
			$this->loaddb();
1184
		}
1185
1186
		if(!is_array($apps))
1187
		{
1188
			$apps = array($apps);
1189
		}
1190
		foreach($apps as $app)
1191
		{
1192
			$this->db->delete($this->acl_table,array(
1193
				'acl_appname'  => $app,
1194
				'acl_location' => $location,
1195
				'acl_account'  => $account
1196
			),__LINE__,__FILE__);
1197
1198
			if ((int) $rights)
1199
			{
1200
				$this->db->insert($this->acl_table,array(
1201
					'acl_rights' => $rights
1202
				),array(
1203
					'acl_appname'  => $app,
1204
					'acl_location' => $location,
1205
					'acl_account'  => $account,
1206
				),__LINE__,__FILE__);
1207
			}
1208
		}
1209
	}
1210
1211
	/**
1212
	 * checks if one of the given tables exist, returns the first match
1213
	 *
1214
	 * @param array $tables array with possible table-names
1215
	 * @return string/boolean tablename or false
1216
	 */
1217
	function table_exist($tables,$force_refresh=False)
1218
	{
1219
		static $table_names = False;
1220
1221
		if (!$table_names || $force_refresh) $table_names = $this->db->table_names();
1222
1223
		if (!$table_names) return false;
1224
1225
		foreach($table_names as $data)
1226
		{
1227
			if (($key = array_search($data['table_name'],$tables)) !== false)
1228
			{
1229
				return $tables[$key];
1230
			}
1231
		}
1232
		return false;
1233
	}
1234
1235
	/**
1236
	 * Checks and set the names of the tables, which get accessed before an update: eg. config- and applications-table
1237
	 *
1238
	 * Other tables can always use the most up to date name
1239
	 */
1240
	function set_table_names($force_refresh=False)
1241
	{
1242
		foreach(array(
1243
			'config_table'       => array('egw_config','phpgw_config','config'),
1244
			'applications_table' => array('egw_applications','phpgw_applications','applications'),
1245
			'accounts_table'     => array('egw_accounts','phpgw_accounts'),
1246
			'acl_table'          => array('egw_acl','phpgw_acl'),
1247
			'lang_table'         => array('egw_lang','phpgw_lang','lang'),
1248
			'languages_table'    => array('egw_languages','phpgw_languages','languages'),
1249
		) as $name => $tables)
1250
		{
1251
			$table = $this->table_exist($tables,$force_refresh);
1252
1253
			if ($table && $table != $this->$name)	// only overwrite the default name, if we realy got one (important for new installs)
1254
			{
1255
				$this->$name = $table;
1256
			}
1257
			//echo "<p>setup::set_table_names: $name = '{$this->$name}'</p>\n";
1258
		}
1259
	}
1260
}
1261