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 13use EGroupware\Api; 14use EGroupware\Api\Egw; 15 16/** 17 * setup command: abstract baseclass for all setup commands, extending admin_cmd 18 */ 19abstract 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 36 { 37 if (!$this->uid) $this->uid = true; 38 $this->set_header_secret($this->header_admin_user,$this->header_admin_password); 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); 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); 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); 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); 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 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 ;-) 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) 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) 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