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)
11 * @todo move $this->_db calls to backend class
17 require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Tinebase' . DIRECTORY_SEPARATOR . 'Helper.php';
20 * class to handle setup of Tine 2.0
23 * @subpackage Controller
25 class Setup_Controller
28 * holds the instance of the singleton
30 * @var Setup_Controller
32 private static $_instance = NULL;
37 * @var Setup_Backend_Interface
39 protected $_backend = NULL;
42 * the directory where applications are located
49 * the email configs to get/set
53 protected $_emailConfigKeys = array();
56 * number of updated apps
60 protected $_updatedApplications = 0;
63 * don't clone. Use the singleton.
66 private function __clone() {}
69 * url to Tine 2.0 wiki
73 protected $_helperLink = ' <a href="http://wiki.tine20.org/Admins/Install_Howto" target="_blank">Check the Tine 2.0 wiki for support.</a>';
76 * the singleton pattern
78 * @return Setup_Controller
80 public static function getInstance()
82 if (self::$_instance === NULL) {
83 self::$_instance = new Setup_Controller;
86 return self::$_instance;
93 protected function __construct()
95 // setup actions could take quite a while we try to set max execution time to unlimited
96 Setup_Core::setExecutionLifeTime(0);
98 if (!defined('MAXLOOPCOUNT')) {
99 define('MAXLOOPCOUNT', 50);
102 $this->_baseDir = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR;
104 if (Setup_Core::get(Setup_Core::CHECKDB)) {
105 $this->_db = Setup_Core::getDb();
106 $this->_backend = Setup_Backend_Factory::factory();
111 $this->_emailConfigKeys = array(
112 'imap' => Tinebase_Config::IMAP,
113 'smtp' => Tinebase_Config::SMTP,
114 'sieve' => Tinebase_Config::SIEVE,
119 * check system/php requirements (env + ext check)
123 * @todo add message to results array
125 public function checkRequirements()
127 $envCheck = $this->environmentCheck();
129 $databaseCheck = $this->checkDatabase();
131 $extCheck = new Setup_ExtCheck(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'essentials.xml');
132 $extResult = $extCheck->getData();
135 'success' => ($envCheck['success'] && $databaseCheck['success'] && $extResult['success']),
136 'results' => array_merge($envCheck['result'], $databaseCheck['result'], $extResult['result']),
139 $result['totalcount'] = count($result['results']);
145 * check which database extensions are available
149 public function checkDatabase()
156 $loadedExtensions = get_loaded_extensions();
158 if (! in_array('PDO', $loadedExtensions)) {
159 $result['result'][] = array(
162 'message' => "PDO extension not found." . $this->_helperLink
168 // check mysql requirements
169 $missingMysqlExtensions = array_diff(array('pdo_mysql'), $loadedExtensions);
171 // check pgsql requirements
172 $missingPgsqlExtensions = array_diff(array('pgsql', 'pdo_pgsql'), $loadedExtensions);
174 // check oracle requirements
175 $missingOracleExtensions = array_diff(array('oci8'), $loadedExtensions);
177 if (! empty($missingMysqlExtensions) && ! empty($missingPgsqlExtensions) && ! empty($missingOracleExtensions)) {
178 $result['result'][] = array(
181 'message' => 'Database extensions missing. For MySQL install: ' . implode(', ', $missingMysqlExtensions) .
182 ' For Oracle install: ' . implode(', ', $missingOracleExtensions) .
183 ' For PostgreSQL install: ' . implode(', ', $missingPgsqlExtensions) .
190 $result['result'][] = array(
193 'message' => 'Support for following databases enabled: ' .
194 (empty($missingMysqlExtensions) ? 'MySQL' : '') . ' ' .
195 (empty($missingOracleExtensions) ? 'Oracle' : '') . ' ' .
196 (empty($missingPgsqlExtensions) ? 'PostgreSQL' : '') . ' '
198 $result['success'] = TRUE;
204 * Check if logger is properly configured (or not configured at all)
208 public function checkConfigLogger()
210 $config = Setup_Core::get(Setup_Core::CONFIG);
211 if (!isset($config->logger) || !$config->logger->active) {
215 isset($config->logger->filename)
217 file_exists($config->logger->filename) && is_writable($config->logger->filename)
218 || is_writable(dirname($config->logger->filename))
225 * Check if caching is properly configured (or not configured at all)
229 public function checkConfigCaching()
233 $config = Setup_Core::get(Setup_Core::CONFIG);
235 if (! isset($config->caching) || !$config->caching->active) {
238 } else if (! isset($config->caching->backend) || ucfirst($config->caching->backend) === 'File') {
239 $result = $this->checkDir('path', 'caching', FALSE);
241 } else if (ucfirst($config->caching->backend) === 'Redis') {
242 $result = $this->_checkRedisConnect(isset($config->caching->redis) ? $config->caching->redis->toArray() : array());
244 } else if (ucfirst($config->caching->backend) === 'Memcached') {
245 $result = $this->_checkMemcacheConnect(isset($config->caching->memcached) ? $config->caching->memcached->toArray() : array());
253 * checks redis extension and connection
255 * @param array $config
258 protected function _checkRedisConnect($config)
260 if (! extension_loaded('redis')) {
261 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' redis extension not loaded');
265 $host = isset($config['host']) ? $config['host'] : 'localhost';
266 $port = isset($config['port']) ? $config['port'] : 6379;
268 $result = $redis->connect($host, $port);
272 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not connect to redis server at ' . $host . ':' . $port);
279 * checks memcached extension and connection
281 * @param array $config
284 protected function _checkMemcacheConnect($config)
286 if (! extension_loaded('memcache')) {
287 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' memcache extension not loaded');
290 $memcache = new Memcache;
291 $host = isset($config['host']) ? $config['host'] : 'localhost';
292 $port = isset($config['port']) ? $config['port'] : 11211;
293 $result = $memcache->connect($host, $port);
299 * Check if queue is properly configured (or not configured at all)
303 public function checkConfigQueue()
305 $config = Setup_Core::get(Setup_Core::CONFIG);
306 if (! isset($config->actionqueue) || ! $config->actionqueue->active) {
309 $result = $this->_checkRedisConnect($config->actionqueue->toArray());
316 * check config session
320 public function checkConfigSession()
323 $config = Setup_Core::get(Setup_Core::CONFIG);
324 if (! isset($config->session) || !$config->session->active) {
326 } else if (ucfirst($config->session->backend) === 'File') {
327 return $this->checkDir('path', 'session', FALSE);
328 } else if (ucfirst($config->session->backend) === 'Redis') {
329 $result = $this->_checkRedisConnect($config->session->toArray());
336 * checks if path in config is writable
338 * @param string $_name
339 * @param string $_group
342 public function checkDir($_name, $_group = NULL, $allowEmptyPath = TRUE)
344 $config = $this->getConfigData();
345 if ($_group !== NULL && (isset($config[$_group]) || array_key_exists($_group, $config))) {
346 $config = $config[$_group];
349 $path = (isset($config[$_name]) || array_key_exists($_name, $config)) ? $config[$_name] : false;
351 return $allowEmptyPath;
353 return @is_writable($path);
358 * get list of applications as found in the filesystem
360 * @return array appName => setupXML
362 public function getInstallableApplications()
364 // create Tinebase tables first
365 $applications = array('Tinebase' => $this->getSetupXml('Tinebase'));
368 $dirIterator = new DirectoryIterator($this->_baseDir);
369 } catch (Exception $e) {
370 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not open base dir: ' . $this->_baseDir);
371 throw new Tinebase_Exception_AccessDenied('Could not open Tine 2.0 root directory.');
374 foreach ($dirIterator as $item) {
375 $appName = $item->getFileName();
376 if($appName{0} != '.' && $appName != 'Tinebase' && $item->isDir()) {
377 $fileName = $this->_baseDir . $item->getFileName() . '/Setup/setup.xml' ;
378 if(file_exists($fileName)) {
379 $applications[$item->getFileName()] = $this->getSetupXml($item->getFileName());
384 return $applications;
388 * updates installed applications. does nothing if no applications are installed
390 * @param Tinebase_Record_RecordSet $_applications
391 * @return array messages
393 public function updateApplications(Tinebase_Record_RecordSet $_applications)
395 $this->_updatedApplications = 0;
396 $smallestMajorVersion = NULL;
397 $biggestMajorVersion = NULL;
399 //find smallest major version
400 foreach ($_applications as $application) {
401 if ($smallestMajorVersion === NULL || $application->getMajorVersion() < $smallestMajorVersion) {
402 $smallestMajorVersion = $application->getMajorVersion();
404 if ($biggestMajorVersion === NULL || $application->getMajorVersion() > $biggestMajorVersion) {
405 $biggestMajorVersion = $application->getMajorVersion();
411 // update tinebase first (to biggest major version)
412 $tinebase = $_applications->filter('name', 'Tinebase')->getFirstRecord();
413 if (! empty($tinebase)) {
414 unset($_applications[$_applications->getIndexById($tinebase->getId())]);
416 list($major, $minor) = explode('.', $this->getSetupXml('Tinebase')->version[0]);
417 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updating Tinebase to version ' . $major . '.' . $minor);
419 for ($majorVersion = $tinebase->getMajorVersion(); $majorVersion <= $major; $majorVersion++) {
420 $messages += $this->updateApplication($tinebase, $majorVersion);
425 for ($majorVersion = $smallestMajorVersion; $majorVersion <= $biggestMajorVersion; $majorVersion++) {
426 foreach ($_applications as $application) {
427 if ($application->getMajorVersion() <= $majorVersion) {
428 $messages += $this->updateApplication($application, $majorVersion);
434 'messages' => $messages,
435 'updated' => $this->_updatedApplications,
440 * load the setup.xml file and returns a simplexml object
442 * @param string $_applicationName name of the application
443 * @return SimpleXMLElement
445 public function getSetupXml($_applicationName)
447 $setupXML = $this->_baseDir . ucfirst($_applicationName) . '/Setup/setup.xml';
449 if (!file_exists($setupXML)) {
450 throw new Setup_Exception_NotFound(ucfirst($_applicationName)
451 . '/Setup/setup.xml not found. If application got renamed or deleted, re-run setup.php.');
454 $xml = simplexml_load_file($setupXML);
462 * @param Tinebase_Model_Application $_application
463 * @throws Setup_Exception
465 public function checkUpdate(Tinebase_Model_Application $_application)
467 $xmlTables = $this->getSetupXml($_application->name);
468 if(isset($xmlTables->tables)) {
469 foreach ($xmlTables->tables[0] as $tableXML) {
470 $table = Setup_Backend_Schema_Table_Factory::factory('Xml', $tableXML);
471 if (true == $this->_backend->tableExists($table->name)) {
473 $this->_backend->checkTable($table);
474 } catch (Setup_Exception $e) {
475 Setup_Core::getLogger()->error(__METHOD__ . '::' . __LINE__ . " Checking table failed with message '{$e->getMessage()}'");
478 throw new Setup_Exception('Table ' . $table->name . ' for application' . $_application->name . " does not exist. \n<strong>Update broken</strong>");
485 * update installed application
487 * @param Tinebase_Model_Application $_application
488 * @param string $_majorVersion
489 * @return array messages
490 * @throws Setup_Exception if current app version is too high
492 public function updateApplication(Tinebase_Model_Application $_application, $_majorVersion)
494 $setupXml = $this->getSetupXml($_application->name);
497 switch (version_compare($_application->version, $setupXml->version)) {
499 $message = "Executing updates for " . $_application->name . " (starting at " . $_application->version . ")";
501 $messages[] = $message;
502 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' ' . $message);
504 $version = $_application->getMajorAndMinorVersion();
505 $minor = $version['minor'];
507 $className = ucfirst($_application->name) . '_Setup_Update_Release' . $_majorVersion;
508 if(! class_exists($className)) {
509 $nextMajorRelease = ($_majorVersion + 1) . ".0";
510 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
511 . " Update class {$className} does not exists, skipping release {$_majorVersion} for app "
512 . "{$_application->name} and increasing version to $nextMajorRelease"
514 $_application->version = $nextMajorRelease;
515 Tinebase_Application::getInstance()->updateApplication($_application);
518 $update = new $className($this->_backend);
520 $classMethods = get_class_methods($update);
522 // we must do at least one update
524 $functionName = 'update_' . $minor;
527 $db = Setup_Core::getDb();
528 $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($db);
530 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
531 . ' Updating ' . $_application->name . ' - ' . $functionName
534 $update->$functionName();
536 Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
538 } catch (Exception $e) {
539 Tinebase_TransactionManager::getInstance()->rollBack();
540 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getMessage());
541 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getTraceAsString());
546 } while(array_search('update_' . $minor, $classMethods) !== false);
549 $messages[] = "<strong> Updated " . $_application->name . " successfully to " . $_majorVersion . '.' . $minor . "</strong>";
551 // update app version
552 $updatedApp = Tinebase_Application::getInstance()->getApplicationById($_application->getId());
553 $_application->version = $updatedApp->version;
554 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updated ' . $_application->name . " successfully to " . $_application->version);
555 $this->_updatedApplications++;
560 Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' No update needed for ' . $_application->name);
564 throw new Setup_Exception('Current application version is higher than version from setup.xml: '
565 . $_application->version . ' > ' . $setupXml->version
570 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Clearing cache after update ...');
571 $this->_enableCaching();
572 Tinebase_Core::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL);
578 * checks if update is required
582 public function updateNeeded($_application)
585 $setupXml = $this->getSetupXml($_application->name);
586 } catch (Setup_Exception_NotFound $senf) {
587 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' ' . $senf->getMessage() . ' Disabling application "' . $_application->name . '".');
588 Tinebase_Application::getInstance()->setApplicationState(array($_application->getId()), Tinebase_Application::DISABLED);
592 $updateNeeded = version_compare($_application->version, $setupXml->version);
594 if($updateNeeded === -1) {
602 * search for installed and installable applications
606 public function searchApplications()
608 // get installable apps
609 $installable = $this->getInstallableApplications();
611 // get installed apps
612 if (Setup_Core::get(Setup_Core::CHECKDB)) {
614 $installed = Tinebase_Application::getInstance()->getApplications(NULL, 'id')->toArray();
616 // merge to create result array
617 $applications = array();
618 foreach ($installed as $application) {
620 if (! (isset($installable[$application['name']]) || array_key_exists($application['name'], $installable))) {
621 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' App ' . $application['name'] . ' does not exist any more.');
625 $depends = (array) $installable[$application['name']]->depends;
626 if (isset($depends['application'])) {
627 $depends = implode(', ', (array) $depends['application']);
630 $application['current_version'] = (string) $installable[$application['name']]->version;
631 $application['install_status'] = (version_compare($application['version'], $application['current_version']) === -1) ? 'updateable' : 'uptodate';
632 $application['depends'] = $depends;
633 $applications[] = $application;
634 unset($installable[$application['name']]);
636 } catch (Zend_Db_Statement_Exception $zse) {
641 foreach ($installable as $name => $setupXML) {
642 $depends = (array) $setupXML->depends;
643 if (isset($depends['application'])) {
644 $depends = implode(', ', (array) $depends['application']);
647 $applications[] = array(
649 'current_version' => (string) $setupXML->version,
650 'install_status' => 'uninstalled',
651 'depends' => $depends,
656 'results' => $applications,
657 'totalcount' => count($applications)
662 * checks if setup is required
666 public function setupRequired()
670 // check if applications table exists / only if db available
671 if (Setup_Core::isRegistered(Setup_Core::DB)) {
673 $applicationTable = Setup_Core::getDb()->describeTable(SQL_TABLE_PREFIX . 'applications');
674 if (empty($applicationTable)) {
675 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Applications table empty');
678 } catch (Zend_Db_Statement_Exception $zdse) {
679 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $zdse->getMessage());
681 } catch (Zend_Db_Adapter_Exception $zdae) {
682 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $zdae->getMessage());
691 * do php.ini environment check
695 public function environmentCheck()
703 // check php environment
704 $requiredIniSettings = array(
705 'magic_quotes_sybase' => 0,
706 'magic_quotes_gpc' => 0,
707 'magic_quotes_runtime' => 0,
708 'mbstring.func_overload' => 0,
709 'eaccelerator.enable' => 0,
710 'memory_limit' => '48M'
713 foreach ($requiredIniSettings as $variable => $newValue) {
714 $oldValue = ini_get($variable);
716 if ($variable == 'memory_limit') {
717 $required = Tinebase_Helper::convertToBytes($newValue);
718 $set = Tinebase_Helper::convertToBytes($oldValue);
720 if ( $set < $required) {
724 'message' => "You need to set $variable equal or greater than $required (now: $set)." . $this->_helperLink
729 } elseif ($oldValue != $newValue) {
730 if (ini_set($variable, $newValue) === false) {
734 'message' => "You need to set $variable from $oldValue to $newValue." . $this->_helperLink
749 'success' => $success,
754 * get config file default values
758 public function getConfigDefaults()
760 $defaultPath = Setup_Core::guessTempDir();
764 'host' => 'localhost',
765 'dbname' => 'tine20',
766 'username' => 'tine20',
768 'adapter' => 'pdo_mysql',
769 'tableprefix' => 'tine20_',
773 'filename' => $defaultPath . DIRECTORY_SEPARATOR . 'tine20.log',
780 'path' => $defaultPath,
782 'tmpdir' => $defaultPath,
784 'path' => Tinebase_Session::getSessionDir(),
793 * get config file values
797 public function getConfigData()
799 $configArray = Setup_Core::get(Setup_Core::CONFIG)->toArray();
801 #####################################
802 # LEGACY/COMPATIBILITY:
803 # (1) had to rename session.save_path key to sessiondir because otherwise the
804 # generic save config method would interpret the "_" as array key/value seperator
805 # (2) moved session config to subgroup 'session'
806 if (empty($configArray['session']) || empty($configArray['session']['path'])) {
807 foreach (array('session.save_path', 'sessiondir') as $deprecatedSessionDir) {
808 $sessionDir = (isset($configArray[$deprecatedSessionDir]) || array_key_exists($deprecatedSessionDir, $configArray)) ? $configArray[$deprecatedSessionDir] : '';
809 if (! empty($sessionDir)) {
810 if (empty($configArray['session'])) {
811 $configArray['session'] = array();
813 $configArray['session']['path'] = $sessionDir;
814 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " config.inc.php key '{$deprecatedSessionDir}' should be renamed to 'path' and moved to 'session' group.");
818 #####################################
824 * save data to config file
826 * @param array $_data
827 * @param boolean $_merge
829 public function saveConfigData($_data, $_merge = TRUE)
831 if (!empty($_data['setupuser']['password']) && !Setup_Auth::isMd5($_data['setupuser']['password'])) {
832 $password = $_data['setupuser']['password'];
833 $_data['setupuser']['password'] = md5($_data['setupuser']['password']);
835 if (Setup_Core::configFileExists() && !Setup_Core::configFileWritable()) {
836 throw new Setup_Exception('Config File is not writeable.');
839 if (Setup_Core::configFileExists()) {
841 $filename = Setup_Core::getConfigFilePath();
844 $filename = dirname(__FILE__) . '/../config.inc.php';
847 $config = $this->writeConfigToFile($_data, $_merge, $filename);
849 Setup_Core::set(Setup_Core::CONFIG, $config);
851 Setup_Core::setupLogger();
853 if ($doLogin && isset($password)) {
854 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Create session for setup user ' . $_data['setupuser']['username']);
855 $this->login($_data['setupuser']['username'], $password);
860 * write config to a file
862 * @param array $_data
863 * @param boolean $_merge
864 * @param string $_filename
865 * @return Zend_Config
867 public function writeConfigToFile($_data, $_merge, $_filename)
869 // merge config data and active config
871 $activeConfig = Setup_Core::get(Setup_Core::CONFIG);
872 $config = new Zend_Config($activeConfig->toArray(), true);
873 $config->merge(new Zend_Config($_data));
875 $config = new Zend_Config($_data);
879 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updating config.inc.php');
880 $writer = new Zend_Config_Writer_Array(array(
882 'filename' => $_filename,
890 * load authentication data
894 public function loadAuthenticationData()
897 'authentication' => $this->_getAuthProviderData(),
898 'accounts' => $this->_getAccountsStorageData(),
899 'redirectSettings' => $this->_getRedirectSettings(),
900 'password' => $this->_getPasswordSettings(),
901 'saveusername' => $this->_getReuseUsernameSettings()
906 * Update authentication data
908 * Needs Tinebase tables to store the data, therefore
909 * installs Tinebase if it is not already installed
911 * @param array $_authenticationData
915 public function saveAuthentication($_authenticationData)
917 if ($this->isInstalled('Tinebase')) {
918 // NOTE: Tinebase_Setup_Initialiser calls this function again so
919 // we come to this point on initial installation _and_ update
920 $this->_updateAuthentication($_authenticationData);
922 $installationOptions = array('authenticationData' => $_authenticationData);
923 $this->installApplications(array('Tinebase'), $installationOptions);
928 * Save {@param $_authenticationData} to config file
930 * @param array $_authenticationData [hash containing settings for authentication and accountsStorage]
933 protected function _updateAuthentication($_authenticationData)
935 // this is a dangerous TRACE as there might be passwords in here!
936 //if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_authenticationData, TRUE));
938 $this->_enableCaching();
940 if (isset($_authenticationData['authentication'])) {
941 $this->_updateAuthenticationProvider($_authenticationData['authentication']);
944 if (isset($_authenticationData['accounts'])) {
945 $this->_updateAccountsStorage($_authenticationData['accounts']);
948 if (isset($_authenticationData['redirectSettings'])) {
949 $this->_updateRedirectSettings($_authenticationData['redirectSettings']);
952 if (isset($_authenticationData['password'])) {
953 $this->_updatePasswordSettings($_authenticationData['password']);
956 if (isset($_authenticationData['saveusername'])) {
957 $this->_updateReuseUsername($_authenticationData['saveusername']);
960 if (isset($_authenticationData['acceptedTermsVersion'])) {
961 $this->saveAcceptedTerms($_authenticationData['acceptedTermsVersion']);
966 * enable caching to make sure cache gets cleaned if config options change
968 protected function _enableCaching()
970 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Activate caching backend if available ...');
972 Tinebase_Core::setupCache();
976 * Update authentication provider
978 * @param array $_data
981 protected function _updateAuthenticationProvider($_data)
983 Tinebase_Auth::setBackendType($_data['backend']);
984 $config = (isset($_data[$_data['backend']])) ? $_data[$_data['backend']] : $_data;
986 $excludeKeys = array('adminLoginName', 'adminPassword', 'adminPasswordConfirmation');
987 foreach ($excludeKeys as $key) {
988 if ((isset($config[$key]) || array_key_exists($key, $config))) {
989 unset($config[$key]);
993 Tinebase_Auth::setBackendConfiguration($config, null, true);
994 Tinebase_Auth::saveBackendConfiguration();
998 * Update accountsStorage
1000 * @param array $_data
1003 protected function _updateAccountsStorage($_data)
1005 $originalBackend = Tinebase_User::getConfiguredBackend();
1006 $newBackend = $_data['backend'];
1008 Tinebase_User::setBackendType($_data['backend']);
1009 $config = (isset($_data[$_data['backend']])) ? $_data[$_data['backend']] : $_data;
1010 Tinebase_User::setBackendConfiguration($config, null, true);
1011 Tinebase_User::saveBackendConfiguration();
1013 if ($originalBackend != $newBackend && $this->isInstalled('Addressbook') && $originalBackend == Tinebase_User::SQL) {
1014 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Switching from $originalBackend to $newBackend account storage");
1016 $db = Setup_Core::getDb();
1017 $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($db);
1018 $this->_migrateFromSqlAccountsStorage();
1019 Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
1021 } catch (Exception $e) {
1022 Tinebase_TransactionManager::getInstance()->rollBack();
1023 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getMessage());
1024 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getTraceAsString());
1026 Tinebase_User::setBackendType($originalBackend);
1027 Tinebase_User::saveBackendConfiguration();
1035 * migrate from SQL account storage to another one (for example LDAP)
1036 * - deletes all users, groups and roles because they will be
1037 * imported from new accounts storage backend
1039 protected function _migrateFromSqlAccountsStorage()
1041 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Deleting all user accounts, groups, roles and rights');
1042 Tinebase_User::factory(Tinebase_User::SQL)->deleteAllUsers();
1044 $contactSQLBackend = new Addressbook_Backend_Sql();
1045 $allUserContactIds = $contactSQLBackend->search(new Addressbook_Model_ContactFilter(array('type' => 'user')), null, true);
1046 if (count($allUserContactIds) > 0) {
1047 $contactSQLBackend->delete($allUserContactIds);
1051 Tinebase_Group::factory(Tinebase_Group::SQL)->deleteAllGroups();
1052 $listsSQLBackend = new Addressbook_Backend_List();
1053 $allGroupListIds = $listsSQLBackend->search(new Addressbook_Model_ListFilter(array('type' => 'group')), null, true);
1054 if (count($allGroupListIds) > 0) {
1055 $listsSQLBackend->delete($allGroupListIds);
1058 $roles = Tinebase_Acl_Roles::getInstance();
1059 $roles->deleteAllRoles();
1061 // import users (from new backend) / create initial users (SQL)
1062 Tinebase_User::syncUsers(array('syncContactData' => TRUE));
1064 $roles->createInitialRoles();
1065 $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
1066 foreach ($applications as $application) {
1067 Setup_Initialize::initializeApplicationRights($application);
1072 * Update redirect settings
1074 * @param array $_data
1077 protected function _updateRedirectSettings($_data)
1079 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_data, 1));
1080 $keys = array(Tinebase_Config::REDIRECTURL, Tinebase_Config::REDIRECTALWAYS, Tinebase_Config::REDIRECTTOREFERRER);
1081 foreach ($keys as $key) {
1082 if ((isset($_data[$key]) || array_key_exists($key, $_data))) {
1083 if (strlen($_data[$key]) === 0) {
1084 Tinebase_Config::getInstance()->delete($key);
1086 Tinebase_Config::getInstance()->set($key, $_data[$key]);
1093 * update pw settings
1095 * @param array $data
1097 protected function _updatePasswordSettings($data)
1099 foreach ($data as $config => $value) {
1100 Tinebase_Config::getInstance()->set($config, $value);
1105 * update pw settings
1107 * @param array $data
1109 protected function _updateReuseUsername($data)
1111 foreach ($data as $config => $value) {
1112 Tinebase_Config::getInstance()->set($config, $value);
1118 * get auth provider data
1122 * @todo get this from config table instead of file!
1124 protected function _getAuthProviderData()
1126 $result = Tinebase_Auth::getBackendConfigurationWithDefaults(Setup_Core::get(Setup_Core::CHECKDB));
1127 $result['backend'] = (Setup_Core::get(Setup_Core::CHECKDB)) ? Tinebase_Auth::getConfiguredBackend() : Tinebase_Auth::SQL;
1133 * get Accounts storage data
1137 protected function _getAccountsStorageData()
1139 $result = Tinebase_User::getBackendConfigurationWithDefaults(Setup_Core::get(Setup_Core::CHECKDB));
1140 $result['backend'] = (Setup_Core::get(Setup_Core::CHECKDB)) ? Tinebase_User::getConfiguredBackend() : Tinebase_User::SQL;
1146 * Get redirect Settings from config table.
1147 * If Tinebase is not installed, default values will be returned.
1151 protected function _getRedirectSettings()
1154 Tinebase_Config::REDIRECTURL => '',
1155 Tinebase_Config::REDIRECTTOREFERRER => '0'
1157 if (Setup_Core::get(Setup_Core::CHECKDB) && $this->isInstalled('Tinebase')) {
1158 $return[Tinebase_Config::REDIRECTURL] = Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTURL, '');
1159 $return[Tinebase_Config::REDIRECTTOREFERRER] = Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTTOREFERRER, '');
1165 * get password settings
1169 * @todo should use generic mechanism to fetch setup related configs
1171 protected function _getPasswordSettings()
1174 Tinebase_Config::PASSWORD_CHANGE => 1,
1175 Tinebase_Config::PASSWORD_POLICY_ACTIVE => 0,
1176 Tinebase_Config::PASSWORD_POLICY_ONLYASCII => 0,
1177 Tinebase_Config::PASSWORD_POLICY_MIN_LENGTH => 0,
1178 Tinebase_Config::PASSWORD_POLICY_MIN_WORD_CHARS => 0,
1179 Tinebase_Config::PASSWORD_POLICY_MIN_UPPERCASE_CHARS => 0,
1180 Tinebase_Config::PASSWORD_POLICY_MIN_SPECIAL_CHARS => 0,
1181 Tinebase_Config::PASSWORD_POLICY_MIN_NUMBERS => 0,
1185 $tinebaseInstalled = $this->isInstalled('Tinebase');
1186 foreach ($configs as $config => $default) {
1187 $result[$config] = ($tinebaseInstalled) ? Tinebase_Config::getInstance()->get($config, $default) : $default;
1194 * get Reuse Username to login textbox
1198 * @todo should use generic mechanism to fetch setup related configs
1200 protected function _getReuseUsernameSettings()
1203 Tinebase_Config::REUSEUSERNAME_SAVEUSERNAME => 0,
1207 $tinebaseInstalled = $this->isInstalled('Tinebase');
1208 foreach ($configs as $config => $default) {
1209 $result[$config] = ($tinebaseInstalled) ? Tinebase_Config::getInstance()->get($config, $default) : $default;
1220 public function getEmailConfig()
1224 foreach ($this->_emailConfigKeys as $configName => $configKey) {
1225 $config = Tinebase_Config::getInstance()->get($configKey, new Tinebase_Config_Struct(array()))->toArray();
1226 if (! empty($config) && ! isset($config['active'])) {
1227 $config['active'] = TRUE;
1229 $result[$configName] = $config;
1238 * @param array $_data
1241 public function saveEmailConfig($_data)
1243 // this is a dangerous TRACE as there might be passwords in here!
1244 //if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_data, TRUE));
1246 $this->_enableCaching();
1248 foreach ($this->_emailConfigKeys as $configName => $configKey) {
1249 if ((isset($_data[$configName]) || array_key_exists($configName, $_data))) {
1250 // fetch current config first and preserve all values that aren't in $_data array
1251 $currentConfig = Tinebase_Config::getInstance()->get($configKey, new Tinebase_Config_Struct(array()))->toArray();
1252 $newConfig = array_merge($_data[$configName], array_diff_key($currentConfig, $_data[$configName]));
1253 Tinebase_Config::getInstance()->set($configKey, $newConfig);
1259 * returns all email config keys
1263 public function getEmailConfigKeys()
1265 return $this->_emailConfigKeys;
1269 * get accepted terms config
1273 public function getAcceptedTerms()
1275 return Tinebase_Config::getInstance()->get(Tinebase_Config::ACCEPTEDTERMSVERSION, 0);
1279 * save acceptedTermsVersion
1284 public function saveAcceptedTerms($_data)
1286 Tinebase_Config::getInstance()->set(Tinebase_Config::ACCEPTEDTERMSVERSION, $_data);
1290 * save config option in db
1292 * @param string $key
1293 * @param string|array $value
1294 * @param string $applicationName
1297 public function setConfigOption($key, $value, $applicationName = 'Tinebase')
1299 $config = Tinebase_Config_Abstract::factory($applicationName);
1302 $config->set($key, $value);
1307 * create new setup user session
1309 * @param string $_username
1310 * @param string $_password
1313 public function login($_username, $_password)
1315 $setupAuth = new Setup_Auth($_username, $_password);
1316 $authResult = Zend_Auth::getInstance()->authenticate($setupAuth);
1318 if ($authResult->isValid()) {
1319 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Valid credentials, setting username in session and registry.');
1320 Tinebase_Session::regenerateId();
1322 Setup_Core::set(Setup_Core::USER, $_username);
1323 Setup_Session::getSessionNamespace()->setupuser = $_username;
1327 Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Invalid credentials! ' . print_r($authResult->getMessages(), TRUE));
1328 Tinebase_Session::expireSessionCookie();
1339 public function logout()
1341 $_SESSION = array();
1343 Tinebase_Session::destroyAndRemoveCookie();
1347 * install list of applications
1349 * @param array $_applications list of application names
1350 * @param array | optional $_options
1353 public function installApplications($_applications, $_options = null)
1355 $this->_clearCache();
1357 // check requirements for initial install / add required apps to list
1358 if (! $this->isInstalled('Tinebase')) {
1360 $minimumRequirements = array('Addressbook', 'Tinebase', 'Admin');
1362 foreach ($minimumRequirements as $requiredApp) {
1363 if (!in_array($requiredApp, $_applications) && !$this->isInstalled($requiredApp)) {
1364 // Addressbook has to be installed with Tinebase for initial data (user contact)
1365 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
1366 . ' ' . $requiredApp . ' has to be installed first (adding it to list).'
1368 $_applications[] = $requiredApp;
1373 // get xml and sort apps first
1374 $applications = array();
1375 foreach ($_applications as $applicationName) {
1376 if ($this->isInstalled($applicationName)) {
1377 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1378 . " skipping installation of application {$applicationName} because it is already installed");
1380 $applications[$applicationName] = $this->getSetupXml($applicationName);
1383 $applications = $this->_sortInstallableApplications($applications);
1385 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Installing applications: ' . print_r(array_keys($applications), true));
1387 foreach ($applications as $name => $xml) {
1389 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Could not install application ' . $name);
1391 $this->_installApplication($xml, $_options);
1397 * delete list of applications
1399 * @param array $_applications list of application names
1401 public function uninstallApplications($_applications)
1403 $this->_clearCache();
1405 $installedApps = Tinebase_Application::getInstance()->getApplications();
1407 // uninstall all apps if tinebase ist going to be uninstalled
1408 if (count($installedApps) !== count($_applications) && in_array('Tinebase', $_applications)) {
1409 $_applications = $installedApps->name;
1412 // deactivate foreign key check if all installed apps should be uninstalled
1413 if (count($installedApps) == count($_applications) && get_class($this->_backend) == 'Setup_Backend_Mysql') {
1414 $this->_backend->setForeignKeyChecks(0);
1415 foreach ($installedApps as $app) {
1416 if ($app->name != 'Tinebase') {
1417 $this->_uninstallApplication($app, true);
1422 // tinebase should be uninstalled last
1423 $this->_uninstallApplication($tinebase);
1424 $this->_backend->setForeignKeyChecks(1);
1426 // get xml and sort apps first
1427 $applications = array();
1428 foreach($_applications as $applicationName) {
1429 $applications[$applicationName] = $this->getSetupXml($applicationName);
1431 $applications = $this->_sortUninstallableApplications($applications);
1433 foreach ($applications as $name => $xml) {
1434 $app = Tinebase_Application::getInstance()->getApplicationByName($name);
1435 $this->_uninstallApplication($app);
1441 * install given application
1443 * @param SimpleXMLElement $_xml
1444 * @param array | optional $_options
1446 * @throws Tinebase_Exception_Backend_Database
1448 protected function _installApplication(SimpleXMLElement $_xml, $_options = null)
1450 if ($this->_backend === NULL) {
1451 throw new Tinebase_Exception_Backend_Database('Need configured and working database backend for install.');
1455 if (Setup_Core::isLogLevel(Zend_Log::INFO)) Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Installing application: ' . $_xml->name);
1457 $createdTables = array();
1459 // traditional xml declaration
1460 if (isset($_xml->tables)) {
1461 foreach ($_xml->tables[0] as $tableXML) {
1462 $table = Setup_Backend_Schema_Table_Factory::factory('Xml', $tableXML);
1463 $this->_createTable($table);
1464 $createdTables[] = $table;
1468 // do we have modelconfig + doctrine
1470 $application = Setup_Core::getApplicationInstance($_xml->name, '', true);
1471 $models = $application->getModels(true /* MCv2only */);
1473 if (count($models) > 0) {
1474 // create tables using doctrine 2
1475 Setup_SchemaTool::createSchema($_xml->name, $models);
1477 // adopt to old workflow
1478 foreach ($models as $model) {
1479 $modelConfiguration = $model::getConfiguration();
1480 $createdTables[] = (object)array(
1481 'name' => Tinebase_Helper::array_value('name', $modelConfiguration->getTable()),
1482 'version' => $modelConfiguration->getVersion(),
1488 $application = new Tinebase_Model_Application(array(
1489 'name' => (string)$_xml->name,
1490 'status' => $_xml->status ? (string)$_xml->status : Tinebase_Application::ENABLED,
1491 'order' => $_xml->order ? (string)$_xml->order : 99,
1492 'version' => (string)$_xml->version
1495 $application = Tinebase_Application::getInstance()->addApplication($application);
1497 // keep track of tables belonging to this application
1498 foreach ($createdTables as $table) {
1499 Tinebase_Application::getInstance()->addApplicationTable($application, (string) $table->name, (int) $table->version);
1502 // insert default records
1503 if (isset($_xml->defaultRecords)) {
1504 foreach ($_xml->defaultRecords[0] as $record) {
1505 $this->_backend->execInsertStatement($record);
1509 // look for import definitions and put them into the db
1510 $this->createImportExportDefinitions($application);
1512 Setup_Initialize::initialize($application, $_options);
1513 } catch (Exception $e) {
1514 Tinebase_Exception::log($e, /* suppress trace */ false);
1519 protected function _createTable($table)
1521 if (Setup_Core::isLogLevel(Zend_Log::DEBUG)) Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Creating table: ' . $table->name);
1524 $this->_backend->createTable($table);
1525 } catch (Zend_Db_Statement_Exception $zdse) {
1526 throw new Tinebase_Exception_Backend_Database('Could not create table: ' . $zdse->getMessage());
1527 } catch (Zend_Db_Adapter_Exception $zdae) {
1528 throw new Tinebase_Exception_Backend_Database('Could not create table: ' . $zdae->getMessage());
1532 * look for import definitions and put them into the db
1534 * @param Tinebase_Model_Application $_application
1536 public function createImportExportDefinitions($_application)
1538 foreach (array('Import', 'Export') as $type) {
1540 $this->_baseDir . $_application->name .
1541 DIRECTORY_SEPARATOR . $type . DIRECTORY_SEPARATOR . 'definitions';
1543 if (file_exists($path)) {
1544 foreach (new DirectoryIterator($path) as $item) {
1545 $filename = $path . DIRECTORY_SEPARATOR . $item->getFileName();
1546 if (preg_match("/\.xml/", $filename)) {
1548 Tinebase_ImportExportDefinition::getInstance()->updateOrCreateFromFilename($filename, $_application);
1549 } catch (Exception $e) {
1550 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
1551 . ' Not installing import/export definion from file: ' . $filename
1552 . ' / Error message: ' . $e->getMessage());
1563 * @param Tinebase_Model_Application $_application
1564 * @throws Setup_Exception
1566 protected function _uninstallApplication(Tinebase_Model_Application $_application, $uninstallAll = false)
1568 if ($this->_backend === null) {
1569 throw new Setup_Exception('No setup backend available');
1572 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Uninstall ' . $_application);
1574 $applicationTables = Tinebase_Application::getInstance()->getApplicationTables($_application);
1575 } catch (Zend_Db_Statement_Exception $zdse) {
1576 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . " " . $zdse);
1577 throw new Setup_Exception('Could not uninstall ' . $_application . ' (you might need to remove the tables by yourself): ' . $zdse->getMessage());
1579 $disabledFK = FALSE;
1580 $db = Tinebase_Core::getDb();
1583 $oldCount = count($applicationTables);
1585 if ($_application->name == 'Tinebase') {
1586 $installedApplications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
1587 if (count($installedApplications) !== 1) {
1588 throw new Setup_Exception_Dependency('Failed to uninstall application "Tinebase" because of dependencies to other installed applications.');
1592 foreach ($applicationTables as $key => $table) {
1593 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Remove table: $table");
1596 // drop foreign keys which point to current table first
1597 $foreignKeys = $this->_backend->getExistingForeignKeys($table);
1598 foreach ($foreignKeys as $foreignKey) {
1599 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .
1600 " Drop index: " . $foreignKey['table_name'] . ' => ' . $foreignKey['constraint_name']);
1601 $this->_backend->dropForeignKey($foreignKey['table_name'], $foreignKey['constraint_name']);
1605 $this->_backend->dropTable($table);
1607 if ($_application->name != 'Tinebase') {
1608 Tinebase_Application::getInstance()->removeApplicationTable($_application, $table);
1611 unset($applicationTables[$key]);
1613 } catch (Zend_Db_Statement_Exception $e) {
1614 // we need to catch exceptions here, as we don't want to break here, as a table
1615 // might still have some foreign keys
1616 // this works with mysql only
1617 $message = $e->getMessage();
1618 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " Could not drop table $table - " . $message);
1620 // remove app table if table not found in db
1621 if (preg_match('/SQLSTATE\[42S02\]: Base table or view not found/', $message) && $_application->name != 'Tinebase') {
1622 Tinebase_Application::getInstance()->removeApplicationTable($_application, $table);
1623 unset($applicationTables[$key]);
1625 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . " Disabling foreign key checks ... ");
1626 if ($db instanceof Zend_Db_Adapter_Pdo_Mysql) {
1627 $db->query("SET FOREIGN_KEY_CHECKS=0");
1634 if ($oldCount > 0 && count($applicationTables) == $oldCount) {
1635 throw new Setup_Exception('dead lock detected oldCount: ' . $oldCount);
1637 } while (count($applicationTables) > 0);
1640 if ($db instanceof Zend_Db_Adapter_Pdo_Mysql) {
1641 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . " Enabling foreign key checks again... ");
1642 $db->query("SET FOREIGN_KEY_CHECKS=1");
1646 if ($_application->name != 'Tinebase') {
1647 if (!$uninstallAll) {
1648 Tinebase_Relations::getInstance()->removeApplication($_application->name);
1650 Tinebase_Timemachine_ModificationLog::getInstance()->removeApplication($_application);
1652 // delete containers, config options and other data for app
1653 Tinebase_Application::getInstance()->removeApplicationData($_application);
1656 // remove application from table of installed applications
1657 Tinebase_Application::getInstance()->deleteApplication($_application);
1660 Setup_Uninitialize::uninitialize($_application);
1662 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Removed app: " . $_application->name);
1666 * sort applications by checking dependencies
1668 * @param array $_applications
1671 protected function _sortInstallableApplications($_applications)
1675 // begin with Tinebase, Admin and Addressbook
1676 $alwaysOnTop = array('Tinebase', 'Admin', 'Addressbook');
1677 foreach ($alwaysOnTop as $app) {
1678 if (isset($_applications[$app])) {
1679 $result[$app] = $_applications[$app];
1680 unset($_applications[$app]);
1684 // get all apps to install ($name => $dependencies)
1685 $appsToSort = array();
1686 foreach($_applications as $name => $xml) {
1687 $depends = (array) $xml->depends;
1688 if (isset($depends['application'])) {
1689 if ($depends['application'] == 'Tinebase') {
1690 $appsToSort[$name] = array();
1693 $depends['application'] = (array) $depends['application'];
1695 foreach ($depends['application'] as $app) {
1696 // don't add tinebase (all apps depend on tinebase)
1697 if ($app != 'Tinebase') {
1698 $appsToSort[$name][] = $app;
1703 $appsToSort[$name] = array();
1707 //Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($appsToSort, true));
1711 while (count($appsToSort) > 0 && $count < MAXLOOPCOUNT) {
1713 foreach($appsToSort as $name => $depends) {
1715 if (empty($depends)) {
1716 // no dependencies left -> copy app to result set
1717 $result[$name] = $_applications[$name];
1718 unset($appsToSort[$name]);
1720 foreach ($depends as $key => $dependingAppName) {
1721 if (in_array($dependingAppName, array_keys($result)) || $this->isInstalled($dependingAppName)) {
1722 // remove from depending apps because it is already in result set
1723 unset($appsToSort[$name][$key]);
1731 if ($count == MAXLOOPCOUNT) {
1732 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ .
1733 " Some Applications could not be installed because of (cyclic?) dependencies: " . print_r(array_keys($appsToSort), TRUE));
1740 * sort applications by checking dependencies
1742 * @param array $_applications
1745 protected function _sortUninstallableApplications($_applications)
1749 // get all apps to uninstall ($name => $dependencies)
1750 $appsToSort = array();
1751 foreach($_applications as $name => $xml) {
1752 if ($name !== 'Tinebase') {
1753 $depends = (array) $xml->depends;
1754 if (isset($depends['application'])) {
1755 if ($depends['application'] == 'Tinebase') {
1756 $appsToSort[$name] = array();
1759 $depends['application'] = (array) $depends['application'];
1761 foreach ($depends['application'] as $app) {
1762 // don't add tinebase (all apps depend on tinebase)
1763 if ($app != 'Tinebase') {
1764 $appsToSort[$name][] = $app;
1769 $appsToSort[$name] = array();
1776 while (count($appsToSort) > 0 && $count < MAXLOOPCOUNT) {
1778 foreach($appsToSort as $name => $depends) {
1779 //Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " - $count $name - " . print_r($depends, true));
1781 // don't uninstall if another app depends on this one
1782 $otherAppDepends = FALSE;
1783 foreach($appsToSort as $innerName => $innerDepends) {
1784 if(in_array($name, $innerDepends)) {
1785 $otherAppDepends = TRUE;
1790 // add it to results
1791 if (!$otherAppDepends) {
1792 $result[$name] = $_applications[$name];
1793 unset($appsToSort[$name]);
1799 if ($count == MAXLOOPCOUNT) {
1800 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ .
1801 " Some Applications could not be uninstalled because of (cyclic?) dependencies: " . print_r(array_keys($appsToSort), TRUE));
1804 // Tinebase is uninstalled last
1805 if (isset($_applications['Tinebase'])) {
1806 $result['Tinebase'] = $_applications['Tinebase'];
1807 unset($_applications['Tinebase']);
1814 * check if an application is installed
1816 * @param string $appname
1819 public function isInstalled($appname)
1822 $result = Tinebase_Application::getInstance()->isInstalled($appname);
1823 } catch (Exception $e) {
1824 Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Application ' . $appname . ' is not installed.');
1825 Setup_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $e);
1837 protected function _clearCache()
1839 // setup cache (via tinebase because it is disabled in setup by default)
1840 Tinebase_Core::setupCache(TRUE);
1842 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Clearing cache ...');
1845 $cache = Setup_Core::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL);
1847 // deactivate cache again
1848 Tinebase_Core::setupCache(FALSE);
1852 * returns TRUE if filesystem is available
1856 public function isFilesystemAvailable()
1858 if ($this->_isFileSystemAvailable === null) {
1860 $session = Tinebase_Session::getSessionNamespace();
1862 if (isset($session->filesystemAvailable)) {
1863 $this->_isFileSystemAvailable = $session->filesystemAvailable;
1865 return $this->_isFileSystemAvailable;
1867 } catch (Zend_Session_Exception $zse) {
1871 $this->_isFileSystemAvailable = (!empty(Tinebase_Core::getConfig()->filesdir) && is_writeable(Tinebase_Core::getConfig()->filesdir));
1873 if ($session instanceof Zend_Session_Namespace) {
1874 if (Tinebase_Session::isWritable()) {
1875 $session->filesystemAvailable = $this->_isFileSystemAvailable;
1879 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
1880 . ' Filesystem available: ' . ($this->_isFileSystemAvailable ? 'yes' : 'no'));
1883 return $this->_isFileSystemAvailable;
1889 * @param $options array(
1890 * 'backupDir' => string // where to store the backup
1891 * 'noTimestamp => bool // don't append timestamp to backup dir
1892 * 'config' => bool // backup config
1893 * 'db' => bool // backup database
1894 * 'files' => bool // backup files
1897 public function backup($options)
1899 $config = Setup_Core::getConfig();
1901 $backupDir = isset($options['backupDir']) ? $options['backupDir'] : $config->backupDir;
1903 throw new Exception('backupDir not configured');
1906 if (! isset($options['noTimestamp'])) {
1907 $backupDir .= '/' . date_create('now', new DateTimeZone('UTC'))->format('Y-m-d-H-i-s');
1910 if (!is_dir($backupDir) && !mkdir($backupDir, 0700, true)) {
1911 throw new Exception("$backupDir could not be created");
1914 if ($options['config']) {
1915 $configFile = stream_resolve_include_path('config.inc.php');
1916 $configDir = dirname($configFile);
1918 $files = file_exists("$configDir/index.php") ? 'config.inc.php' : '.';
1919 `cd $configDir; tar cjf $backupDir/tine20_config.tar.bz2 $files`;
1922 if ($options['db']) {
1923 if (! $this->_backend) {
1924 throw new Exception('db not configured, cannot backup');
1927 $backupOptions = array(
1928 'backupDir' => $backupDir,
1929 'structTables' => $this->_getBackupStructureOnlyTables(),
1932 $this->_backend->backup($backupOptions);
1935 $filesDir = isset($config->filesdir) ? $config->filesdir : false;
1936 if ($options['files'] && $filesDir) {
1937 `cd $filesDir; tar cjf $backupDir/tine20_files.tar.bz2 .`;
1942 * returns an array of all tables of all applications that should only backup the structure
1945 * @throws Setup_Exception_NotFound
1947 protected function _getBackupStructureOnlyTables()
1951 // find tables that only backup structure
1952 $applications = Tinebase_Application::getInstance()->getApplications();
1955 * @var $application Tinebase_Model_Application
1957 foreach($applications as $application) {
1958 $tableDef = $this->getSetupXml($application->name);
1959 $structOnlys = $tableDef->xpath('//table/backupStructureOnly[text()="true"]');
1961 foreach($structOnlys as $structOnly) {
1962 $tableName = $structOnly->xpath('./../name/text()');
1963 $tables[] = SQL_TABLE_PREFIX . $tableName[0];
1973 * @param $options array(
1974 * 'backupDir' => string // location of backup to restore
1975 * 'config' => bool // restore config
1976 * 'db' => bool // restore database
1977 * 'files' => bool // restore files
1983 public function restore($options)
1985 if (! isset($options['backupDir'])) {
1986 throw new Exception("you need to specify the backupDir");
1989 if ($options['config']) {
1990 $configBackupFile = $options['backupDir']. '/tine20_config.tar.bz2';
1991 if (! file_exists($configBackupFile)) {
1992 throw new Exception("$configBackupFile not found");
1995 $configDir = isset($options['configDir']) ? $options['configDir'] : false;
1997 $configFile = stream_resolve_include_path('config.inc.php');
1999 throw new Exception("can't detect configDir, please use configDir option");
2001 $configDir = dirname($configFile);
2004 `cd $configDir; tar xf $configBackupFile`;
2007 Setup_Core::setupConfig();
2008 $config = Setup_Core::getConfig();
2010 if ($options['db']) {
2011 $this->_backend->restore($options['backupDir']);
2014 $filesDir = isset($config->filesdir) ? $config->filesdir : false;
2015 if ($options['files']) {
2016 $filesBackupFile = $options['backupDir'] . '/tine20_files.tar.bz2';
2017 if (! file_exists($filesBackupFile)) {
2018 throw new Exception("$filesBackupFile not found");
2021 `cd $filesDir; tar xf $filesBackupFile`;