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