Add Reset_Demodata Cli function
[tine20] / tine20 / Setup / Frontend / Cli.php
1 <?php
2 /**
3  * Tine 2.0
4  * @package     Tinebase
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)
8  * 
9  * @todo        add ext check again
10  */
11
12 /**
13  * cli server
14  *
15  * This class handles all requests from cli scripts
16  *
17  * @package     Tinebase
18  */
19 class Setup_Frontend_Cli
20 {
21     /**
22      * the internal name of the application
23      *
24      * @var string
25      */
26     protected $_appname = 'Setup';
27
28     /**
29      * authentication
30      *
31      * @param string $_username
32      * @param string $_password
33      * 
34      * @return boolean
35      */
36     public function authenticate($_username, $_password)
37     {
38         return false;
39     }
40     
41     /**
42      * handle request (call -ApplicationName-_Cli.-MethodName- or -ApplicationName-_Cli.getHelp)
43      *
44      * @param Zend_Console_Getopt $_opts
45      * @param boolean $exitAfterHandle
46      * @return void
47      */
48     public function handle(Zend_Console_Getopt $_opts, $exitAfterHandle = true)
49     {
50         Setup_Core::set(Setup_Core::USER, 'setupuser');
51         
52         $result = 0;
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->list)) {
60             $result = $this->_listInstalled();
61         } elseif(isset($_opts->sync_accounts_from_ldap)) {
62             $this->_importAccounts($_opts);
63         } elseif(isset($_opts->sync_passwords_from_ldap)) {
64             $this->_syncPasswords($_opts);
65         } elseif(isset($_opts->egw14import)) {
66             $this->_egw14Import($_opts);
67         } elseif(isset($_opts->check_requirements)) {
68             $this->_checkRequirements($_opts);
69         } elseif(isset($_opts->setconfig)) {
70             $this->_setConfig($_opts);
71         } elseif(isset($_opts->create_admin)) {
72             $this->_createAdminUser($_opts);
73         } elseif(isset($_opts->getconfig)) {
74             $this->_getConfig($_opts);
75         } elseif(isset($_opts->reset_demodata)) {
76             $this->_resetDemodata($_opts);
77         }
78         
79         if ($exitAfterHandle) {
80             exit($result);
81         }
82     }
83     
84     /**
85      * install new applications
86      *
87      * @param Zend_Console_Getopt $_opts
88      */
89     protected function _install(Zend_Console_Getopt $_opts)
90     {
91         $controller = Setup_Controller::getInstance();
92         
93         if($_opts->install === true) {
94             $applications = $controller->getInstallableApplications();
95             $applications = array_keys($applications);
96         } else {
97             $applications = array();
98             $applicationNames = explode(',', $_opts->install);
99             foreach($applicationNames as $applicationName) {
100                 $applicationName = ucfirst(trim($applicationName));
101                 try {
102                     $controller->getSetupXml($applicationName);
103                     $applications[] = $applicationName;
104                 } catch (Setup_Exception_NotFound $e) {
105                     echo "Application $applicationName not found! Skipped...\n";
106                 }
107             }
108         }
109         
110         $options = $this->_parseRemainingArgs($_opts->getRemainingArgs());
111         $this->_promptRemainingOptions($applications, $options);
112         
113         $controller->installApplications($applications, $options);
114         
115         if ((isset($options['acceptedTermsVersion']) || array_key_exists('acceptedTermsVersion', $options))) {
116             Setup_Controller::getInstance()->saveAcceptedTerms($options['acceptedTermsVersion']);
117         }
118         
119         echo "Successfully installed " . count($applications) . " applications.\n";
120     }
121
122     /**
123      * prompt remaining options
124      * 
125      * @param array $_applications
126      * @param array $_options
127      * @return void
128      * 
129      * @todo add required version server side
130      */
131     protected function _promptRemainingOptions($_applications, &$_options) {
132         if (in_array('Tinebase', $_applications)) {
133             
134             if (! isset($_options['acceptedTermsVersion'])) {
135                 fwrite(STDOUT, PHP_EOL . file_get_contents(dirname(dirname(dirname(__FILE__))) . '/LICENSE' ));
136                 $licenseAnswer = Tinebase_Server_Cli::promptInput('I have read the license agreement and accept it (type "yes" to accept)');
137                 
138                 
139                 fwrite(STDOUT, PHP_EOL . file_get_contents(dirname(dirname(dirname(__FILE__))) . '/PRIVACY' ));
140                 $privacyAnswer = Tinebase_Server_Cli::promptInput('I have read the privacy agreement and accept it (type "yes" to accept)');
141             
142                 if (! (strtoupper($licenseAnswer) == 'YES' && strtoupper($privacyAnswer) == 'YES')) {
143                     echo "error: you need to accept the terms! exiting \n";
144                     exit (1);
145                 }
146                 
147                 $_options['acceptedTermsVersion'] = 1;
148             }
149             
150             
151             // initial username
152             if (! isset($_options['adminLoginName'])) {
153                 $_options['adminLoginName'] = Tinebase_Server_Cli::promptInput('Inital Admin Users Username');
154                 if (! $_options['adminLoginName']) {
155                     echo "error: username must be given! exiting \n";
156                     exit (1);
157                 }
158             }
159             
160             // initial password / can be empty => will trigger password change dialogue
161             if (! array_key_exists('adminPassword', $_options)) {
162                 $_options['adminPassword'] = $this->_promptPassword();
163             }
164         }
165     }
166     
167     /**
168      * prompt password
169      * 
170      * @return string
171      */
172     protected function _promptPassword()
173     {
174         $password1 = Tinebase_Server_Cli::promptInput('Admin user password', TRUE);
175         if (! $password1) {
176             echo "Error: Password must not be empty! Exiting ... \n";
177             exit (1);
178         }
179         $password2 = Tinebase_Server_Cli::promptInput('Confirm password', TRUE);
180         if ($password1 !== $password2) {
181             echo "Error: Passwords do not match! Exiting ... \n";
182             exit (1);
183         }
184         
185         return $password1;
186     }
187     
188     /**
189      * update existing applications
190      *
191      * @param Zend_Console_Getopt $_opts
192      * @return integer
193      */
194     protected function _update(Zend_Console_Getopt $_opts)
195     {
196         $maxLoops = 50;
197         do {
198             $result = $this->_updateApplications();
199             if ($_opts->v && ! empty($result['messages'])) {
200                 echo "Messages:\n";
201                 foreach ($result['messages'] as $message) {
202                     echo "  " . $message . "\n";
203                 }
204             }
205             $maxLoops--;
206         } while ($result['updated'] > 0 && $maxLoops > 0);
207         
208         return ($maxLoops > 0) ? 0 : 1;
209     }
210     
211     /**
212      * update all applications
213      * 
214      * @return array
215      */
216     protected function _updateApplications()
217     {
218         $controller = Setup_Controller::getInstance();
219         $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
220         
221         foreach ($applications as $key => &$application) {
222             try {
223                 if (! $controller->updateNeeded($application)) {
224                     unset($applications[$key]);
225                 }
226             } catch (Setup_Exception_NotFound $e) {
227                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ 
228                     . ' Failed to check if an application needs an update:' . $e->getMessage());
229                 unset($applications[$key]);
230             }
231         }
232
233         $result = array();
234         if (count($applications) > 0) {
235             $result = $controller->updateApplications($applications);
236             echo "Updated " . $result['updated'] . " application(s).\n";
237         } else {
238             $result['updated'] = 0;
239         }
240         
241         return $result;
242     }
243
244     /**
245      * uninstall applications
246      *
247      * @param Zend_Console_Getopt $_opts
248      */
249     protected function _uninstall(Zend_Console_Getopt $_opts)
250     {
251         $controller = Setup_Controller::getInstance();
252         
253         if($_opts->uninstall === true) {
254             $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
255         } else {
256             $applications = new Tinebase_Record_RecordSet('Tinebase_Model_Application');
257             $applicationNames = explode(',', $_opts->uninstall);
258             foreach($applicationNames as $applicationName) {
259                 $applicationName = ucfirst(trim($applicationName));
260                 try {
261                     $application = Tinebase_Application::getInstance()->getApplicationByName($applicationName);
262                     $applications->addRecord($application);
263                 } catch (Tinebase_Exception_NotFound $e) {
264                 }
265             }
266         }
267         
268         $controller->uninstallApplications($applications->name);
269         
270         echo "Successfully uninstalled " . count($applications) . " applications.\n";
271     }
272     
273     /**
274      * reinstall applications
275      * and reset Demodata
276      * php setup.php --reset_demodata USERNAME
277      * 
278      * @param Zend_Console_Getopt $_opts
279      */
280     protected function _resetDemodata(Zend_Console_Getopt $_opts)
281     {
282         $controller = Setup_Controller::getInstance();
283         $userController = Admin_Controller_User::getInstance();
284         $containerController = Tinebase_Container::getInstance();
285         $cli = new Tinebase_Frontend_Cli();
286         
287         //Don't reset this applications
288         $fixedApplications = array('Tinebase', 'Admin', 'Addressbook');
289         
290         //Log in
291         $opts = $_opts->getRemainingArgs();
292         $username = $opts[0];
293         if (empty($username)) {
294             echo "Username is missing!\n";
295             exit;
296         }
297         $user = Tinebase_User::getInstance()->getUserByLoginName($username);
298         Tinebase_Core::set(Tinebase_Core::USER, $user);
299         
300         //get all applications and remove some
301         $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
302         
303         foreach ($applications as $key => &$application) {
304             if (in_array($application, $fixedApplications)) {
305                 unset($applications[$key]);
306             }
307         }
308         
309         //Uninstall Applications
310         try {
311             $controller->uninstallApplications($applications->name);
312             echo "Successfully uninstalled " . count($applications) . " applications.\n";
313         } catch (Tinebase_Exception_NotFound $e) {
314         }
315         //Install Applications
316         try {
317             $controller->installApplications($applications->name);
318             echo "Successfully installed " . count($applications) . " applications.\n";
319         } catch (Tinebase_Exception_NotFound $e) {
320         }
321         
322         //Clean up addressbooks
323         $internalContacts = $userController->getDefaultInternalAddressbook();
324         $containers = $containerController->getAll();
325         foreach ($containers as $key => &$container) {
326             if ($container->id == $internalContacts) {
327                 // Do nothing
328             } else {
329                 try {
330                     $containerController->deleteContainer($container, true);
331                 } catch (Tinebase_Exception_NotFound $e) {
332                 }
333             }
334         }
335         
336         echo "Successfully cleand up containers.\n";
337         
338         //Get Demodata
339         $cli->createAllDemoData();
340         
341         echo "Every thing done!\n";
342     }
343
344     /**
345      * list installed apps
346      */
347     protected function _listInstalled()
348     {
349         try {
350             $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
351         } catch (Zend_Db_Statement_Exception $e) {
352             echo "No applications installed\n";
353             return 1;
354         }
355         
356         echo "Currently installed applications:\n";
357         foreach($applications as $application) {
358             echo "* $application\n";
359         }
360         
361         return 0;
362     }
363     
364     /**
365      * import accounts from ldap
366      *
367      * @param Zend_Console_Getopt $_opts
368      */
369     protected function _importAccounts(Zend_Console_Getopt $_opts)
370     {
371         // disable timelimit during import of user accounts
372         Setup_Core::setExecutionLifeTime(0);
373         
374         // import groups
375         if (! $_opts->onlyusers) {
376             Tinebase_Group::syncGroups();
377         }
378         
379         // import users
380         $options = array('syncContactData' => TRUE);
381         if ($_opts->dbmailldap) {
382             $options['ldapplugins'] = array(
383                 new Tinebase_EmailUser_Imap_LdapDbmailSchema(),
384                 new Tinebase_EmailUser_Smtp_LdapDbmailSchema()
385             );
386         }
387         Tinebase_User::syncUsers($options);
388     }
389     
390     /**
391      * sync ldap passwords
392      * 
393      * @param Zend_Console_Getopt $_opts
394      */
395     protected function _syncPasswords(Zend_Console_Getopt $_opts)
396     {
397         Tinebase_User::syncLdapPasswords();
398     }
399     
400     /**
401      * import from egw14
402      * 
403      * @param Zend_Console_Getopt $_opts
404      */
405     protected function _egw14Import(Zend_Console_Getopt $_opts)
406     {
407         $args = $_opts->getRemainingArgs();
408         
409         if (count($args) < 1 || ! is_readable($args[0])) {
410             echo "can not open config file \n";
411             echo "see tine20.org/wiki/EGW_Migration_Howto for details \n\n";
412             echo "usage: ./setup.php --egw14import /path/to/config.ini (see Tinebase/Setup/Import/Egw14/config.ini)\n\n";
413             exit(1);
414         }
415         
416         try {
417             $config = new Zend_Config(array(), TRUE);
418             $config->merge(new Zend_Config_Ini($args[0]));
419             $config = $config->merge($config->all);
420         } catch (Zend_Config_Exception $e) {
421             fwrite(STDERR, "Error while parsing config file($args[0]) " .  $e->getMessage() . PHP_EOL);
422             exit(1);
423         }
424         
425         $writer = new Zend_Log_Writer_Stream('php://output');
426         $logger = new Zend_Log($writer);
427         
428         $filter = new Zend_Log_Filter_Priority((int) $config->loglevel);
429         $logger->addFilter($filter);
430         
431         $importer = new Tinebase_Setup_Import_Egw14($config, $logger);
432         $importer->import();
433     }
434     
435     /**
436      * do the environment check
437      *
438      * @return array
439      */
440     protected function _checkRequirements(Zend_Console_Getopt $_opts)
441     {
442         $results = Setup_Controller::getInstance()->checkRequirements();
443         if ($results['success']) {
444           echo "OK - All requirements are met\n";
445         } else {
446           echo "ERRORS - The following requirements are not met: \n";
447           foreach ($results['results'] as $result) {
448             if (!empty($result['message'])) {
449               echo "- " . strip_tags($result['message']) . "\n";
450             }
451           }
452         }
453     }
454     
455     /**
456      * set config
457      *
458      * @return array
459      */
460     protected function _setConfig(Zend_Console_Getopt $_opts)
461     {
462         $options = $this->_parseRemainingArgs($_opts->getRemainingArgs());
463         $errors = array();
464         if (empty($options['configkey'])) {
465             $errors[] = 'Missing argument: configkey';
466         }
467         if (! isset($options['configvalue'])) {
468             $errors[] = 'Missing argument: configvalue';
469         }
470         $configKey = (string)$options['configkey'];
471         $configValue = self::parseConfigValue($options['configvalue']);
472         $applicationName = (isset($options['app'])) ? $options['app'] : 'Tinebase';
473         
474         if (empty($errors)) {
475            Setup_Controller::getInstance()->setConfigOption($configKey, $configValue, $applicationName);
476            echo "OK - Updated configuration option $configKey for application $applicationName\n";
477         } else {
478             echo "ERRORS - The following errors occured: \n";
479             foreach ($errors as $error) {
480                 echo "- " . $error . "\n";
481             }
482         }
483     }
484     
485     /**
486      * get config
487      *
488      */
489     protected function _getConfig(Zend_Console_Getopt $_opts)
490     {
491         $options = $this->_parseRemainingArgs($_opts->getRemainingArgs());
492         $applicationName = (isset($options['app'])) ? $options['app'] : 'Tinebase';
493         $config = Tinebase_Config_Abstract::factory($applicationName);
494         
495         $errors = array();
496         if (empty($options['configkey'])) {
497             $errors[] = 'Missing argument: configkey';
498             $errors[] = 'Available config settings:';
499             $errors[] = print_r($config::getProperties(), true);
500         }
501         $configKey = (string)$options['configkey'];
502         
503         if (empty($errors)) {
504             $value = $config->get($configKey);
505             $value = is_string($value) ? $value : Zend_Json::encode($value);
506             echo $value . " \n";
507         } else {
508             echo "ERRORS - The following errors occured: \n";
509             foreach ($errors as $error) {
510                 echo "- " . $error . "\n";
511             }
512         }
513     }
514     
515     /**
516      * create admin user / activate existing user / allow to reset password
517      * 
518      * @param Zend_Console_Getopt $_opts
519      * 
520      * @todo check role by rights and not by name
521      * @todo replace echos with stdout logger
522      */
523     protected function _createAdminUser(Zend_Console_Getopt $_opts)
524     {
525         if (! Setup_Controller::getInstance()->isInstalled('Tinebase')) {
526             die('Install Tinebase first.');
527         }
528         
529         echo "Please enter a username. If the user already exists, he is reactivated and you can reset the password.\n";
530         $username = Tinebase_Server_Cli::promptInput('Username');
531         $tomorrow = Tinebase_DateTime::now()->addDay(1);
532         
533         try {
534             $user = Tinebase_User::getInstance()->getFullUserByLoginName($username);
535             echo "User $username already exists.\n";
536             Tinebase_User::getInstance()->setStatus($user->getId(), Tinebase_Model_User::ACCOUNT_STATUS_ENABLED);
537             echo "Activated admin user '$username'.\n";
538             
539             $expire = Tinebase_Server_Cli::promptInput('Should the admin user expire tomorrow (default: "no", "y" or "yes" for expiry)?');
540             if ($expire === 'y' or $expire === 'yes') {
541                 Tinebase_User::getInstance()->setExpiryDate($user->getId(), $tomorrow);
542                 echo "User expires tomorrow at $tomorrow.\n";
543             }
544             
545             $resetPw = Tinebase_Server_Cli::promptInput('Do you want to reset the password (default: "no", "y" or "yes" for reset)?');
546             if ($resetPw === 'y' or $resetPw === 'yes') {
547                 $password = $this->_promptPassword();
548                 Tinebase_User::getInstance()->setPassword($user, $password);
549                 echo "User password has been reset.\n";
550             }
551             
552             $this->_checkAdminGroupMembership($user);
553             $this->_checkAdminRole($user);
554             
555         } catch (Tinebase_Exception_NotFound $tenf) {
556             // create new admin user that expires tomorrow
557             $password = $this->_promptPassword();
558             Tinebase_User::createInitialAccounts(array(
559                 'adminLoginName' => $username,
560                 'adminPassword'  => $password,
561                 'expires'        => $tomorrow,
562             ));
563             echo "Created new admin user '$username' that expires tomorrow.\n";
564         }
565     }
566
567     /**
568      * check admin group membership
569      * 
570      * @param Tinebase_Model_FullUser $user
571      */
572     protected function _checkAdminGroupMembership($user)
573     {
574         $adminGroup = Tinebase_Group::getInstance()->getDefaultAdminGroup();
575         $memberships = Tinebase_Group::getInstance()->getGroupMemberships($user);
576         if (! in_array($adminGroup->getId(), $memberships)) {
577             try {
578                 Tinebase_Group::getInstance()->addGroupMember($adminGroup, $user);
579                 echo "Added user to default admin group\n";
580                 // @todo clear roles/groups cache
581             } catch (Exception $e) {
582                 Tinebase_Exception::log($e);
583                 echo "Could not add user to default admin group: " . $e->getMessage();
584             }
585         }
586     }
587     
588     /**
589      * check admin role membership
590      * 
591      * @param Tinebase_Model_FullUser $user
592      */
593     protected function _checkAdminRole($user)
594     {
595         $roleMemberships = Tinebase_Acl_Roles::getInstance()->getRoleMemberships($user->getId());
596         $adminRoleFound = FALSE;
597         foreach ($roleMemberships as $roleId) {
598             $role = Tinebase_Acl_Roles::getInstance()->getRoleById($roleId);
599             if ($role->name === 'admin role') {
600                 $adminRoleFound = TRUE;
601                 //print_r(Tinebase_Acl_Roles::getInstance()->getRoleRights($role->getId()));
602                 break;
603             }
604         }
605         
606         if (! $adminRoleFound || ! Tinebase_Acl_Roles::getInstance()->hasRight('Admin', $user->getId(), Tinebase_Acl_Rights::ADMIN)) {
607             echo "Admin role not found for user " . $user->accountLoginName . ".\n";
608             $adminRole = new Tinebase_Model_Role(array(
609                 'name'                  => 'admin role',
610                 'description'           => 'admin role for tine. this role has all rights per default.',
611             ));
612             $adminRole = Tinebase_Acl_Roles::getInstance()->createRole($adminRole);
613             Tinebase_Acl_Roles::getInstance()->setRoleMembers($adminRole->getId(), array(
614                 array(
615                     'id'    => $user->getId(),
616                     'type'  => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER, 
617                 )
618             ));
619             
620             // add all rights for all apps
621             $enabledApps = Tinebase_Application::getInstance()->getApplicationsByState(Tinebase_Application::ENABLED);
622             $roleRights = array();
623             foreach ($enabledApps as $application) {
624                 $allRights = Tinebase_Application::getInstance()->getAllRights($application->getId());
625                 foreach ($allRights as $right) {
626                     $roleRights[] = array(
627                         'application_id' => $application->getId(),
628                         'right'          => $right,
629                     );
630                 }
631             }
632             Tinebase_Acl_Roles::getInstance()->setRoleRights($adminRole->getId(), $roleRights);
633             
634             echo "Created admin role for user " . $user->accountLoginName . ".\n";
635             // @todo clear roles/groups cache
636         }
637     }
638     
639     /**
640      * parse options
641      * 
642      * @param string $_value
643      * @return array|string
644      */
645     public static function parseConfigValue($_value)
646     {
647         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_value, TRUE));
648         
649         // check value is json encoded
650         if (Tinebase_Helper::is_json($_value)) {
651             return Zend_Json::decode($_value); 
652         }
653         
654         $result = array(
655             'active' => 1
656         );
657
658         // keep spaces, \: and \,
659         $_value = preg_replace(array('/ /', '/\\\:/', '/\\\,/', '/\s*/'), array('§', '@', ';', ''), $_value);
660         
661         $parts = explode(',', $_value);
662         
663         foreach ($parts as $part) {
664             $part = str_replace(';', ',', $part);
665             $part = str_replace('§', ' ', $part);
666             $part = str_replace('@', ':', $part);
667             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $part);
668             if (strpos($part, '_') !== FALSE) {
669                 list($key, $sub) = preg_split('/_/', $part, 2);
670                 if (preg_match('/:/', $sub)) {
671                     list($subKey, $value) = explode(':', $sub);
672                     $result[$key][$subKey] = $value;
673                 } else {
674                     // might be a '_' in the value
675                     if (preg_match('/:/', $part)) {
676                         $exploded = explode(':', $part);
677                         $key = array_shift($exploded);
678                         $result[$key] = implode(':', $exploded);
679                     } else {
680                         throw new Timetracker_Exception_UnexpectedValue('You have an error in the config syntax (":" expected): ' . $part);
681                     }
682                 }
683             } else {
684                 if (strpos($part, ':') !== FALSE) {
685                     list($key, $value) = preg_split('/:/', $part, 2);
686                     $result[$key] = $value;
687                 } else {
688                     $result = $part;
689                 }
690             }
691         }
692
693         return $result;
694     }
695     
696     /**
697      * parse remaining args
698      * 
699      * @param string $_args
700      * @return array
701      */
702     protected function _parseRemainingArgs($_args)
703     {
704         $options = array();
705         foreach ($_args as $arg) {
706             if (strpos($arg, '=') !== FALSE) {
707                 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $arg);
708                 list($key, $value) = preg_split('/=/', $arg, 2);
709                 $options[$key] = $value;
710             }
711         }
712         
713         return $options;
714     }
715 }