0013236: Use filepicker for filemanager relations
[tine20] / tine20 / Setup / Controller.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Setup
6  * @subpackage  Controller
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Lars Kneschke <l.kneschke@metaways.de>
9  * @copyright   Copyright (c) 2008-2016 Metaways Infosystems GmbH (http://www.metaways.de)
10  *
11  * @todo        move $this->_db calls to backend class
12  */
13
14 /**
15  * php helpers
16  */
17 require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Tinebase' . DIRECTORY_SEPARATOR . 'Helper.php';
18
19 /**
20  * class to handle setup of Tine 2.0
21  *
22  * @package     Setup
23  * @subpackage  Controller
24  */
25 class Setup_Controller
26 {
27     /**
28      * holds the instance of the singleton
29      *
30      * @var Setup_Controller
31      */
32     private static $_instance = NULL;
33     
34     /**
35      * setup backend
36      *
37      * @var Setup_Backend_Interface
38      */
39     protected $_backend = NULL;
40     
41     /**
42      * the directory where applications are located
43      *
44      * @var string
45      */
46     protected $_baseDir;
47     
48     /**
49      * the email configs to get/set
50      *
51      * @var array
52      */
53     protected $_emailConfigKeys = array();
54     
55     /**
56      * number of updated apps
57      * 
58      * @var integer
59      */
60     protected $_updatedApplications = 0;
61
62     const MAX_DB_PREFIX_LENGTH = 10;
63
64     /**
65      * don't clone. Use the singleton.
66      *
67      */
68     private function __clone() {}
69     
70     /**
71      * url to Tine 2.0 wiki
72      *
73      * @var string
74      */
75     protected $_helperLink = ' <a href="http://wiki.tine20.org/Admins/Install_Howto" target="_blank">Check the Tine 2.0 wiki for support.</a>';
76
77     /**
78      * the singleton pattern
79      *
80      * @return Setup_Controller
81      */
82     public static function getInstance()
83     {
84         if (self::$_instance === NULL) {
85             self::$_instance = new Setup_Controller;
86         }
87         
88         return self::$_instance;
89     }
90
91     /**
92      * the constructor
93      *
94      */
95     protected function __construct()
96     {
97         // setup actions could take quite a while we try to set max execution time to unlimited
98         Setup_Core::setExecutionLifeTime(0);
99         
100         if (!defined('MAXLOOPCOUNT')) {
101             define('MAXLOOPCOUNT', 50);
102         }
103         
104         $this->_baseDir = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR;
105         
106         if (Setup_Core::get(Setup_Core::CHECKDB)) {
107             $this->_db = Setup_Core::getDb();
108             $this->_backend = Setup_Backend_Factory::factory();
109         } else {
110             $this->_db = NULL;
111         }
112         
113         $this->_emailConfigKeys = array(
114             'imap'  => Tinebase_Config::IMAP,
115             'smtp'  => Tinebase_Config::SMTP,
116             'sieve' => Tinebase_Config::SIEVE,
117         );
118     }
119
120     /**
121      * check system/php requirements (env + ext check)
122      *
123      * @return array
124      *
125      * @todo add message to results array
126      */
127     public function checkRequirements()
128     {
129         $envCheck = $this->environmentCheck();
130         
131         $databaseCheck = $this->checkDatabase();
132         
133         $extCheck = new Setup_ExtCheck(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'essentials.xml');
134         $extResult = $extCheck->getData();
135
136         $result = array(
137             'success' => ($envCheck['success'] && $databaseCheck['success'] && $extResult['success']),
138             'results' => array_merge($envCheck['result'], $databaseCheck['result'], $extResult['result']),
139         );
140
141         $result['totalcount'] = count($result['results']);
142         
143         return $result;
144     }
145     
146     /**
147      * check which database extensions are available
148      *
149      * @return array
150      */
151     public function checkDatabase()
152     {
153         $result = array(
154             'result'  => array(),
155             'success' => false
156         );
157         
158         $loadedExtensions = get_loaded_extensions();
159         
160         if (! in_array('PDO', $loadedExtensions)) {
161             $result['result'][] = array(
162                 'key'       => 'Database',
163                 'value'     => FALSE,
164                 'message'   => "PDO extension not found."  . $this->_helperLink
165             );
166             
167             return $result;
168         }
169         
170         // check mysql requirements
171         $missingMysqlExtensions = array_diff(array('pdo_mysql'), $loadedExtensions);
172         
173         // check pgsql requirements
174         $missingPgsqlExtensions = array_diff(array('pgsql', 'pdo_pgsql'), $loadedExtensions);
175         
176         // check oracle requirements
177         $missingOracleExtensions = array_diff(array('oci8'), $loadedExtensions);
178
179         if (! empty($missingMysqlExtensions) && ! empty($missingPgsqlExtensions) && ! empty($missingOracleExtensions)) {
180             $result['result'][] = array(
181                 'key'       => 'Database',
182                 'value'     => FALSE,
183                 'message'   => 'Database extensions missing. For MySQL install: ' . implode(', ', $missingMysqlExtensions) . 
184                                ' For Oracle install: ' . implode(', ', $missingOracleExtensions) . 
185                                ' For PostgreSQL install: ' . implode(', ', $missingPgsqlExtensions) .
186                                $this->_helperLink
187             );
188             
189             return $result;
190         }
191         
192         $result['result'][] = array(
193             'key'       => 'Database',
194             'value'     => TRUE,
195             'message'   => 'Support for following databases enabled: ' . 
196                            (empty($missingMysqlExtensions) ? 'MySQL' : '') . ' ' .
197                            (empty($missingOracleExtensions) ? 'Oracle' : '') . ' ' .
198                            (empty($missingPgsqlExtensions) ? 'PostgreSQL' : '') . ' '
199         );
200         $result['success'] = TRUE;
201         
202         return $result;
203     }
204     
205     /**
206      * Check if tableprefix is longer than 6 charcters
207      *
208      * @return boolean
209      */
210     public function checkDatabasePrefix()
211     {
212         $config = Setup_Core::get(Setup_Core::CONFIG);
213         if (isset($config->database->tableprefix) && strlen($config->database->tableprefix) > self::MAX_DB_PREFIX_LENGTH) {
214             if (Setup_Core::isLogLevel(Zend_Log::ERR)) Setup_Core::getLogger()->error(__METHOD__ . '::' . __LINE__
215                 . ' Tableprefix: "' . $config->database->tableprefix . '" is longer than ' . self::MAX_DB_PREFIX_LENGTH
216                 . '  characters! Please check your configuration.');
217             return false;
218         }
219         return true;
220     }
221     
222     /**
223      * Check if logger is properly configured (or not configured at all)
224      *
225      * @return boolean
226      */
227     public function checkConfigLogger()
228     {
229         $config = Setup_Core::get(Setup_Core::CONFIG);
230         if (!isset($config->logger) || !$config->logger->active) {
231             return true;
232         } else {
233             return (
234                 isset($config->logger->filename)
235                 && (
236                     file_exists($config->logger->filename) && is_writable($config->logger->filename)
237                     || is_writable(dirname($config->logger->filename))
238                 )
239             );
240         }
241     }
242     
243     /**
244      * Check if caching is properly configured (or not configured at all)
245      *
246      * @return boolean
247      */
248     public function checkConfigCaching()
249     {
250         $result = FALSE;
251         
252         $config = Setup_Core::get(Setup_Core::CONFIG);
253         
254         if (! isset($config->caching) || !$config->caching->active) {
255             $result = TRUE;
256             
257         } else if (! isset($config->caching->backend) || ucfirst($config->caching->backend) === 'File') {
258             $result = $this->checkDir('path', 'caching', FALSE);
259             
260         } else if (ucfirst($config->caching->backend) === 'Redis') {
261             $result = $this->_checkRedisConnect(isset($config->caching->redis) ? $config->caching->redis->toArray() : array());
262             
263         } else if (ucfirst($config->caching->backend) === 'Memcached') {
264             $result = $this->_checkMemcacheConnect(isset($config->caching->memcached) ? $config->caching->memcached->toArray() : array());
265             
266         }
267         
268         return $result;
269     }
270     
271     /**
272      * checks redis extension and connection
273      * 
274      * @param array $config
275      * @return boolean
276      */
277     protected function _checkRedisConnect($config)
278     {
279         if (! extension_loaded('redis')) {
280             Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' redis extension not loaded');
281             return FALSE;
282         }
283         $redis = new Redis;
284         $host = isset($config['host']) ? $config['host'] : 'localhost';
285         $port = isset($config['port']) ? $config['port'] : 6379;
286         
287         $result = $redis->connect($host, $port);
288         if ($result) {
289             $redis->close();
290         } else {
291             Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not connect to redis server at ' . $host . ':' . $port);
292         }
293         
294         return $result;
295     }
296     
297     /**
298      * checks memcached extension and connection
299      * 
300      * @param array $config
301      * @return boolean
302      */
303     protected function _checkMemcacheConnect($config)
304     {
305         if (! extension_loaded('memcache')) {
306             Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' memcache extension not loaded');
307             return FALSE;
308         }
309         $memcache = new Memcache;
310         $host = isset($config['host']) ? $config['host'] : 'localhost';
311         $port = isset($config['port']) ? $config['port'] : 11211;
312         $result = $memcache->connect($host, $port);
313         
314         return $result;
315     }
316     
317     /**
318      * Check if queue is properly configured (or not configured at all)
319      *
320      * @return boolean
321      */
322     public function checkConfigQueue()
323     {
324         $config = Setup_Core::get(Setup_Core::CONFIG);
325         if (! isset($config->actionqueue) || ! $config->actionqueue->active) {
326             $result = TRUE;
327         } else {
328             $result = $this->_checkRedisConnect($config->actionqueue->toArray());
329         }
330         
331         return $result;
332     }
333     
334     /**
335      * check config session
336      * 
337      * @return boolean
338      */
339     public function checkConfigSession()
340     {
341         $result = FALSE;
342         $config = Setup_Core::get(Setup_Core::CONFIG);
343         if (! isset($config->session) || !$config->session->active) {
344             return TRUE;
345         } else if (ucfirst($config->session->backend) === 'File') {
346             return $this->checkDir('path', 'session', FALSE);
347         } else if (ucfirst($config->session->backend) === 'Redis') {
348             $result = $this->_checkRedisConnect($config->session->toArray());
349         }
350         
351         return $result;
352     }
353     
354     /**
355      * checks if path in config is writable
356      *
357      * @param string $_name
358      * @param string $_group
359      * @return boolean
360      */
361     public function checkDir($_name, $_group = NULL, $allowEmptyPath = TRUE)
362     {
363         $config = $this->getConfigData();
364         if ($_group !== NULL && (isset($config[$_group]) || array_key_exists($_group, $config))) {
365             $config = $config[$_group];
366         }
367         
368         $path = (isset($config[$_name]) || array_key_exists($_name, $config)) ? $config[$_name] : false;
369         if (empty($path)) {
370             return $allowEmptyPath;
371         } else {
372             return @is_writable($path);
373         }
374     }
375     
376     /**
377      * get list of applications as found in the filesystem
378      *
379      * @return array appName => setupXML
380      */
381     public function getInstallableApplications()
382     {
383         // create Tinebase tables first
384         $applications = array('Tinebase' => $this->getSetupXml('Tinebase'));
385         
386         try {
387             $dirIterator = new DirectoryIterator($this->_baseDir);
388         } catch (Exception $e) {
389             Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not open base dir: ' . $this->_baseDir);
390             throw new Tinebase_Exception_AccessDenied('Could not open Tine 2.0 root directory.');
391         }
392         
393         foreach ($dirIterator as $item) {
394             $appName = $item->getFileName();
395             if($appName{0} != '.' && $appName != 'Tinebase' && $item->isDir()) {
396                 $fileName = $this->_baseDir . $appName . '/Setup/setup.xml' ;
397                 if(file_exists($fileName)) {
398                     $applications[$appName] = $this->getSetupXml($appName);
399                 }
400             }
401         }
402         
403         return $applications;
404     }
405     
406     /**
407      * updates installed applications. does nothing if no applications are installed
408      *
409      * @param Tinebase_Record_RecordSet $_applications
410      * @return  array   messages
411      */
412     public function updateApplications(Tinebase_Record_RecordSet $_applications = null)
413     {
414         if (null === ($user = Setup_Update_Abstract::getSetupFromConfigOrCreateOnTheFly())) {
415             throw new Tinebase_Exception('could not create setup user');
416         }
417         Tinebase_Core::set(Tinebase_Core::USER, $user);
418
419         if ($_applications === null) {
420             $_applications = Tinebase_Application::getInstance()->getApplications();
421         }
422
423         // we need to clone here because we would taint the app cache otherwise
424         $applications = clone($_applications);
425
426         $this->_updatedApplications = 0;
427         $smallestMajorVersion = NULL;
428         $biggestMajorVersion = NULL;
429         
430         //find smallest major version
431         foreach ($applications as $application) {
432             if ($smallestMajorVersion === NULL || $application->getMajorVersion() < $smallestMajorVersion) {
433                 $smallestMajorVersion = $application->getMajorVersion();
434             }
435             if ($biggestMajorVersion === NULL || $application->getMajorVersion() > $biggestMajorVersion) {
436                 $biggestMajorVersion = $application->getMajorVersion();
437             }
438         }
439         
440         $messages = array();
441         
442         // update tinebase first (to biggest major version)
443         $tinebase = $applications->filter('name', 'Tinebase')->getFirstRecord();
444         if (! empty($tinebase)) {
445             unset($applications[$applications->getIndexById($tinebase->getId())]);
446         
447             list($major, $minor) = explode('.', $this->getSetupXml('Tinebase')->version[0]);
448             Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updating Tinebase to version ' . $major . '.' . $minor);
449             
450             for ($majorVersion = $tinebase->getMajorVersion(); $majorVersion <= $major; $majorVersion++) {
451                 $messages = array_merge($messages, $this->updateApplication($tinebase, $majorVersion));
452             }
453         }
454             
455         // update the rest
456         for ($majorVersion = $smallestMajorVersion; $majorVersion <= $biggestMajorVersion; $majorVersion++) {
457             foreach ($applications as $application) {
458                 if ($application->getMajorVersion() <= $majorVersion) {
459                     $messages = array_merge($messages, $this->updateApplication($application, $majorVersion));
460                 }
461             }
462         }
463         
464         return array(
465             'messages' => $messages,
466             'updated'  => $this->_updatedApplications,
467         );
468     }    
469     
470     /**
471      * load the setup.xml file and returns a simplexml object
472      *
473      * @param string $_applicationName name of the application
474      * @return SimpleXMLElement
475      */
476     public function getSetupXml($_applicationName)
477     {
478         $setupXML = $this->_baseDir . ucfirst($_applicationName) . '/Setup/setup.xml';
479
480         if (!file_exists($setupXML)) {
481             throw new Setup_Exception_NotFound(ucfirst($_applicationName)
482                 . '/Setup/setup.xml not found. If application got renamed or deleted, re-run setup.php.');
483         }
484         
485         $xml = simplexml_load_file($setupXML);
486
487         return $xml;
488     }
489     
490     /**
491      * check update
492      *
493      * @param   Tinebase_Model_Application $_application
494      * @throws  Setup_Exception
495      */
496     public function checkUpdate(Tinebase_Model_Application $_application)
497     {
498         $xmlTables = $this->getSetupXml($_application->name);
499         if(isset($xmlTables->tables)) {
500             foreach ($xmlTables->tables[0] as $tableXML) {
501                 $table = Setup_Backend_Schema_Table_Factory::factory('Xml', $tableXML);
502                 if (true == $this->_backend->tableExists($table->name)) {
503                     try {
504                         $this->_backend->checkTable($table);
505                     } catch (Setup_Exception $e) {
506                         Setup_Core::getLogger()->error(__METHOD__ . '::' . __LINE__ . " Checking table failed with message '{$e->getMessage()}'");
507                     }
508                 } else {
509                     throw new Setup_Exception('Table ' . $table->name . ' for application' . $_application->name . " does not exist. \n<strong>Update broken</strong>");
510                 }
511             }
512         }
513     }
514     
515     /**
516      * update installed application
517      *
518      * @param   Tinebase_Model_Application    $_application
519      * @param   string    $_majorVersion
520      * @return  array   messages
521      * @throws  Setup_Exception if current app version is too high
522      */
523     public function updateApplication(Tinebase_Model_Application $_application, $_majorVersion)
524     {
525         $setupXml = $this->getSetupXml($_application->name);
526         $messages = array();
527         
528         switch (version_compare($_application->version, $setupXml->version)) {
529             case -1:
530                 $message = "Executing updates for " . $_application->name . " (starting at " . $_application->version . ")";
531                 
532                 $messages[] = $message;
533                 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' ' . $message);
534
535                 $version = $_application->getMajorAndMinorVersion();
536                 $minor = $version['minor'];
537                 
538                 $className = ucfirst($_application->name) . '_Setup_Update_Release' . $_majorVersion;
539                 if(! class_exists($className)) {
540                     $nextMajorRelease = ($_majorVersion + 1) . ".0";
541                     Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
542                         . " Update class {$className} does not exists, skipping release {$_majorVersion} for app "
543                         . "{$_application->name} and increasing version to $nextMajorRelease"
544                     );
545                     $_application->version = $nextMajorRelease;
546                     Tinebase_Application::getInstance()->updateApplication($_application);
547
548                 } else {
549                     $update = new $className($this->_backend);
550                 
551                     $classMethods = get_class_methods($update);
552               
553                     // we must do at least one update
554                     do {
555                         $functionName = 'update_' . $minor;
556                         
557                         try {
558                             $db = Setup_Core::getDb();
559                             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($db);
560                         
561                             Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
562                                 . ' Updating ' . $_application->name . ' - ' . $functionName
563                             );
564                             
565                             $update->$functionName();
566                         
567                             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
568                 
569                         } catch (Exception $e) {
570                             Tinebase_TransactionManager::getInstance()->rollBack();
571                             Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getMessage());
572                             Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getTraceAsString());
573                             throw $e;
574                         }
575                             
576                         $minor++;
577                     } while(array_search('update_' . $minor, $classMethods) !== false);
578                 }
579                 
580                 $messages[] = "<strong> Updated " . $_application->name . " successfully to " .  $_majorVersion . '.' . $minor . "</strong>";
581                 
582                 // update app version
583                 $updatedApp = Tinebase_Application::getInstance()->getApplicationById($_application->getId());
584                 $_application->version = $updatedApp->version;
585                 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updated ' . $_application->name . " successfully to " .  $_application->version);
586                 $this->_updatedApplications++;
587                 
588                 break;
589                 
590             case 0:
591                 Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' No update needed for ' . $_application->name);
592                 break;
593                 
594             case 1:
595                 throw new Setup_Exception('Current application version is higher than version from setup.xml: '
596                     . $_application->version . ' > ' . $setupXml->version
597                 );
598                 break;
599         }
600         
601         Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Clearing cache after update ...');
602         $this->_enableCaching();
603         Tinebase_Core::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL);
604         
605         return $messages;
606     }
607
608     /**
609      * checks if update is required
610      *
611      * @return boolean
612      */
613     public function updateNeeded($_application)
614     {
615         try {
616             $setupXml = $this->getSetupXml($_application->name);
617         } catch (Setup_Exception_NotFound $senf) {
618             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' ' . $senf->getMessage() . ' Disabling application "' . $_application->name . '".');
619             Tinebase_Application::getInstance()->setApplicationState(array($_application->getId()), Tinebase_Application::DISABLED);
620             return false;
621         }
622         
623         $updateNeeded = version_compare($_application->version, $setupXml->version);
624         
625         if($updateNeeded === -1) {
626             return true;
627         }
628         
629         return false;
630     }
631     
632     /**
633      * search for installed and installable applications
634      *
635      * @return array
636      */
637     public function searchApplications()
638     {
639         // get installable apps
640         $installable = $this->getInstallableApplications();
641         
642         // get installed apps
643         if (Setup_Core::get(Setup_Core::CHECKDB)) {
644             try {
645                 $installed = Tinebase_Application::getInstance()->getApplications(NULL, 'id')->toArray();
646                 
647                 // merge to create result array
648                 $applications = array();
649                 foreach ($installed as $application) {
650                     
651                     if (! (isset($installable[$application['name']]) || array_key_exists($application['name'], $installable))) {
652                         Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' App ' . $application['name'] . ' does not exist any more.');
653                         continue;
654                     }
655                     
656                     $depends = (array) $installable[$application['name']]->depends;
657                     if (isset($depends['application'])) {
658                         $depends = implode(', ', (array) $depends['application']);
659                     }
660                     
661                     $application['current_version'] = (string) $installable[$application['name']]->version;
662                     $application['install_status'] = (version_compare($application['version'], $application['current_version']) === -1) ? 'updateable' : 'uptodate';
663                     $application['depends'] = $depends;
664                     $applications[] = $application;
665                     unset($installable[$application['name']]);
666                 }
667             } catch (Zend_Db_Statement_Exception $zse) {
668                 // no tables exist
669             }
670         }
671         
672         foreach ($installable as $name => $setupXML) {
673             $depends = (array) $setupXML->depends;
674             if (isset($depends['application'])) {
675                 $depends = implode(', ', (array) $depends['application']);
676             }
677             
678             $applications[] = array(
679                 'name'              => $name,
680                 'current_version'   => (string) $setupXML->version,
681                 'install_status'    => 'uninstalled',
682                 'depends'           => $depends,
683             );
684         }
685         
686         return array(
687             'results'       => $applications,
688             'totalcount'    => count($applications)
689         );
690     }
691
692     /**
693      * checks if setup is required
694      *
695      * @return boolean
696      */
697     public function setupRequired()
698     {
699         $result = FALSE;
700         
701         // check if applications table exists / only if db available
702         if (Setup_Core::isRegistered(Setup_Core::DB)) {
703             try {
704                 $applicationTable = Setup_Core::getDb()->describeTable(SQL_TABLE_PREFIX . 'applications');
705                 if (empty($applicationTable)) {
706                     Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Applications table empty');
707                     $result = TRUE;
708                 }
709             } catch (Zend_Db_Statement_Exception $zdse) {
710                 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $zdse->getMessage());
711                 $result = TRUE;
712             } catch (Zend_Db_Adapter_Exception $zdae) {
713                 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $zdae->getMessage());
714                 $result = TRUE;
715             }
716         }
717         
718         return $result;
719     }
720     
721     /**
722      * do php.ini environment check
723      *
724      * @return array
725      */
726     public function environmentCheck()
727     {
728         $result = array();
729         $message = array();
730         $success = TRUE;
731         
732         
733         
734         // check php environment
735         $requiredIniSettings = array(
736             'magic_quotes_sybase'  => 0,
737             'magic_quotes_gpc'     => 0,
738             'magic_quotes_runtime' => 0,
739             'mbstring.func_overload' => 0,
740             'eaccelerator.enable' => 0,
741             'memory_limit' => '48M'
742         );
743         
744         foreach ($requiredIniSettings as $variable => $newValue) {
745             $oldValue = ini_get($variable);
746             
747             if ($variable == 'memory_limit') {
748                 $required = Tinebase_Helper::convertToBytes($newValue);
749                 $set = Tinebase_Helper::convertToBytes($oldValue);
750                 
751                 if ( $set < $required) {
752                     $result[] = array(
753                         'key'       => $variable,
754                         'value'     => FALSE,
755                         'message'   => "You need to set $variable equal or greater than $required (now: $set)." . $this->_helperLink
756                     );
757                     $success = FALSE;
758                 }
759
760             } elseif ($oldValue != $newValue) {
761                 if (ini_set($variable, $newValue) === false) {
762                     $result[] = array(
763                         'key'       => $variable,
764                         'value'     => FALSE,
765                         'message'   => "You need to set $variable from $oldValue to $newValue."  . $this->_helperLink
766                     );
767                     $success = FALSE;
768                 }
769             } else {
770                 $result[] = array(
771                     'key'       => $variable,
772                     'value'     => TRUE,
773                     'message'   => ''
774                 );
775             }
776         }
777         
778         return array(
779             'result'        => $result,
780             'success'       => $success,
781         );
782     }
783     
784     /**
785      * get config file default values
786      *
787      * @return array
788      */
789     public function getConfigDefaults()
790     {
791         $defaultPath = Setup_Core::guessTempDir();
792         
793         $result = array(
794             'database' => array(
795                 'host'  => 'localhost',
796                 'dbname' => 'tine20',
797                 'username' => 'tine20',
798                 'password' => '',
799                 'adapter' => 'pdo_mysql',
800                 'tableprefix' => 'tine20_',
801                 'port'          => 3306
802             ),
803             'logger' => array(
804                 'filename' => $defaultPath . DIRECTORY_SEPARATOR . 'tine20.log',
805                 'priority' => '5'
806             ),
807             'caching' => array(
808                'active' => 1,
809                'lifetime' => 3600,
810                'backend' => 'File',
811                'path' => $defaultPath,
812             ),
813             'tmpdir' => $defaultPath,
814             'session' => array(
815                 'path'      => Tinebase_Session::getSessionDir(),
816                 'liftime'   => 86400,
817             ),
818         );
819         
820         return $result;
821     }
822
823     /**
824      * get config file values
825      *
826      * @return array
827      */
828     public function getConfigData()
829     {
830         $configArray = Setup_Core::get(Setup_Core::CONFIG)->toArray();
831         
832         #####################################
833         # LEGACY/COMPATIBILITY:
834         # (1) had to rename session.save_path key to sessiondir because otherwise the
835         # generic save config method would interpret the "_" as array key/value seperator
836         # (2) moved session config to subgroup 'session'
837         if (empty($configArray['session']) || empty($configArray['session']['path'])) {
838             foreach (array('session.save_path', 'sessiondir') as $deprecatedSessionDir) {
839                 $sessionDir = (isset($configArray[$deprecatedSessionDir]) || array_key_exists($deprecatedSessionDir, $configArray)) ? $configArray[$deprecatedSessionDir] : '';
840                 if (! empty($sessionDir)) {
841                     if (empty($configArray['session'])) {
842                         $configArray['session'] = array();
843                     }
844                     $configArray['session']['path'] = $sessionDir;
845                     Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " config.inc.php key '{$deprecatedSessionDir}' should be renamed to 'path' and moved to 'session' group.");
846                 }
847             }
848         }
849         #####################################
850         
851         return $configArray;
852     }
853     
854     /**
855      * save data to config file
856      *
857      * @param array   $_data
858      * @param boolean $_merge
859      */
860     public function saveConfigData($_data, $_merge = TRUE)
861     {
862         if (!empty($_data['setupuser']['password']) && !Setup_Auth::isMd5($_data['setupuser']['password'])) {
863             $password = $_data['setupuser']['password'];
864             $_data['setupuser']['password'] = md5($_data['setupuser']['password']);
865         }
866         if (Setup_Core::configFileExists() && !Setup_Core::configFileWritable()) {
867             throw new Setup_Exception('Config File is not writeable.');
868         }
869         
870         if (Setup_Core::configFileExists()) {
871             $doLogin = FALSE;
872             $filename = Setup_Core::getConfigFilePath();
873         } else {
874             $doLogin = TRUE;
875             $filename = dirname(__FILE__) . '/../config.inc.php';
876         }
877         
878         $config = $this->writeConfigToFile($_data, $_merge, $filename);
879         
880         Setup_Core::set(Setup_Core::CONFIG, $config);
881         
882         Setup_Core::setupLogger();
883         
884         if ($doLogin && isset($password)) {
885             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Create session for setup user ' . $_data['setupuser']['username']);
886             $this->login($_data['setupuser']['username'], $password);
887         }
888     }
889     
890     /**
891      * write config to a file
892      *
893      * @param array $_data
894      * @param boolean $_merge
895      * @param string $_filename
896      * @return Zend_Config
897      */
898     public function writeConfigToFile($_data, $_merge, $_filename)
899     {
900         // merge config data and active config
901         if ($_merge) {
902             $activeConfig = Setup_Core::get(Setup_Core::CONFIG);
903             $config = new Zend_Config($activeConfig->toArray(), true);
904             $config->merge(new Zend_Config($_data));
905         } else {
906             $config = new Zend_Config($_data);
907         }
908         
909         // write to file
910         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updating config.inc.php');
911         $writer = new Zend_Config_Writer_Array(array(
912             'config'   => $config,
913             'filename' => $_filename,
914         ));
915         $writer->write();
916         
917         return $config;
918     }
919     
920     /**
921      * load authentication data
922      *
923      * @return array
924      */
925     public function loadAuthenticationData()
926     {
927         return array(
928             'authentication'    => $this->_getAuthProviderData(),
929             'accounts'          => $this->_getAccountsStorageData(),
930             'redirectSettings'  => $this->_getRedirectSettings(),
931             'password'          => $this->_getPasswordSettings(),
932             'saveusername'      => $this->_getReuseUsernameSettings()
933         );
934     }
935     
936     /**
937      * Update authentication data
938      *
939      * Needs Tinebase tables to store the data, therefore
940      * installs Tinebase if it is not already installed
941      *
942      * @param array $_authenticationData
943      *
944      * @return bool
945      */
946     public function saveAuthentication($_authenticationData)
947     {
948         if ($this->isInstalled('Tinebase')) {
949             // NOTE: Tinebase_Setup_Initialiser calls this function again so
950             //       we come to this point on initial installation _and_ update
951             $this->_updateAuthentication($_authenticationData);
952         } else {
953             $installationOptions = array('authenticationData' => $_authenticationData);
954             $this->installApplications(array('Tinebase'), $installationOptions);
955         }
956     }
957
958     /**
959      * Save {@param $_authenticationData} to config file
960      *
961      * @param array $_authenticationData [hash containing settings for authentication and accountsStorage]
962      * @return void
963      */
964     protected function _updateAuthentication($_authenticationData)
965     {
966         // this is a dangerous TRACE as there might be passwords in here!
967         //if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_authenticationData, TRUE));
968
969         $this->_enableCaching();
970         
971         if (isset($_authenticationData['authentication'])) {
972             $this->_updateAuthenticationProvider($_authenticationData['authentication']);
973         }
974         
975         if (isset($_authenticationData['accounts'])) {
976             $this->_updateAccountsStorage($_authenticationData['accounts']);
977         }
978         
979         if (isset($_authenticationData['redirectSettings'])) {
980             $this->_updateRedirectSettings($_authenticationData['redirectSettings']);
981         }
982         
983         if (isset($_authenticationData['password'])) {
984             $this->_updatePasswordSettings($_authenticationData['password']);
985         }
986         
987         if (isset($_authenticationData['saveusername'])) {
988             $this->_updateReuseUsername($_authenticationData['saveusername']);
989         }
990         
991         if (isset($_authenticationData['acceptedTermsVersion'])) {
992             $this->saveAcceptedTerms($_authenticationData['acceptedTermsVersion']);
993         }
994     }
995     
996     /**
997      * enable caching to make sure cache gets cleaned if config options change
998      */
999     protected function _enableCaching()
1000     {
1001         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Activate caching backend if available ...');
1002         
1003         Tinebase_Core::setupCache();
1004     }
1005     
1006     /**
1007      * Update authentication provider
1008      *
1009      * @param array $_data
1010      * @return void
1011      */
1012     protected function _updateAuthenticationProvider($_data)
1013     {
1014         Tinebase_Auth::setBackendType($_data['backend']);
1015         $config = (isset($_data[$_data['backend']])) ? $_data[$_data['backend']] : $_data;
1016         
1017         $excludeKeys = array('adminLoginName', 'adminPassword', 'adminPasswordConfirmation');
1018         foreach ($excludeKeys as $key) {
1019             if ((isset($config[$key]) || array_key_exists($key, $config))) {
1020                 unset($config[$key]);
1021             }
1022         }
1023         
1024         Tinebase_Auth::setBackendConfiguration($config, null, true);
1025         Tinebase_Auth::saveBackendConfiguration();
1026     }
1027     
1028     /**
1029      * Update accountsStorage
1030      *
1031      * @param array $_data
1032      * @return void
1033      */
1034     protected function _updateAccountsStorage($_data)
1035     {
1036         $originalBackend = Tinebase_User::getConfiguredBackend();
1037         $newBackend = $_data['backend'];
1038         
1039         Tinebase_User::setBackendType($_data['backend']);
1040         $config = (isset($_data[$_data['backend']])) ? $_data[$_data['backend']] : $_data;
1041         Tinebase_User::setBackendConfiguration($config, null, true);
1042         Tinebase_User::saveBackendConfiguration();
1043         
1044         if ($originalBackend != $newBackend && $this->isInstalled('Addressbook') && $originalBackend == Tinebase_User::SQL) {
1045             Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Switching from $originalBackend to $newBackend account storage");
1046             try {
1047                 $db = Setup_Core::getDb();
1048                 $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($db);
1049                 $this->_migrateFromSqlAccountsStorage();
1050                 Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
1051         
1052             } catch (Exception $e) {
1053                 Tinebase_TransactionManager::getInstance()->rollBack();
1054                 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getMessage());
1055                 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getTraceAsString());
1056                 
1057                 Tinebase_User::setBackendType($originalBackend);
1058                 Tinebase_User::saveBackendConfiguration();
1059                 
1060                 throw $e;
1061             }
1062         }
1063     }
1064     
1065     /**
1066      * migrate from SQL account storage to another one (for example LDAP)
1067      * - deletes all users, groups and roles because they will be
1068      *   imported from new accounts storage backend
1069      */
1070     protected function _migrateFromSqlAccountsStorage()
1071     {
1072         Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Deleting all user accounts, groups, roles and rights');
1073         Tinebase_User::factory(Tinebase_User::SQL)->deleteAllUsers();
1074         
1075         $contactSQLBackend = new Addressbook_Backend_Sql();
1076         $allUserContactIds = $contactSQLBackend->search(new Addressbook_Model_ContactFilter(array('type' => 'user')), null, true);
1077         if (count($allUserContactIds) > 0) {
1078             $contactSQLBackend->delete($allUserContactIds);
1079         }
1080         
1081         
1082         Tinebase_Group::factory(Tinebase_Group::SQL)->deleteAllGroups();
1083         $listsSQLBackend = new Addressbook_Backend_List();
1084         $allGroupListIds = $listsSQLBackend->search(new Addressbook_Model_ListFilter(array('type' => 'group')), null, true);
1085         if (count($allGroupListIds) > 0) {
1086             $listsSQLBackend->delete($allGroupListIds);
1087         }
1088
1089         $roles = Tinebase_Acl_Roles::getInstance();
1090         $roles->deleteAllRoles();
1091         
1092         // import users (from new backend) / create initial users (SQL)
1093         Tinebase_User::syncUsers(array('syncContactData' => TRUE));
1094         
1095         $roles->createInitialRoles();
1096         $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
1097         foreach ($applications as $application) {
1098              Setup_Initialize::initializeApplicationRights($application);
1099         }
1100     }
1101     
1102     /**
1103      * Update redirect settings
1104      *
1105      * @param array $_data
1106      * @return void
1107      */
1108     protected function _updateRedirectSettings($_data)
1109     {
1110         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_data, 1));
1111         $keys = array(Tinebase_Config::REDIRECTURL, Tinebase_Config::REDIRECTALWAYS, Tinebase_Config::REDIRECTTOREFERRER);
1112         foreach ($keys as $key) {
1113             if ((isset($_data[$key]) || array_key_exists($key, $_data))) {
1114                 if (strlen($_data[$key]) === 0) {
1115                     Tinebase_Config::getInstance()->delete($key);
1116                 } else {
1117                     Tinebase_Config::getInstance()->set($key, $_data[$key]);
1118                 }
1119             }
1120         }
1121     }
1122
1123         /**
1124      * update pw settings
1125      * 
1126      * @param array $data
1127      */
1128     protected function _updatePasswordSettings($data)
1129     {
1130         foreach ($data as $config => $value) {
1131             Tinebase_Config::getInstance()->set($config, $value);
1132         }
1133     }
1134     
1135     /**
1136      * update pw settings
1137      * 
1138      * @param array $data
1139      */
1140     protected function _updateReuseUsername($data)
1141     {
1142         foreach ($data as $config => $value) {
1143             Tinebase_Config::getInstance()->set($config, $value);
1144         }
1145     }
1146     
1147     /**
1148      *
1149      * get auth provider data
1150      *
1151      * @return array
1152      *
1153      * @todo get this from config table instead of file!
1154      */
1155     protected function _getAuthProviderData()
1156     {
1157         $result = Tinebase_Auth::getBackendConfigurationWithDefaults(Setup_Core::get(Setup_Core::CHECKDB));
1158         $result['backend'] = (Setup_Core::get(Setup_Core::CHECKDB)) ? Tinebase_Auth::getConfiguredBackend() : Tinebase_Auth::SQL;
1159
1160         return $result;
1161     }
1162     
1163     /**
1164      * get Accounts storage data
1165      *
1166      * @return array
1167      */
1168     protected function _getAccountsStorageData()
1169     {
1170         $result = Tinebase_User::getBackendConfigurationWithDefaults(Setup_Core::get(Setup_Core::CHECKDB));
1171         $result['backend'] = (Setup_Core::get(Setup_Core::CHECKDB)) ? Tinebase_User::getConfiguredBackend() : Tinebase_User::SQL;
1172
1173         return $result;
1174     }
1175     
1176     /**
1177      * Get redirect Settings from config table.
1178      * If Tinebase is not installed, default values will be returned.
1179      *
1180      * @return array
1181      */
1182     protected function _getRedirectSettings()
1183     {
1184         $return = array(
1185               Tinebase_Config::REDIRECTURL => '',
1186               Tinebase_Config::REDIRECTTOREFERRER => '0'
1187         );
1188         if (Setup_Core::get(Setup_Core::CHECKDB) && $this->isInstalled('Tinebase')) {
1189             $return[Tinebase_Config::REDIRECTURL] = Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTURL, '');
1190             $return[Tinebase_Config::REDIRECTTOREFERRER] = Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTTOREFERRER, '');
1191         }
1192         return $return;
1193     }
1194
1195     /**
1196      * get password settings
1197      * 
1198      * @return array
1199      * 
1200      * @todo should use generic mechanism to fetch setup related configs
1201      */
1202     protected function _getPasswordSettings()
1203     {
1204         $configs = array(
1205             Tinebase_Config::PASSWORD_CHANGE                     => 1,
1206             Tinebase_Config::PASSWORD_POLICY_ACTIVE              => 0,
1207             Tinebase_Config::PASSWORD_POLICY_ONLYASCII           => 0,
1208             Tinebase_Config::PASSWORD_POLICY_MIN_LENGTH          => 0,
1209             Tinebase_Config::PASSWORD_POLICY_MIN_WORD_CHARS      => 0,
1210             Tinebase_Config::PASSWORD_POLICY_MIN_UPPERCASE_CHARS => 0,
1211             Tinebase_Config::PASSWORD_POLICY_MIN_SPECIAL_CHARS   => 0,
1212             Tinebase_Config::PASSWORD_POLICY_MIN_NUMBERS         => 0,
1213             Tinebase_Config::PASSWORD_POLICY_CHANGE_AFTER        => 0,
1214         );
1215
1216         $result = array();
1217         $tinebaseInstalled = $this->isInstalled('Tinebase');
1218         foreach ($configs as $config => $default) {
1219             $result[$config] = ($tinebaseInstalled) ? Tinebase_Config::getInstance()->get($config, $default) : $default;
1220         }
1221         
1222         return $result;
1223     }
1224     
1225     /**
1226      * get Reuse Username to login textbox
1227      * 
1228      * @return array
1229      * 
1230      * @todo should use generic mechanism to fetch setup related configs
1231      */
1232     protected function _getReuseUsernameSettings()
1233     {
1234         $configs = array(
1235             Tinebase_Config::REUSEUSERNAME_SAVEUSERNAME         => 0,
1236         );
1237
1238         $result = array();
1239         $tinebaseInstalled = $this->isInstalled('Tinebase');
1240         foreach ($configs as $config => $default) {
1241             $result[$config] = ($tinebaseInstalled) ? Tinebase_Config::getInstance()->get($config, $default) : $default;
1242         }
1243         
1244         return $result;
1245     }
1246     
1247     /**
1248      * get email config
1249      *
1250      * @return array
1251      */
1252     public function getEmailConfig()
1253     {
1254         $result = array();
1255         
1256         foreach ($this->_emailConfigKeys as $configName => $configKey) {
1257             $config = Tinebase_Config::getInstance()->get($configKey, new Tinebase_Config_Struct(array()))->toArray();
1258             if (! empty($config) && ! isset($config['active'])) {
1259                 $config['active'] = TRUE;
1260             }
1261             $result[$configName] = $config;
1262         }
1263         
1264         return $result;
1265     }
1266     
1267     /**
1268      * save email config
1269      *
1270      * @param array $_data
1271      * @return void
1272      */
1273     public function saveEmailConfig($_data)
1274     {
1275         // this is a dangerous TRACE as there might be passwords in here!
1276         //if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_data, TRUE));
1277         
1278         $this->_enableCaching();
1279         
1280         foreach ($this->_emailConfigKeys as $configName => $configKey) {
1281             if ((isset($_data[$configName]) || array_key_exists($configName, $_data))) {
1282                 // fetch current config first and preserve all values that aren't in $_data array
1283                 $currentConfig = Tinebase_Config::getInstance()->get($configKey, new Tinebase_Config_Struct(array()))->toArray();
1284                 $newConfig = array_merge($_data[$configName], array_diff_key($currentConfig, $_data[$configName]));
1285                 Tinebase_Config::getInstance()->set($configKey, $newConfig);
1286             }
1287         }
1288     }
1289     
1290     /**
1291      * returns all email config keys
1292      *
1293      * @return array
1294      */
1295     public function getEmailConfigKeys()
1296     {
1297         return $this->_emailConfigKeys;
1298     }
1299     
1300     /**
1301      * get accepted terms config
1302      *
1303      * @return integer
1304      */
1305     public function getAcceptedTerms()
1306     {
1307         return Tinebase_Config::getInstance()->get(Tinebase_Config::ACCEPTEDTERMSVERSION, 0);
1308     }
1309     
1310     /**
1311      * save acceptedTermsVersion
1312      *
1313      * @param $_data
1314      * @return void
1315      */
1316     public function saveAcceptedTerms($_data)
1317     {
1318         Tinebase_Config::getInstance()->set(Tinebase_Config::ACCEPTEDTERMSVERSION, $_data);
1319     }
1320     
1321     /**
1322      * save config option in db
1323      *
1324      * @param string $key
1325      * @param string|array $value
1326      * @param string $applicationName
1327      * @return void
1328      */
1329     public function setConfigOption($key, $value, $applicationName = 'Tinebase')
1330     {
1331         $config = Tinebase_Config_Abstract::factory($applicationName);
1332         
1333         if ($config) {
1334             $config->set($key, $value);
1335         }
1336     }
1337     
1338     /**
1339      * create new setup user session
1340      *
1341      * @param   string $_username
1342      * @param   string $_password
1343      * @return  bool
1344      */
1345     public function login($_username, $_password)
1346     {
1347         $setupAuth = new Setup_Auth($_username, $_password);
1348         $authResult = Zend_Auth::getInstance()->authenticate($setupAuth);
1349         
1350         if ($authResult->isValid()) {
1351             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Valid credentials, setting username in session and registry.');
1352             Tinebase_Session::regenerateId();
1353             
1354             Setup_Core::set(Setup_Core::USER, $_username);
1355             Setup_Session::getSessionNamespace()->setupuser = $_username;
1356             return true;
1357             
1358         } else {
1359             Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Invalid credentials! ' . print_r($authResult->getMessages(), TRUE));
1360             Tinebase_Session::expireSessionCookie();
1361             sleep(2);
1362             return false;
1363         }
1364     }
1365     
1366     /**
1367      * destroy session
1368      *
1369      * @return void
1370      */
1371     public function logout()
1372     {
1373         $_SESSION = array();
1374         
1375         Tinebase_Session::destroyAndRemoveCookie();
1376     }
1377     
1378     /**
1379      * install list of applications
1380      *
1381      * @param array $_applications list of application names
1382      * @param array | optional $_options
1383      * @return void
1384      */
1385     public function installApplications($_applications, $_options = null)
1386     {
1387         $this->_clearCache();
1388         
1389         // check requirements for initial install / add required apps to list
1390         if (! $this->isInstalled('Tinebase')) {
1391     
1392             $minimumRequirements = array('Addressbook', 'Tinebase', 'Admin');
1393             
1394             foreach ($minimumRequirements as $requiredApp) {
1395                 if (!in_array($requiredApp, $_applications) && !$this->isInstalled($requiredApp)) {
1396                     // Addressbook has to be installed with Tinebase for initial data (user contact)
1397                     Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
1398                         . ' ' . $requiredApp . ' has to be installed first (adding it to list).'
1399                     );
1400                     $_applications[] = $requiredApp;
1401                 }
1402             }
1403         } else {
1404             $setupUser = Setup_Update_Abstract::getSetupFromConfigOrCreateOnTheFly();
1405             if ($setupUser && ! Tinebase_Core::getUser() instanceof Tinebase_Model_User) {
1406                 Tinebase_Core::set(Tinebase_Core::USER, $setupUser);
1407             }
1408         }
1409         
1410         // get xml and sort apps first
1411         $applications = array();
1412         foreach ($_applications as $applicationName) {
1413             if ($this->isInstalled($applicationName)) {
1414                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1415                     . " skipping installation of application {$applicationName} because it is already installed");
1416             } else {
1417                 $applications[$applicationName] = $this->getSetupXml($applicationName);
1418             }
1419         }
1420         $applications = $this->_sortInstallableApplications($applications);
1421         
1422         Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Installing applications: ' . print_r(array_keys($applications), true));
1423         
1424         foreach ($applications as $name => $xml) {
1425             if (! $xml) {
1426                 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Could not install application ' . $name);
1427             } else {
1428                 $this->_installApplication($xml, $_options);
1429             }
1430         }
1431     }
1432
1433     /**
1434      * install tine from dump file
1435      *
1436      * @param $options
1437      * @throws Setup_Exception
1438      * @return boolean
1439      */
1440     public function installFromDump($options)
1441     {
1442         $this->_clearCache();
1443
1444         if ($this->isInstalled('Tinebase')) {
1445             throw new Setup_Exception('Tinebase already installed!');
1446         }
1447
1448         $mysqlBackupFile = $options['backupDir'] . '/tine20_mysql.sql.bz2';
1449         if (! file_exists($mysqlBackupFile)) {
1450             throw new Setup_Exception("$mysqlBackupFile not found");
1451         }
1452
1453         Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Installing from dump ' . $mysqlBackupFile);
1454
1455         $this->_replaceTinebaseidInDump($mysqlBackupFile);
1456         $this->restore($options);
1457
1458         $setupUser = Setup_Update_Abstract::getSetupFromConfigOrCreateOnTheFly();
1459         if ($setupUser && ! Tinebase_Core::getUser() instanceof Tinebase_Model_User) {
1460             Tinebase_Core::set(Tinebase_Core::USER, $setupUser);
1461         }
1462
1463         // set the replication master id
1464         $tinebase = Tinebase_Application::getInstance()->getApplicationByName('Tinebase');
1465         $state = $tinebase->state;
1466         if (!is_array($state)) {
1467             $state = array();
1468         }
1469         $state[Tinebase_Model_Application::STATE_REPLICATION_MASTER_ID] = Tinebase_Timemachine_ModificationLog::getInstance()->getMaxInstanceSeq();
1470         $tinebase->state = $state;
1471         Tinebase_Application::getInstance()->updateApplication($tinebase);
1472
1473         $this->updateApplications();
1474
1475         return true;
1476     }
1477
1478     /**
1479      * replace old Tinebase ID in dump to make sure we have a unique installation ID
1480      *
1481      * TODO: think about moving the Tinebase ID (and more info) to a metadata.json file in the backup zip
1482      *
1483      * @param $mysqlBackupFile
1484      * @throws Setup_Exception
1485      */
1486     protected function _replaceTinebaseidInDump($mysqlBackupFile)
1487     {
1488         // fetch old Tinebase ID
1489         $cmd = "bzcat $mysqlBackupFile | grep \",'Tinebase',\"";
1490         $result = exec($cmd);
1491         if (! preg_match("/'([0-9a-f]+)','Tinebase'/", $result, $matches)) {
1492             throw new Setup_Exception('could not find Tinebase ID in dump');
1493         }
1494         $oldTinebaseId = $matches[1];
1495
1496         $cmd = "bzcat $mysqlBackupFile | sed s/"
1497             . $oldTinebaseId . '/'
1498             . Tinebase_Record_Abstract::generateUID() . "/g | " // g for global!
1499             . "bzip2 > " . $mysqlBackupFile . '.tmp';
1500
1501         Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $cmd);
1502
1503         exec($cmd);
1504         copy($mysqlBackupFile . '.tmp', $mysqlBackupFile);
1505         unlink($mysqlBackupFile . '.tmp');
1506     }
1507
1508     /**
1509      * delete list of applications
1510      *
1511      * @param array $_applications list of application names
1512      */
1513     public function uninstallApplications($_applications)
1514     {
1515         if (null === ($user = Setup_Update_Abstract::getSetupFromConfigOrCreateOnTheFly())) {
1516             throw new Tinebase_Exception('could not create setup user');
1517         }
1518         Tinebase_Core::set(Tinebase_Core::USER, $user);
1519
1520         $this->_clearCache();
1521
1522         $installedApps = Tinebase_Application::getInstance()->getApplications();
1523         
1524         // uninstall all apps if tinebase ist going to be uninstalled
1525         if (count($installedApps) !== count($_applications) && in_array('Tinebase', $_applications)) {
1526             $_applications = $installedApps->name;
1527         }
1528         
1529         // deactivate foreign key check if all installed apps should be uninstalled
1530         $deactivatedForeignKeyCheck = false;
1531         if (count($installedApps) == count($_applications) && get_class($this->_backend) == 'Setup_Backend_Mysql') {
1532             $this->_backend->setForeignKeyChecks(0);
1533             $deactivatedForeignKeyCheck = true;
1534         }
1535
1536         // get xml and sort apps first
1537         $applications = array();
1538         foreach ($_applications as $applicationName) {
1539             try {
1540                 $applications[$applicationName] = $this->getSetupXml($applicationName);
1541             } catch (Setup_Exception_NotFound $senf) {
1542                 // application setup.xml not found
1543                 Tinebase_Exception::log($senf);
1544                 $applications[$applicationName] = null;
1545             }
1546         }
1547         $applications = $this->_sortUninstallableApplications($applications);
1548
1549         foreach ($applications as $name => $xml) {
1550             $app = Tinebase_Application::getInstance()->getApplicationByName($name);
1551             $this->_uninstallApplication($app);
1552         }
1553
1554         if (true === $deactivatedForeignKeyCheck) {
1555             $this->_backend->setForeignKeyChecks(1);
1556         }
1557     }
1558     
1559     /**
1560      * install given application
1561      *
1562      * @param  SimpleXMLElement $_xml
1563      * @param  array | optional $_options
1564      * @return void
1565      * @throws Tinebase_Exception_Backend_Database
1566      */
1567     protected function _installApplication(SimpleXMLElement $_xml, $_options = null)
1568     {
1569         if ($this->_backend === NULL) {
1570             throw new Tinebase_Exception_Backend_Database('Need configured and working database backend for install.');
1571         }
1572         
1573         if (!$this->checkDatabasePrefix()) {
1574             throw new Tinebase_Exception_Backend_Database('Tableprefix is too long');
1575         }
1576         
1577         try {
1578             if (Setup_Core::isLogLevel(Zend_Log::INFO)) Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Installing application: ' . $_xml->name);
1579
1580             $createdTables = array();
1581
1582             // traditional xml declaration
1583             if (isset($_xml->tables)) {
1584                 foreach ($_xml->tables[0] as $tableXML) {
1585                     $table = Setup_Backend_Schema_Table_Factory::factory('Xml', $tableXML);
1586                     if ($this->_createTable($table) !== true) {
1587                         // table was gracefully not created, maybe due to missing requirements, just continue
1588                         continue;
1589                     }
1590                     $createdTables[] = $table;
1591                 }
1592             }
1593
1594             // do we have modelconfig + doctrine
1595             else {
1596                 $application = Setup_Core::getApplicationInstance($_xml->name, '', true);
1597                 $models = $application->getModels(true /* MCv2only */);
1598
1599                 if (count($models) > 0) {
1600                     // create tables using doctrine 2
1601                     Setup_SchemaTool::createSchema($_xml->name, $models);
1602
1603                     // adopt to old workflow
1604                     foreach ($models as $model) {
1605                         $modelConfiguration = $model::getConfiguration();
1606                         $createdTables[] = (object)array(
1607                             'name' => Tinebase_Helper::array_value('name', $modelConfiguration->getTable()),
1608                             'version' => $modelConfiguration->getVersion(),
1609                         );
1610                     }
1611                 }
1612             }
1613     
1614             $application = new Tinebase_Model_Application(array(
1615                 'name'      => (string)$_xml->name,
1616                 'status'    => $_xml->status ? (string)$_xml->status : Tinebase_Application::ENABLED,
1617                 'order'     => $_xml->order ? (string)$_xml->order : 99,
1618                 'version'   => (string)$_xml->version
1619             ));
1620
1621             $application = Tinebase_Application::getInstance()->addApplication($application);
1622             
1623             // keep track of tables belonging to this application
1624             foreach ($createdTables as $table) {
1625                 Tinebase_Application::getInstance()->addApplicationTable($application, (string) $table->name, (int) $table->version);
1626             }
1627             
1628             // insert default records
1629             if (isset($_xml->defaultRecords)) {
1630                 foreach ($_xml->defaultRecords[0] as $record) {
1631                     $this->_backend->execInsertStatement($record);
1632                 }
1633             }
1634             
1635             Setup_Initialize::initialize($application, $_options);
1636
1637             // look for import definitions and put them into the db
1638             $this->createImportExportDefinitions($application);
1639         } catch (Exception $e) {
1640             Tinebase_Exception::log($e, /* suppress trace */ false);
1641             throw $e;
1642         }
1643     }
1644
1645     protected function _createTable($table)
1646     {
1647         if (Setup_Core::isLogLevel(Zend_Log::DEBUG)) Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Creating table: ' . $table->name);
1648
1649         try {
1650             $result = $this->_backend->createTable($table);
1651         } catch (Zend_Db_Statement_Exception $zdse) {
1652             throw new Tinebase_Exception_Backend_Database('Could not create table: ' . $zdse->getMessage());
1653         } catch (Zend_Db_Adapter_Exception $zdae) {
1654             throw new Tinebase_Exception_Backend_Database('Could not create table: ' . $zdae->getMessage());
1655         }
1656
1657         return $result;
1658     }
1659
1660     /**
1661      * look for export & import definitions and put them into the db
1662      *
1663      * @param Tinebase_Model_Application $_application
1664      */
1665     public function createImportExportDefinitions($_application)
1666     {
1667         foreach (array('Import', 'Export') as $type) {
1668             $path =
1669                 $this->_baseDir . $_application->name .
1670                 DIRECTORY_SEPARATOR . $type . DIRECTORY_SEPARATOR . 'definitions';
1671     
1672             if (file_exists($path)) {
1673                 foreach (new DirectoryIterator($path) as $item) {
1674                     $filename = $path . DIRECTORY_SEPARATOR . $item->getFileName();
1675                     if (preg_match("/\.xml/", $filename)) {
1676                         try {
1677                             Tinebase_ImportExportDefinition::getInstance()->updateOrCreateFromFilename($filename, $_application);
1678                         } catch (Exception $e) {
1679                             Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
1680                                 . ' Not installing import/export definion from file: ' . $filename
1681                                 . ' / Error message: ' . $e->getMessage());
1682                         }
1683                     }
1684                 }
1685             }
1686
1687             $path =
1688                 $this->_baseDir . $_application->name .
1689                 DIRECTORY_SEPARATOR . $type . DIRECTORY_SEPARATOR . 'templates';
1690
1691             if (file_exists($path)) {
1692                 $fileSystem = Tinebase_FileSystem::getInstance();
1693
1694                 $basepath = $fileSystem->getApplicationBasePath(
1695                     'Tinebase',
1696                     Tinebase_FileSystem::FOLDER_TYPE_SHARED
1697                 ) . '/' . strtolower($type);
1698
1699                 if (false === $fileSystem->isDir($basepath)) {
1700                     $fileSystem->createAclNode($basepath);
1701                 }
1702
1703                 $templateAppPath = Tinebase_Model_Tree_Node_Path::createFromPath($basepath . '/templates/' . $_application->name);
1704
1705                 if (! $fileSystem->isDir($templateAppPath->statpath)) {
1706                     $fileSystem->mkdir($templateAppPath->statpath);
1707                 }
1708
1709                 foreach (new DirectoryIterator($path) as $item) {
1710                     if (!$item->isFile()) {
1711                         continue;
1712                     }
1713                     if (false === ($content = file_get_contents($item->getPathname()))) {
1714                         Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
1715                             . ' Could not import template: ' . $item->getPathname());
1716                         continue;
1717                     }
1718                     if (false === ($file = $fileSystem->fopen($templateAppPath->statpath . '/' . $item->getFileName(), 'w'))) {
1719                         Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
1720                             . ' could not open ' . $templateAppPath->statpath . '/' . $item->getFileName() . ' for writting');
1721                         continue;
1722                     }
1723                     fwrite($file, $content);
1724                     if (true !== $fileSystem->fclose($file)) {
1725                         Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
1726                             . ' write to ' . $templateAppPath->statpath . '/' . $item->getFileName() . ' did not succeed');
1727                         continue;
1728                     }
1729                 }
1730             }
1731         }
1732     }
1733     
1734     /**
1735      * uninstall app
1736      *
1737      * @param Tinebase_Model_Application $_application
1738      * @throws Setup_Exception
1739      */
1740     protected function _uninstallApplication(Tinebase_Model_Application $_application, $uninstallAll = false)
1741     {
1742         if ($this->_backend === null) {
1743             throw new Setup_Exception('No setup backend available');
1744         }
1745         
1746         Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Uninstall ' . $_application);
1747         try {
1748             $applicationTables = Tinebase_Application::getInstance()->getApplicationTables($_application);
1749         } catch (Zend_Db_Statement_Exception $zdse) {
1750             Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . " " . $zdse);
1751             throw new Setup_Exception('Could not uninstall ' . $_application . ' (you might need to remove the tables by yourself): ' . $zdse->getMessage());
1752         }
1753         $disabledFK = FALSE;
1754         $db = Tinebase_Core::getDb();
1755         
1756         do {
1757             $oldCount = count($applicationTables);
1758
1759             if ($_application->name == 'Tinebase') {
1760                 $installedApplications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
1761                 if (count($installedApplications) !== 1) {
1762                     Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Installed apps: ' . print_r($installedApplications->name, true));
1763                     throw new Setup_Exception_Dependency('Failed to uninstall application "Tinebase" because of dependencies to other installed applications.');
1764                 }
1765             }
1766
1767             foreach ($applicationTables as $key => $table) {
1768                 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Remove table: $table");
1769                 
1770                 try {
1771                     // drop foreign keys which point to current table first
1772                     $foreignKeys = $this->_backend->getExistingForeignKeys($table);
1773                     foreach ($foreignKeys as $foreignKey) {
1774                         Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . 
1775                             " Drop index: " . $foreignKey['table_name'] . ' => ' . $foreignKey['constraint_name']);
1776                         $this->_backend->dropForeignKey($foreignKey['table_name'], $foreignKey['constraint_name']);
1777                     }
1778                     
1779                     // drop table
1780                     $this->_backend->dropTable($table);
1781                     
1782                     if ($_application->name != 'Tinebase') {
1783                         Tinebase_Application::getInstance()->removeApplicationTable($_application, $table);
1784                     }
1785                     
1786                     unset($applicationTables[$key]);
1787                     
1788                 } catch (Zend_Db_Statement_Exception $e) {
1789                     // we need to catch exceptions here, as we don't want to break here, as a table
1790                     // might still have some foreign keys
1791                     // this works with mysql only
1792                     $message = $e->getMessage();
1793                     Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " Could not drop table $table - " . $message);
1794                     
1795                     // remove app table if table not found in db
1796                     if (preg_match('/SQLSTATE\[42S02\]: Base table or view not found/', $message) && $_application->name != 'Tinebase') {
1797                         Tinebase_Application::getInstance()->removeApplicationTable($_application, $table);
1798                         unset($applicationTables[$key]);
1799                     } else {
1800                         Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . " Disabling foreign key checks ... ");
1801                         if ($db instanceof Zend_Db_Adapter_Pdo_Mysql) {
1802                             $db->query("SET FOREIGN_KEY_CHECKS=0");
1803                         }
1804                         $disabledFK = TRUE;
1805                     }
1806                 }
1807             }
1808             
1809             if ($oldCount > 0 && count($applicationTables) == $oldCount) {
1810                 throw new Setup_Exception('dead lock detected oldCount: ' . $oldCount);
1811             }
1812         } while (count($applicationTables) > 0);
1813         
1814         if ($disabledFK) {
1815             if ($db instanceof Zend_Db_Adapter_Pdo_Mysql) {
1816                 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . " Enabling foreign key checks again... ");
1817                 $db->query("SET FOREIGN_KEY_CHECKS=1");
1818             }
1819         }
1820         
1821         if ($_application->name != 'Tinebase') {
1822             if (!$uninstallAll) {
1823                 Tinebase_Relations::getInstance()->removeApplication($_application->name);
1824
1825                 Tinebase_Timemachine_ModificationLog::getInstance()->removeApplication($_application);
1826
1827                 // delete containers, config options and other data for app
1828                 Tinebase_Application::getInstance()->removeApplicationAuxiliaryData($_application);
1829             }
1830             
1831             // remove application from table of installed applications
1832             Tinebase_Application::getInstance()->deleteApplication($_application);
1833         }
1834
1835         Setup_Uninitialize::uninitialize($_application);
1836
1837         Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Removed app: " . $_application->name);
1838     }
1839
1840     /**
1841      * sort applications by checking dependencies
1842      *
1843      * @param array $_applications
1844      * @return array
1845      */
1846     protected function _sortInstallableApplications($_applications)
1847     {
1848         $result = array();
1849         
1850         // begin with Tinebase, Admin and Addressbook
1851         $alwaysOnTop = array('Tinebase', 'Admin', 'Addressbook');
1852         foreach ($alwaysOnTop as $app) {
1853             if (isset($_applications[$app])) {
1854                 $result[$app] = $_applications[$app];
1855                 unset($_applications[$app]);
1856             }
1857         }
1858         
1859         // get all apps to install ($name => $dependencies)
1860         $appsToSort = array();
1861         foreach($_applications as $name => $xml) {
1862             $depends = (array) $xml->depends;
1863             if (isset($depends['application'])) {
1864                 if ($depends['application'] == 'Tinebase') {
1865                     $appsToSort[$name] = array();
1866                     
1867                 } else {
1868                     $depends['application'] = (array) $depends['application'];
1869                     
1870                     foreach ($depends['application'] as $app) {
1871                         // don't add tinebase (all apps depend on tinebase)
1872                         if ($app != 'Tinebase') {
1873                             $appsToSort[$name][] = $app;
1874                         }
1875                     }
1876                 }
1877             } else {
1878                 $appsToSort[$name] = array();
1879             }
1880         }
1881         
1882         //Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($appsToSort, true));
1883         
1884         // re-sort apps
1885         $count = 0;
1886         while (count($appsToSort) > 0 && $count < MAXLOOPCOUNT) {
1887             
1888             foreach($appsToSort as $name => $depends) {
1889
1890                 if (empty($depends)) {
1891                     // no dependencies left -> copy app to result set
1892                     $result[$name] = $_applications[$name];
1893                     unset($appsToSort[$name]);
1894                 } else {
1895                     foreach ($depends as $key => $dependingAppName) {
1896                         if (in_array($dependingAppName, array_keys($result)) || $this->isInstalled($dependingAppName)) {
1897                             // remove from depending apps because it is already in result set
1898                             unset($appsToSort[$name][$key]);
1899                         }
1900                     }
1901                 }
1902             }
1903             $count++;
1904         }
1905         
1906         if ($count == MAXLOOPCOUNT) {
1907             Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ .
1908                 " Some Applications could not be installed because of (cyclic?) dependencies: " . print_r(array_keys($appsToSort), TRUE));
1909         }
1910         
1911         return $result;
1912     }
1913
1914     /**
1915      * sort applications by checking dependencies
1916      *
1917      * @param array $_applications
1918      * @return array
1919      */
1920     protected function _sortUninstallableApplications($_applications)
1921     {
1922         $result = array();
1923         
1924         // get all apps to uninstall ($name => $dependencies)
1925         $appsToSort = array();
1926         foreach($_applications as $name => $xml) {
1927             if ($name !== 'Tinebase') {
1928                 $depends = $xml ? (array) $xml->depends : array();
1929                 if (isset($depends['application'])) {
1930                     if ($depends['application'] == 'Tinebase') {
1931                         $appsToSort[$name] = array();
1932                         
1933                     } else {
1934                         $depends['application'] = (array) $depends['application'];
1935                         
1936                         foreach ($depends['application'] as $app) {
1937                             // don't add tinebase (all apps depend on tinebase)
1938                             if ($app != 'Tinebase') {
1939                                 $appsToSort[$name][] = $app;
1940                             }
1941                         }
1942                     }
1943                 } else {
1944                     $appsToSort[$name] = array();
1945                 }
1946             }
1947         }
1948         
1949         // re-sort apps
1950         $count = 0;
1951         while (count($appsToSort) > 0 && $count < MAXLOOPCOUNT) {
1952
1953             foreach($appsToSort as $name => $depends) {
1954                 //Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " - $count $name - " . print_r($depends, true));
1955                 
1956                 // don't uninstall if another app depends on this one
1957                 $otherAppDepends = FALSE;
1958                 foreach($appsToSort as $innerName => $innerDepends) {
1959                     if(in_array($name, $innerDepends)) {
1960                         $otherAppDepends = TRUE;
1961                         break;
1962                     }
1963                 }
1964                 
1965                 // add it to results
1966                 if (!$otherAppDepends) {
1967                     $result[$name] = $_applications[$name];
1968                     unset($appsToSort[$name]);
1969                 }
1970             }
1971             $count++;
1972         }
1973         
1974         if ($count == MAXLOOPCOUNT) {
1975             Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ .
1976                 " Some Applications could not be uninstalled because of (cyclic?) dependencies: " . print_r(array_keys($appsToSort), TRUE));
1977         }
1978
1979         // Tinebase is uninstalled last
1980         if (isset($_applications['Tinebase'])) {
1981             $result['Tinebase'] = $_applications['Tinebase'];
1982             unset($_applications['Tinebase']);
1983         }
1984         
1985         return $result;
1986     }
1987     
1988     /**
1989      * check if an application is installed
1990      *
1991      * @param string $appname
1992      * @return boolean
1993      */
1994     public function isInstalled($appname)
1995     {
1996         try {
1997             $result = Tinebase_Application::getInstance()->isInstalled($appname);
1998         } catch (Exception $e) {
1999             Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Application ' . $appname . ' is not installed.');
2000             Setup_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $e);
2001             $result = FALSE;
2002         }
2003         
2004         return $result;
2005     }
2006     
2007     /**
2008      * clear cache
2009      *
2010      * @return void
2011      */
2012     protected function _clearCache()
2013     {
2014         // setup cache (via tinebase because it is disabled in setup by default)
2015         Tinebase_Core::setupCache(TRUE);
2016         
2017         Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Clearing cache ...');
2018         
2019         // clear cache
2020         $cache = Setup_Core::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL);
2021
2022         Tinebase_Application::getInstance()->resetClassCache();
2023         Tinebase_Cache_PerRequest::getInstance()->reset();
2024
2025         // deactivate cache again
2026         Tinebase_Core::setupCache(FALSE);
2027     }
2028
2029     /**
2030      * returns TRUE if filesystem is available
2031      * 
2032      * @return boolean
2033      */
2034     public function isFilesystemAvailable()
2035     {
2036         if ($this->_isFileSystemAvailable === null) {
2037             try {
2038                 $session = Tinebase_Session::getSessionNamespace();
2039
2040                 if (isset($session->filesystemAvailable)) {
2041                     $this->_isFileSystemAvailable = $session->filesystemAvailable;
2042
2043                     return $this->_isFileSystemAvailable;
2044                 }
2045             } catch (Zend_Session_Exception $zse) {
2046                 $session = null;
2047             }
2048
2049             $this->_isFileSystemAvailable = (!empty(Tinebase_Core::getConfig()->filesdir) && is_writeable(Tinebase_Core::getConfig()->filesdir));
2050
2051             if ($session instanceof Zend_Session_Namespace) {
2052                 if (Tinebase_Session::isWritable()) {
2053                     $session->filesystemAvailable = $this->_isFileSystemAvailable;
2054                 }
2055             }
2056
2057             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
2058                 . ' Filesystem available: ' . ($this->_isFileSystemAvailable ? 'yes' : 'no'));
2059         }
2060
2061         return $this->_isFileSystemAvailable;
2062     }
2063
2064     /**
2065      * backup
2066      *
2067      * @param $options array(
2068      *      'backupDir'  => string // where to store the backup
2069      *      'noTimestamp => bool   // don't append timestamp to backup dir
2070      *      'config'     => bool   // backup config
2071      *      'db'         => bool   // backup database
2072      *      'files'      => bool   // backup files
2073      *    )
2074      */
2075     public function backup($options)
2076     {
2077         $config = Setup_Core::getConfig();
2078
2079         $backupDir = isset($options['backupDir']) ? $options['backupDir'] : $config->backupDir;
2080         if (! $backupDir) {
2081             throw new Exception('backupDir not configured');
2082         }
2083
2084         if (! isset($options['noTimestamp'])) {
2085             $backupDir .= '/' . date_create('now', new DateTimeZone('UTC'))->format('Y-m-d-H-i-s');
2086         }
2087
2088         if (!is_dir($backupDir) && !mkdir($backupDir, 0700, true)) {
2089             throw new Exception("$backupDir could  not be created");
2090         }
2091
2092         if ($options['config']) {
2093             $configFile = stream_resolve_include_path('config.inc.php');
2094             $configDir = dirname($configFile);
2095
2096             $files = file_exists("$configDir/index.php") ? 'config.inc.php' : '.';
2097             `cd $configDir; tar cjf $backupDir/tine20_config.tar.bz2 $files`;
2098         }
2099
2100         if ($options['db']) {
2101             if (! $this->_backend) {
2102                 throw new Exception('db not configured, cannot backup');
2103             }
2104
2105             $backupOptions = array(
2106                 'backupDir'         => $backupDir,
2107                 'structTables'      => $this->_getBackupStructureOnlyTables(),
2108             );
2109
2110             $this->_backend->backup($backupOptions);
2111         }
2112
2113         $filesDir = isset($config->filesdir) ? $config->filesdir : false;
2114         if ($options['files'] && $filesDir) {
2115             `cd $filesDir; tar cjf $backupDir/tine20_files.tar.bz2 .`;
2116         }
2117     }
2118
2119     /**
2120      * returns an array of all tables of all applications that should only backup the structure
2121      *
2122      * @return array
2123      * @throws Setup_Exception_NotFound
2124      */
2125     protected function _getBackupStructureOnlyTables()
2126     {
2127         $tables = array();
2128
2129         // find tables that only backup structure
2130         $applications = Tinebase_Application::getInstance()->getApplications();
2131
2132         /**
2133          * @var $application Tinebase_Model_Application
2134          */
2135         foreach($applications as $application) {
2136             $tableDef = $this->getSetupXml($application->name);
2137             $structOnlys = $tableDef->xpath('//table/backupStructureOnly[text()="true"]');
2138
2139             foreach($structOnlys as $structOnly) {
2140                 $tableName = $structOnly->xpath('./../name/text()');
2141                 $tables[] = SQL_TABLE_PREFIX . $tableName[0];
2142             }
2143         }
2144
2145         return $tables;
2146     }
2147
2148     /**
2149      * restore
2150      *
2151      * @param $options array(
2152      *      'backupDir'  => string // location of backup to restore
2153      *      'config'     => bool   // restore config
2154      *      'db'         => bool   // restore database
2155      *      'files'      => bool   // restore files
2156      *    )
2157      *
2158      * @param $options
2159      * @throws Setup_Exception
2160      */
2161     public function restore($options)
2162     {
2163         if (! isset($options['backupDir'])) {
2164             throw new Setup_Exception("you need to specify the backupDir");
2165         }
2166
2167         if (isset($options['config']) && $options['config']) {
2168             $configBackupFile = $options['backupDir']. '/tine20_config.tar.bz2';
2169             if (! file_exists($configBackupFile)) {
2170                 throw new Setup_Exception("$configBackupFile not found");
2171             }
2172
2173             $configDir = isset($options['configDir']) ? $options['configDir'] : false;
2174             if (!$configDir) {
2175                 $configFile = stream_resolve_include_path('config.inc.php');
2176                 if (!$configFile) {
2177                     throw new Setup_Exception("can't detect configDir, please use configDir option");
2178                 }
2179                 $configDir = dirname($configFile);
2180             }
2181
2182             `cd $configDir; tar xf $configBackupFile`;
2183         }
2184
2185         Setup_Core::setupConfig();
2186         $config = Setup_Core::getConfig();
2187
2188         if (isset($options['db']) && $options['db']) {
2189             $this->_backend->restore($options['backupDir']);
2190         }
2191
2192         $filesDir = isset($config->filesdir) ? $config->filesdir : false;
2193         if (isset($options['files']) && $options['files']) {
2194             $dir = $options['backupDir'];
2195             $filesBackupFile = $dir . '/tine20_files.tar.bz2';
2196             if (! file_exists($filesBackupFile)) {
2197                 throw new Setup_Exception("$filesBackupFile not found");
2198             }
2199
2200             `cd $filesDir; tar xf $filesBackupFile`;
2201         }
2202     }
2203
2204     public function compareSchema($options)
2205     {
2206         if (! isset($options['otherdb'])) {
2207             throw new Exception("you need to specify the otherdb");
2208         }
2209
2210         return Setup_SchemaTool::compareSchema($options['otherdb']);
2211     }
2212 }