1<?php
2/**
3 * EGgroupware setup - create or update the header.inc.php
4 *
5 * @link http://www.egroupware.org
6 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
7 * @package setup
8 * @copyright (c) 2007-19 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
9 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10 */
11
12use EGroupware\Api;
13
14/**
15 * setup command: create or update the header.inc.php
16 *
17 * @ToDo: incorporate setup_header here
18 */
19class setup_cmd_header extends setup_cmd
20{
21	/**
22	 * Instance of setup's header object
23	 *
24	 * @var setup_header
25	 */
26	private $setup_header;
27	/**
28	 * Full path of the header.inc.php
29	 *
30	 * @var string
31	 */
32	private $header_path;
33
34	/**
35	 * Constructor
36	 *
37	 * @param string|array $sub_command ='create' 'create','edit','delete'(-domain) or array with all arguments
38	 * @param array $arguments =null comand line arguments
39	 */
40	function __construct($sub_command='create',$arguments=null)
41	{
42		if (!is_array($sub_command))
43		{
44			$sub_command = array(
45				'sub_command' => $sub_command,
46				'arguments'   => $arguments,
47			);
48		}
49		//echo __CLASS__.'::__construct()'; _debug_array($domain);
50		admin_cmd::__construct($sub_command);
51
52		// header is 3 levels lower then this command in setup/inc
53		$this->header_path = dirname(dirname(__DIR__)).'/header.inc.php';
54
55		// if header is a symlink --> work on it's target
56		if (is_link($this->header_path))
57		{
58			$this->header_path = readlink($this->header_path);
59			if ($this->header_path[0] != '/' && $this->header_path[1] != ':')
60			{
61				$this->header_path = dirname(dirname(__DIR__)).'/'.$this->header_path;
62			}
63		}
64		$this->setup_header = new setup_header();
65	}
66
67	/**
68	 * Create or update header.inc.php
69	 *
70	 * @param boolean $check_only =false only run the checks (and throw the exceptions), but not the command itself
71	 * @return string serialized $GLOBALS defined in the header.inc.php
72	 * @throws Exception(lang('Wrong credentials to access the header.inc.php file!'),2);
73	 * @throws Exception('header.inc.php not found!');
74	 */
75	protected function exec($check_only=false)
76	{
77		if ($check_only && $this->remote_id)
78		{
79			return true;	// can only check locally
80		}
81		if (!file_exists($this->header_path) || filesize($this->header_path) < 200)	// redirect header in rpms is ~150 byte
82		{
83			if ($this->sub_command != 'create')
84			{
85				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);
86			}
87			$this->defaults(false);
88		}
89		else
90		{
91			if ($this->sub_command == 'create')
92			{
93				throw new Api\Exception\WrongUserinput(
94					lang('EGroupware configuration file header.inc.php already exists, you need to use --edit-header or delete it first!'),20);
95			}
96			if ($this->arguments)
97			{
98				list($this->header_admin_password,$this->header_admin_user) = explode(',',$this->arguments[1]);
99			}
100			$this->check_setup_auth($this->header_admin_user,$this->header_admin_password);	// no domain, we require header access!
101
102			$GLOBALS['egw_info']['server']['server_root'] = EGW_SERVER_ROOT;
103			$GLOBALS['egw_info']['server']['include_root'] = EGW_INCLUDE_ROOT;
104		}
105
106		if ($this->arguments)	// we have command line arguments
107		{
108			$this->_parse_cli_arguments();
109		}
110		elseif ($this->sub_command == 'delete')
111		{
112			self::_delete_domain($this->domain);
113		}
114		else
115		{
116			$this->_parse_properties();
117		}
118		if (($errors = $this->validation_errors($GLOBALS['egw_info']['server']['server_root'],
119			$GLOBALS['egw_info']['server']['include_root'])))
120		{
121			if ($this->arguments)
122			{
123				unset($GLOBALS['egw_info']['flags']);
124				echo '$GLOBALS[egw_info] = '; print_r($GLOBALS['egw_info']);
125				echo '$GLOBALS[egw_domain] = '; print_r($GLOBALS['egw_domain']);
126			}
127			throw new Api\Exception\WrongUserinput(lang('Configuration errors:')."\n- ".implode("\n- ",$errors)."\n".lang("You need to fix the above errors, before the configuration file header.inc.php can be written!"),23);
128		}
129		if ($check_only)
130		{
131			return true;
132		}
133		// check if php has persistent mysql connections disabled --> disable it in header, to not fill the log with warnings
134		if ($GLOBALS['egw_info']['server']['db_persistent'])
135		{
136			$GLOBALS['egw_info']['server']['db_persistent'] = $this->check_db_persistent($GLOBALS['egw_domain']);
137		}
138		$header = $this->generate($GLOBALS['egw_info'],$GLOBALS['egw_domain']);
139
140		if ($this->arguments)
141		{
142			echo $header;	// for cli, we echo the header
143		}
144		if (file_exists($this->header_path) && is_writable($this->header_path) || is_writable(dirname($this->header_path)) ||
145			function_exists('posix_getuid') && !posix_getuid())	// root has all rights
146		{
147			if (file_exists($this->header_path) && !is_writable($this->header_path))
148			{
149				unlink($this->header_path);
150			}
151			if (($f = fopen($this->header_path,'wb')) && fwrite($f,$header))
152			{
153				fclose($f);
154				return lang('header.inc.php successful written.');
155			}
156		}
157		throw new Api\Exception\NoPermission(lang("Failed writing configuration file header.inc.php, check the permissions !!!"),24);
158	}
159
160	/**
161	 * Magic method to allow to call all methods from setup_header, as if they were our own
162	 *
163	 * @param string $method
164	 * @param array $args =null
165	 * @return mixed
166	 */
167	function __call($method,array $args=null)
168	{
169		if (method_exists($this->setup_header,$method))
170		{
171			return call_user_func_array(array($this->setup_header,$method),$args);
172		}
173	}
174
175	/**
176	 * Available options and allowed arguments
177	 *
178	 * @var array
179	 */
180	static $options = array(
181		'--create-header' => array(
182			'header_admin_password' => 'egw_info/server/',
183			'header_admin_user' => 'egw_info/server/',
184		),
185		'--edit-header'   => array(
186			'header_admin_password' => 'egw_info/server/',
187			'header_admin_user' => 'egw_info/server/',
188			'new_admin_password' => 'egw_info/server/header_admin_password',
189			'new_admin_user' => 'egw_info/server/header_admin_user',
190		),
191		'--server-root'  => 'egw_info/server/server_root',
192		'--include-root' => 'egw_info/server/include_root',
193		'--session-type' => array(
194			'sessions_type' => array(
195				'type' => 'egw_info/server/',
196				'allowed' => array('php'=>'php4','php4'=>'php4','php-restore'=>'php4-restore','php4-restore'=>'php4-restore','db'=>'db'),
197			),
198		),
199		'--session-handler' => array(
200			'session_handler' => array(
201				'type' => 'egw_info/server/',
202				'allowed' => array('files'=>'files','memcache'=>'memcache','db'=>'db'),
203			),
204		),
205		'--limit-access' => 'egw_info/server/setup_acl',	// name used in setup
206		'--setup-acl'    => 'egw_info/server/setup_acl',	// alias to match the real name
207		'--mcrypt' => array(
208			'mcrypt_enabled' => array(
209				'type' => 'egw_info/server/',
210				'allowed' => array('on' => true,'off' => false),
211			),
212			'mcrypt_iv' => 'egw_info/server/',
213			'mcrypt' => 'egw_info/versions/mcrypt',
214		),
215		'--domain-selectbox' => array(
216			'show_domain_selectbox' => array(
217				'type' => 'egw_info/server/',
218				'allowed' => array('on' => true,'off' => false),
219			),
220		),
221		'--db-persistent' => array(
222			'db_persistent' => array(
223				'type' => 'egw_info/server/',
224				'allowed' => array('on' => true,'off' => false),
225			),
226		),
227		'--domain' => array(
228			'domain' => '@',
229			'db_name' => 'egw_domain/@/',
230			'db_user' => 'egw_domain/@/',
231			'db_pass' => 'egw_domain/@/',
232			'db_type' => 'egw_domain/@/',
233			'db_host' => 'egw_domain/@/',
234			'db_port' => 'egw_domain/@/',
235			'config_user'   => 'egw_domain/@/',
236			'config_passwd' => 'egw_domain/@/',
237		),
238		'--delete-domain' => true,
239	);
240
241	/**
242	 * Parses properties from this object
243	 */
244	private function _parse_properties()
245	{
246		foreach(self::$options as $arg => $option)
247		{
248			foreach(is_array($option) ? $option : array($option => $option) as $name => $data)
249			{
250				if (strpos($name,'/') !== false)
251				{
252					$name = array_pop($parts = explode('/',$name));
253				}
254				if (isset($this->$name))
255				{
256					$this->_parse_value($arg,$name,$data,$this->$name);
257				}
258			}
259		}
260	}
261
262	/**
263	 * Parses command line arguments in $this->arguments
264	 */
265	private function _parse_cli_arguments()
266	{
267		$arguments = $this->arguments;
268		while(($arg = array_shift($arguments)))
269		{
270			$values = count($arguments) && substr($arguments[0],0,2) !== '--' ? array_shift($arguments) : 'on';
271
272			if ($arg == '--delete-domain')
273			{
274				$this->_delete_domain($values);
275				continue;
276			}
277
278			if (!isset(self::$options[$arg]))
279			{
280				throw new Api\Exception\WrongUserinput(lang("Unknown option '%1' !!!",$arg),90);
281			}
282
283			$option = self::$options[$arg];
284			$vals = !is_array($option) ? array($values) : explode(',',$values);
285			if (!is_array($option)) $option = array($option => $option);
286			$n = 0;
287			foreach($option as $name => $data)
288			{
289				if ($n >= count($vals)) break;
290
291				$this->_parse_value($arg,$name,$data,$vals[$n++]);
292			}
293		}
294	}
295
296	/**
297	 * Delete a given domain/instance from the header
298	 *
299	 * @param string $domain
300	 */
301	private static function _delete_domain($domain)
302	{
303		if (!isset($GLOBALS['egw_domain'][$domain]))
304		{
305			throw new Api\Exception\WrongUserinput(lang("Domain '%1' does NOT exist !!!",$domain),92);
306		}
307		unset($GLOBALS['egw_domain'][$domain]);
308	}
309
310	/**
311	 * Parses a single value
312	 *
313	 * @param string $arg current cli argument processed
314	 * @param string $name name of the property
315	 * @param array/string $data string with type or array containing values for type, allowed
316	 * @param mixed $value value to set
317	 */
318	private function _parse_value($arg,$name,$data,$value)
319	{
320		static $domain=null;
321
322		if (!is_array($data)) $data = array('type' => $data);
323		$type = $data['type'];
324
325		if (isset($data['allowed']))
326		{
327			if (!isset($data['allowed'][$value]))
328			{
329				throw new Api\Exception\WrongUserinput(lang("'%1' is not allowed as %2. arguments of option %3 !!!",$value,1,$arg),91);
330			}
331			$value = $data['allowed'][$value];
332		}
333		if ($type == '@')
334		{
335			$domain = $arg == '--domain' && !$value ? 'default' : $value;
336			if ($arg == '--domain' && (!isset($GLOBALS['egw_domain'][$domain]) || $this->sub_command == 'create'))
337			{
338				$GLOBALS['egw_domain'][$domain] = $this->domain_defaults($GLOBALS['egw_info']['server']['header_admin_user'],$GLOBALS['egw_info']['server']['header_admin_password']);
339			}
340		}
341		elseif ($value !== '')
342		{
343			self::_set_value($GLOBALS,str_replace('@',$domain,$type),$name,$value);
344			if ($type == 'egw_info/server/server_root')
345			{
346				self::_set_value($GLOBALS,'egw_info/server/include_root',$name,$value);
347			}
348		}
349	}
350
351	/**
352	 * Set a value in the given array $arr with (multidimensional) key $index[/$name]
353	 *
354	 * @param array &$arr
355	 * @param string $index multidimensional index written with / as separator, eg. egw_info/server/
356	 * @param string $name additional index to use if $index end with a slash
357	 * @param mixed $value value to set
358	 */
359	static private function _set_value(&$arr,$index,$name,$value)
360	{
361		if (substr($index,-1) == '/') $index .= $name;
362
363		$var =& $arr;
364		foreach(explode('/',$index) as $name)
365		{
366			$var =& $var[$name];
367		}
368		if (true) $var = $value;
369	}
370}
371