5 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
6 * @author Philipp Schüle <p.schuele@metaways.de>
7 * @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
9 * @todo add ext check again
15 * This class handles all requests from cli scripts
19 class Setup_Frontend_Cli
22 * the internal name of the application
26 protected $_appname = 'Setup';
31 * @param string $_username
32 * @param string $_password
36 public function authenticate($_username, $_password)
42 * handle request (call -ApplicationName-_Cli.-MethodName- or -ApplicationName-_Cli.getHelp)
44 * @param Zend_Console_Getopt $_opts
45 * @param boolean $exitAfterHandle
48 public function handle(Zend_Console_Getopt $_opts, $exitAfterHandle = true)
50 Setup_Core::set(Setup_Core::USER, 'setupuser');
53 if (isset($_opts->install)) {
54 $this->_install($_opts);
55 } elseif(isset($_opts->update)) {
56 $result = $this->_update($_opts);
57 } elseif(isset($_opts->uninstall)) {
58 $this->_uninstall($_opts);
59 } elseif(isset($_opts->install_dump)) {
60 $this->_installDump($_opts);
61 } elseif(isset($_opts->list)) {
62 $result = $this->_listInstalled();
63 } elseif(isset($_opts->sync_accounts_from_ldap)) {
64 $this->_importAccounts($_opts);
65 } elseif(isset($_opts->sync_passwords_from_ldap)) {
66 $this->_syncPasswords($_opts);
67 } elseif(isset($_opts->egw14import)) {
68 $this->_egw14Import($_opts);
69 } elseif(isset($_opts->check_requirements)) {
70 $this->_checkRequirements($_opts);
71 } elseif(isset($_opts->setconfig)) {
72 $this->_setConfig($_opts);
73 } elseif(isset($_opts->create_admin)) {
74 $this->_createAdminUser($_opts);
75 } elseif(isset($_opts->getconfig)) {
76 $this->_getConfig($_opts);
77 } elseif(isset($_opts->reset_demodata)) {
78 $this->_resetDemodata($_opts);
79 } elseif(isset($_opts->updateAllImportExportDefinitions)) {
80 $this->_updateAllImportExportDefinitions($_opts);
81 } elseif(isset($_opts->backup)) {
82 $this->_backup($_opts);
83 } elseif(isset($_opts->restore)) {
84 $this->_restore($_opts);
85 } elseif(isset($_opts->compare)) {
86 $this->_compare($_opts);
89 if ($exitAfterHandle) {
95 * install new applications
97 * @param Zend_Console_Getopt $_opts
99 protected function _install(Zend_Console_Getopt $_opts)
101 $controller = Setup_Controller::getInstance();
103 if($_opts->install === true) {
104 $applications = $controller->getInstallableApplications();
105 $applications = array_keys($applications);
107 $applications = array();
108 $applicationNames = explode(',', $_opts->install);
109 foreach($applicationNames as $applicationName) {
110 $applicationName = ucfirst(trim($applicationName));
112 $controller->getSetupXml($applicationName);
113 $applications[] = $applicationName;
114 } catch (Setup_Exception_NotFound $e) {
115 echo "Application $applicationName not found! Skipped...\n";
120 $options = $this->_parseRemainingArgs($_opts->getRemainingArgs());
121 $this->_promptRemainingOptions($applications, $options);
123 $controller->installApplications($applications, $options);
125 if ((isset($options['acceptedTermsVersion']) || array_key_exists('acceptedTermsVersion', $options))) {
126 Setup_Controller::getInstance()->saveAcceptedTerms($options['acceptedTermsVersion']);
129 echo "Successfully installed " . count($applications) . " applications.\n";
133 * prompt remaining options
135 * @param array $_applications
136 * @param array $_options
139 * @todo add required version server side
141 protected function _promptRemainingOptions($_applications, &$_options) {
142 if (in_array('Tinebase', $_applications)) {
144 if (! isset($_options['acceptedTermsVersion'])) {
145 fwrite(STDOUT, PHP_EOL . file_get_contents(dirname(dirname(dirname(__FILE__))) . '/LICENSE' ));
146 $licenseAnswer = Tinebase_Server_Cli::promptInput('I have read the license agreement and accept it (type "yes" to accept)');
149 fwrite(STDOUT, PHP_EOL . file_get_contents(dirname(dirname(dirname(__FILE__))) . '/PRIVACY' ));
150 $privacyAnswer = Tinebase_Server_Cli::promptInput('I have read the privacy agreement and accept it (type "yes" to accept)');
152 if (! (strtoupper($licenseAnswer) == 'YES' && strtoupper($privacyAnswer) == 'YES')) {
153 echo "error: you need to accept the terms! exiting \n";
157 $_options['acceptedTermsVersion'] = 1;
162 if (! isset($_options['adminLoginName'])) {
163 $_options['adminLoginName'] = Tinebase_Server_Cli::promptInput('Inital Admin Users Username');
164 if (! $_options['adminLoginName']) {
165 echo "error: username must be given! exiting \n";
170 // initial password / can be empty => will trigger password change dialogue
171 if (! array_key_exists('adminPassword', $_options)) {
172 $_options['adminPassword'] = $this->_promptPassword();
182 protected function _promptPassword()
184 $password1 = Tinebase_Server_Cli::promptInput('Admin user password', TRUE);
186 echo "Error: Password must not be empty! Exiting ... \n";
189 $password2 = Tinebase_Server_Cli::promptInput('Confirm password', TRUE);
190 if ($password1 !== $password2) {
191 echo "Error: Passwords do not match! Exiting ... \n";
199 * update existing applications
201 * @param Zend_Console_Getopt $_opts
204 protected function _update(Zend_Console_Getopt $_opts)
208 $result = $this->_updateApplications();
209 if ($_opts->v && ! empty($result['messages'])) {
211 foreach ($result['messages'] as $message) {
212 echo " " . $message . "\n";
216 } while ($result['updated'] > 0 && $maxLoops > 0);
218 return ($maxLoops > 0) ? 0 : 1;
222 * update all applications
226 protected function _updateApplications()
228 $controller = Setup_Controller::getInstance();
229 $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
231 foreach ($applications as $key => &$application) {
233 if (! $controller->updateNeeded($application)) {
234 unset($applications[$key]);
236 } catch (Setup_Exception_NotFound $e) {
237 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
238 . ' Failed to check if an application needs an update:' . $e->getMessage());
239 unset($applications[$key]);
244 if (count($applications) > 0) {
245 $result = $controller->updateApplications($applications);
246 echo "Updated " . $result['updated'] . " application(s).\n";
248 $result['updated'] = 0;
255 * uninstall applications
257 * @param Zend_Console_Getopt $_opts
259 protected function _uninstall(Zend_Console_Getopt $_opts)
261 $controller = Setup_Controller::getInstance();
263 if($_opts->uninstall === true) {
264 $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
266 $applications = new Tinebase_Record_RecordSet('Tinebase_Model_Application');
267 $applicationNames = explode(',', $_opts->uninstall);
268 foreach($applicationNames as $applicationName) {
269 $applicationName = ucfirst(trim($applicationName));
271 $application = Tinebase_Application::getInstance()->getApplicationByName($applicationName);
272 $applications->addRecord($application);
273 } catch (Tinebase_Exception_NotFound $e) {
278 $controller->uninstallApplications($applications->name);
280 echo "Successfully uninstalled " . count($applications) . " applications.\n";
284 * reinstall applications
286 * php setup.php --reset_demodata USERNAME
288 * @param Zend_Console_Getopt $_opts
290 protected function _resetDemodata(Zend_Console_Getopt $_opts)
292 $controller = Setup_Controller::getInstance();
293 $userController = Admin_Controller_User::getInstance();
294 $containerController = Tinebase_Container::getInstance();
295 $cli = new Tinebase_Frontend_Cli();
297 //Don't reset this applications
298 $fixedApplications = array('Tinebase', 'Admin', 'Addressbook');
301 $opts = $_opts->getRemainingArgs();
302 $username = $opts[0];
303 if (empty($username)) {
304 echo "Username is missing!\n";
307 $user = Tinebase_User::getInstance()->getUserByLoginName($username);
308 Tinebase_Core::set(Tinebase_Core::USER, $user);
310 //get all applications and remove some
311 $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
313 foreach ($applications as $key => &$application) {
314 if (in_array($application, $fixedApplications)) {
315 unset($applications[$key]);
320 $users = Tinebase_Acl_Roles::getInstance()->getRoleByName('user role');
321 $rights = Tinebase_Acl_Roles::getInstance()->getRoleRights($users->getId());
323 //Uninstall Applications
325 $controller->uninstallApplications($applications->name);
326 echo "Successfully uninstalled " . count($applications) . " applications.\n";
327 } catch (Tinebase_Exception_NotFound $e) {
329 //Install Applications
331 $controller->installApplications($applications->name);
332 echo "Successfully installed " . count($applications) . " applications.\n";
333 } catch (Tinebase_Exception_NotFound $e) {
337 foreach ($applications as $app) {
338 $newApplicationId = Tinebase_Application::getInstance()->getApplicationByName($app->name)->getId();
340 foreach ($rights as &$right) {
341 if ($right['application_id'] == $app->id) {
342 $right['application_id'] = $newApplicationId;
347 Tinebase_Acl_Roles::getInstance()->setRoleRights($users->getId(), $rights);
348 echo "Successfully restored user rights.\n";
350 //Clean up addressbooks
351 $internalContacts = $userController->getDefaultInternalAddressbook();
352 $containers = $containerController->getAll();
353 foreach ($containers as $key => &$container) {
354 if ($container->id == $internalContacts) {
358 $containerController->deleteContainer($container, true);
359 } catch (Exception $e) {
364 echo "Successfully cleand up containers.\n";
367 $db = Tinebase_Core::getDb();
368 $statement = "TRUNCATE TABLE " . $db->quoteIdentifier(SQL_TABLE_PREFIX . 'state');
369 $db->query($statement);
370 echo "Successfully truncated state table.\n";
373 $cli->createAllDemoData();
376 Tinebase_Core::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL);
377 echo "Successfully cleared Cache.\n";
379 echo "Every thing done!\n";
383 * Update Import Export Definitions for all applications
385 protected function _updateAllImportExportDefinitions(Zend_Console_Getopt $_opts){
387 $setupUser = Setup_Update_Abstract::getSetupFromConfigOrCreateOnTheFly();
388 if (! Tinebase_Core::getUser() instanceof Tinebase_Model_User) {
389 Tinebase_Core::set(Tinebase_Core::USER, $setupUser);
392 //get all applications
393 $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
394 foreach ($applications as $application) {
395 Setup_Controller::getInstance()->createImportExportDefinitions($application);
396 echo "Update definitions for " . $application->name . "...\n";
401 * list installed apps
403 protected function _listInstalled()
406 $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
407 } catch (Zend_Db_Statement_Exception $e) {
408 echo "No applications installed\n";
412 echo "Currently installed applications:\n";
413 foreach($applications as $application) {
414 echo "* $application\n";
421 * import accounts from ldap
423 * @param Zend_Console_Getopt $_opts
425 protected function _importAccounts(Zend_Console_Getopt $_opts)
427 // disable timelimit during import of user accounts
428 Setup_Core::setExecutionLifeTime(0);
431 if (! $_opts->onlyusers) {
432 Tinebase_Group::syncGroups();
436 $options = array('syncContactData' => TRUE);
437 if ($_opts->dbmailldap) {
438 $options['ldapplugins'] = array(
439 new Tinebase_EmailUser_Imap_LdapDbmailSchema(),
440 new Tinebase_EmailUser_Smtp_LdapDbmailSchema()
444 if ($_opts->syncdeletedusers) {
445 $options['deleteUsers'] = true;
447 if ($_opts->syncaccountstatus) {
448 $options['syncAccountStatus'] = true;
450 if ($_opts->syncontactphoto) {
451 $options['syncContactPhoto'] = true;
454 Tinebase_User::syncUsers($options);
458 * sync ldap passwords
460 * @param Zend_Console_Getopt $_opts
462 protected function _syncPasswords(Zend_Console_Getopt $_opts)
464 Tinebase_User::syncLdapPasswords();
470 * @param Zend_Console_Getopt $_opts
472 protected function _egw14Import(Zend_Console_Getopt $_opts)
474 $args = $_opts->getRemainingArgs();
476 if (count($args) < 1 || ! is_readable($args[0])) {
477 echo "can not open config file \n";
478 echo "see tine20.org/wiki/EGW_Migration_Howto for details \n\n";
479 echo "usage: ./setup.php --egw14import /path/to/config.ini (see Tinebase/Setup/Import/Egw14/config.ini)\n\n";
484 $config = new Zend_Config(array(), TRUE);
485 $config->merge(new Zend_Config_Ini($args[0]));
486 $config = $config->merge($config->all);
487 } catch (Zend_Config_Exception $e) {
488 fwrite(STDERR, "Error while parsing config file($args[0]) " . $e->getMessage() . PHP_EOL);
492 $writer = new Zend_Log_Writer_Stream('php://output');
493 $logger = new Zend_Log($writer);
495 $filter = new Zend_Log_Filter_Priority((int) $config->loglevel);
496 $logger->addFilter($filter);
498 $importer = new Tinebase_Setup_Import_Egw14($config, $logger);
503 * do the environment check
507 protected function _checkRequirements(Zend_Console_Getopt $_opts)
509 $results = Setup_Controller::getInstance()->checkRequirements();
510 if ($results['success']) {
511 echo "OK - All requirements are met\n";
513 echo "ERRORS - The following requirements are not met: \n";
514 foreach ($results['results'] as $result) {
515 if (!empty($result['message'])) {
516 echo "- " . strip_tags($result['message']) . "\n";
527 protected function _setConfig(Zend_Console_Getopt $_opts)
529 $options = $this->_parseRemainingArgs($_opts->getRemainingArgs());
531 if (empty($options['configkey'])) {
532 $errors[] = 'Missing argument: configkey';
534 if (! isset($options['configvalue'])) {
535 $errors[] = 'Missing argument: configvalue';
537 $configKey = (string)$options['configkey'];
538 $configValue = self::parseConfigValue($options['configvalue']);
539 $applicationName = (isset($options['app'])) ? $options['app'] : 'Tinebase';
541 if (! Tinebase_Application::getInstance()->isInstalled('Tinebase') || ! Tinebase_Application::getInstance()->isInstalled($applicationName)) {
542 $errors[] = $applicationName . ' is not installed';
545 if (empty($errors)) {
546 Setup_Controller::getInstance()->setConfigOption($configKey, $configValue, $applicationName);
547 echo "OK - Updated configuration option $configKey for application $applicationName\n";
549 echo "ERRORS - The following errors occured: \n";
550 foreach ($errors as $error) {
551 echo "- " . $error . "\n";
560 protected function _getConfig(Zend_Console_Getopt $_opts)
562 $options = $this->_parseRemainingArgs($_opts->getRemainingArgs());
563 $applicationName = (isset($options['app'])) ? $options['app'] : 'Tinebase';
566 if (! Tinebase_Application::getInstance()->isInstalled('Tinebase') || ! Tinebase_Application::getInstance()->isInstalled($applicationName)) {
567 $errors[] = $applicationName . ' is not installed';
570 $config = Tinebase_Config_Abstract::factory($applicationName);
573 if (empty($options['configkey'])) {
574 $errors[] = 'Missing argument: configkey';
576 $errors[] = 'Available config settings:';
577 $errors[] = print_r($config::getProperties(), true);
580 $configKey = (string)$options['configkey'];
582 if (empty($errors)) {
583 $value = $config->get($configKey);
584 $value = is_string($value) ? $value : Zend_Json::encode($value);
587 echo "ERRORS - The following errors occured: \n";
588 foreach ($errors as $error) {
589 echo "- " . $error . "\n";
595 * create admin user / activate existing user / allow to reset password
597 * @param Zend_Console_Getopt $_opts
599 * @todo check role by rights and not by name
600 * @todo replace echos with stdout logger
602 protected function _createAdminUser(Zend_Console_Getopt $_opts)
604 if (! Setup_Controller::getInstance()->isInstalled('Tinebase')) {
605 die('Install Tinebase first.');
608 $setupUser = Setup_Update_Abstract::getSetupFromConfigOrCreateOnTheFly();
609 if (! Tinebase_Core::getUser() instanceof Tinebase_Model_User) {
610 Tinebase_Core::set(Tinebase_Core::USER, $setupUser);
613 echo "Please enter a username. An existing user is reactivated and you can reset the password.\n";
614 $username = strtolower(Tinebase_Server_Cli::promptInput('Username'));
615 $tomorrow = Tinebase_DateTime::now()->addDay(1);
618 $user = Tinebase_User::getInstance()->getFullUserByLoginName($username);
619 echo "User $username already exists.\n";
620 Tinebase_User::getInstance()->setStatus($user->getId(), Tinebase_Model_User::ACCOUNT_STATUS_ENABLED);
621 echo "Activated admin user '$username'.\n";
623 $expire = Tinebase_Server_Cli::promptInput('Should the admin user expire tomorrow (default: "no", "y" or "yes" for expiry)?');
624 if ($expire === 'y' or $expire === 'yes') {
625 Tinebase_User::getInstance()->setExpiryDate($user->getId(), $tomorrow);
626 echo "User expires tomorrow at $tomorrow.\n";
629 $resetPw = Tinebase_Server_Cli::promptInput('Do you want to reset the password (default: "no", "y" or "yes" for reset)?');
630 if ($resetPw === 'y' or $resetPw === 'yes') {
631 $password = $this->_promptPassword();
632 Tinebase_User::getInstance()->setPassword($user, $password);
633 echo "User password has been reset.\n";
637 Tinebase_User::getInstance()->assertAdminGroupMembership($user);
638 echo "Added user to default admin group\n";
639 } catch (Exception $e) {
640 Tinebase_Exception::log($e);
641 echo "Could not add user to default admin group: " . $e->getMessage();
644 $this->_checkAdminRole($user);
646 } catch (Tinebase_Exception_NotFound $tenf) {
647 // create new admin user that expires tomorrow
648 $password = $this->_promptPassword();
649 Tinebase_User::createInitialAccounts(array(
650 'adminLoginName' => $username,
651 'adminPassword' => $password,
652 'expires' => $tomorrow,
654 echo "Created new admin user '$username' that expires tomorrow.\n";
660 * check admin role membership
662 * @param Tinebase_Model_FullUser $user
664 protected function _checkAdminRole($user)
666 $roleMemberships = Tinebase_Acl_Roles::getInstance()->getRoleMemberships($user->getId());
667 $adminRoleFound = FALSE;
668 // TODO allow to configure this / pass it as param
669 $adminRoleName = 'admin role';
671 foreach ($roleMemberships as $roleId) {
672 $role = Tinebase_Acl_Roles::getInstance()->getRoleById($roleId);
673 if ($role->name === $adminRoleName) {
674 $adminRoleFound = TRUE;
679 if (! $adminRoleFound || ! Tinebase_Acl_Roles::getInstance()->hasRight('Admin', $user->getId(), Tinebase_Acl_Rights::ADMIN)) {
680 echo "Admin role not found for user " . $user->accountLoginName . ".\n";
683 $adminRole = Tinebase_Acl_Roles::getInstance()->getRoleByName($adminRoleName);
684 } catch (Tinebase_Exception_NotFound $tenf) {
685 $adminRole = $this->_createNewAdminRoleForAdmin($adminRoleName);
688 Tinebase_Acl_Roles::getInstance()->setRoleMembers($adminRole->getId(), array(
690 'id' => $user->getId(),
691 'type' => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER,
695 echo "Added user " . $user->accountLoginName . " to role '$adminRoleName''.\n";
696 // @todo clear roles/groups cache
700 protected function _createNewAdminRoleForAdmin($adminRoleName)
702 $adminRole = new Tinebase_Model_Role(array(
703 'name' => $adminRoleName,
704 'description' => 'admin role for tine. this role has all rights per default.',
707 $adminRole = Tinebase_Acl_Roles::getInstance()->createRole($adminRole);
708 // add all rights for all apps
709 $enabledApps = Tinebase_Application::getInstance()->getApplicationsByState(Tinebase_Application::ENABLED);
710 $roleRights = array();
711 foreach ($enabledApps as $application) {
712 $allRights = Tinebase_Application::getInstance()->getAllRights($application->getId());
713 foreach ($allRights as $right) {
714 $roleRights[] = array(
715 'application_id' => $application->getId(),
720 Tinebase_Acl_Roles::getInstance()->setRoleRights($adminRole->getId(), $roleRights);
726 * @param Zend_Console_Getopt $_opts
729 protected function _backup(Zend_Console_Getopt $_opts)
731 $options = $this->_parseRemainingArgs($_opts->getRemainingArgs());
732 Setup_Controller::getInstance()->backup($options);
736 * @param Zend_Console_Getopt $_opts
739 protected function _restore(Zend_Console_Getopt $_opts)
741 $options = $this->_parseRemainingArgs($_opts->getRemainingArgs());
742 Setup_Controller::getInstance()->restore($options);
746 * @param Zend_Console_Getopt $_opts
749 protected function _installDump(Zend_Console_Getopt $_opts)
751 $options = $this->_parseRemainingArgs($_opts->getRemainingArgs());
752 Setup_Controller::getInstance()->installFromDump($options);
758 * @param string $_value
759 * @return array|string
761 public static function parseConfigValue($_value)
763 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_value, TRUE));
765 // check value is json encoded
766 if (Tinebase_Helper::is_json($_value)) {
767 return Zend_Json::decode($_value);
774 // keep spaces, \: and \,
775 $_value = preg_replace(array('/ /', '/\\\:/', '/\\\,/', '/\s*/'), array('§', '@', ';', ''), $_value);
777 $parts = explode(',', $_value);
779 foreach ($parts as $part) {
780 $part = str_replace(';', ',', $part);
781 $part = str_replace('§', ' ', $part);
782 $part = str_replace('@', ':', $part);
783 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $part);
784 if (strpos($part, '_') !== FALSE) {
785 list($key, $sub) = preg_split('/_/', $part, 2);
786 if (preg_match('/:/', $sub)) {
787 list($subKey, $value) = explode(':', $sub);
788 $result[$key][$subKey] = $value;
790 // might be a '_' in the value
791 if (preg_match('/:/', $part)) {
792 $exploded = explode(':', $part);
793 $key = array_shift($exploded);
794 $result[$key] = implode(':', $exploded);
796 throw new Timetracker_Exception_UnexpectedValue('You have an error in the config syntax (":" expected): ' . $part);
800 if (strpos($part, ':') !== FALSE) {
801 list($key, $value) = preg_split('/:/', $part, 2);
802 $result[$key] = $value;
813 * parse remaining args
815 * @param string $_args
818 protected function _parseRemainingArgs($_args)
821 foreach ($_args as $arg) {
822 if (strpos($arg, '=') !== FALSE) {
823 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $arg);
824 list($key, $value) = preg_split('/=/', $arg, 2);
825 $options[$key] = $value;
832 protected function _compare(Zend_Console_Getopt $_opts)
834 $options = $this->_parseRemainingArgs($_opts->getRemainingArgs());
835 print_r(Setup_Controller::getInstance()->compareSchema($options));