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;
62 const MAX_DB_PREFIX_LENGTH = 10;
65 * don't clone. Use the singleton.
68 private function __clone() {}
71 * url to Tine 2.0 wiki
75 protected $_helperLink = ' <a href="http://wiki.tine20.org/Admins/Install_Howto" target="_blank">Check the Tine 2.0 wiki for support.</a>';
78 * the singleton pattern
80 * @return Setup_Controller
82 public static function getInstance()
84 if (self::$_instance === NULL) {
85 self::$_instance = new Setup_Controller;
88 return self::$_instance;
95 protected function __construct()
97 // setup actions could take quite a while we try to set max execution time to unlimited
98 Setup_Core::setExecutionLifeTime(0);
100 if (!defined('MAXLOOPCOUNT')) {
101 define('MAXLOOPCOUNT', 50);
104 $this->_baseDir = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR;
106 if (Setup_Core::get(Setup_Core::CHECKDB)) {
107 $this->_db = Setup_Core::getDb();
108 $this->_backend = Setup_Backend_Factory::factory();
113 $this->_emailConfigKeys = array(
114 'imap' => Tinebase_Config::IMAP,
115 'smtp' => Tinebase_Config::SMTP,
116 'sieve' => Tinebase_Config::SIEVE,
121 * check system/php requirements (env + ext check)
125 * @todo add message to results array
127 public function checkRequirements()
129 $envCheck = $this->environmentCheck();
131 $databaseCheck = $this->checkDatabase();
133 $extCheck = new Setup_ExtCheck(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'essentials.xml');
134 $extResult = $extCheck->getData();
137 'success' => ($envCheck['success'] && $databaseCheck['success'] && $extResult['success']),
138 'results' => array_merge($envCheck['result'], $databaseCheck['result'], $extResult['result']),
141 $result['totalcount'] = count($result['results']);
147 * check which database extensions are available
151 public function checkDatabase()
158 $loadedExtensions = get_loaded_extensions();
160 if (! in_array('PDO', $loadedExtensions)) {
161 $result['result'][] = array(
164 'message' => "PDO extension not found." . $this->_helperLink
170 // check mysql requirements
171 $missingMysqlExtensions = array_diff(array('pdo_mysql'), $loadedExtensions);
173 // check pgsql requirements
174 $missingPgsqlExtensions = array_diff(array('pgsql', 'pdo_pgsql'), $loadedExtensions);
176 // check oracle requirements
177 $missingOracleExtensions = array_diff(array('oci8'), $loadedExtensions);
179 if (! empty($missingMysqlExtensions) && ! empty($missingPgsqlExtensions) && ! empty($missingOracleExtensions)) {
180 $result['result'][] = array(
183 'message' => 'Database extensions missing. For MySQL install: ' . implode(', ', $missingMysqlExtensions) .
184 ' For Oracle install: ' . implode(', ', $missingOracleExtensions) .
185 ' For PostgreSQL install: ' . implode(', ', $missingPgsqlExtensions) .
192 $result['result'][] = array(
195 'message' => 'Support for following databases enabled: ' .
196 (empty($missingMysqlExtensions) ? 'MySQL' : '') . ' ' .
197 (empty($missingOracleExtensions) ? 'Oracle' : '') . ' ' .
198 (empty($missingPgsqlExtensions) ? 'PostgreSQL' : '') . ' '
200 $result['success'] = TRUE;
206 * Check if tableprefix is longer than 6 charcters
210 public function checkDatabasePrefix()
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.');
223 * Check if logger is properly configured (or not configured at all)
227 public function checkConfigLogger()
229 $config = Setup_Core::get(Setup_Core::CONFIG);
230 if (!isset($config->logger) || !$config->logger->active) {
234 isset($config->logger->filename)
236 file_exists($config->logger->filename) && is_writable($config->logger->filename)
237 || is_writable(dirname($config->logger->filename))
244 * Check if caching is properly configured (or not configured at all)
248 public function checkConfigCaching()
252 $config = Setup_Core::get(Setup_Core::CONFIG);
254 if (! isset($config->caching) || !$config->caching->active) {
257 } else if (! isset($config->caching->backend) || ucfirst($config->caching->backend) === 'File') {
258 $result = $this->checkDir('path', 'caching', FALSE);
260 } else if (ucfirst($config->caching->backend) === 'Redis') {
261 $result = $this->_checkRedisConnect(isset($config->caching->redis) ? $config->caching->redis->toArray() : array());
263 } else if (ucfirst($config->caching->backend) === 'Memcached') {
264 $result = $this->_checkMemcacheConnect(isset($config->caching->memcached) ? $config->caching->memcached->toArray() : array());
272 * checks redis extension and connection
274 * @param array $config
277 protected function _checkRedisConnect($config)
279 if (! extension_loaded('redis')) {
280 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' redis extension not loaded');
284 $host = isset($config['host']) ? $config['host'] : 'localhost';
285 $port = isset($config['port']) ? $config['port'] : 6379;
287 $result = $redis->connect($host, $port);
291 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not connect to redis server at ' . $host . ':' . $port);
298 * checks memcached extension and connection
300 * @param array $config
303 protected function _checkMemcacheConnect($config)
305 if (! extension_loaded('memcache')) {
306 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' memcache extension not loaded');
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);
318 * Check if queue is properly configured (or not configured at all)
322 public function checkConfigQueue()
324 $config = Setup_Core::get(Setup_Core::CONFIG);
325 if (! isset($config->actionqueue) || ! $config->actionqueue->active) {
328 $result = $this->_checkRedisConnect($config->actionqueue->toArray());
335 * check config session
339 public function checkConfigSession()
342 $config = Setup_Core::get(Setup_Core::CONFIG);
343 if (! isset($config->session) || !$config->session->active) {
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());
355 * checks if path in config is writable
357 * @param string $_name
358 * @param string $_group
361 public function checkDir($_name, $_group = NULL, $allowEmptyPath = TRUE)
363 $config = $this->getConfigData();
364 if ($_group !== NULL && (isset($config[$_group]) || array_key_exists($_group, $config))) {
365 $config = $config[$_group];
368 $path = (isset($config[$_name]) || array_key_exists($_name, $config)) ? $config[$_name] : false;
370 return $allowEmptyPath;
372 return @is_writable($path);
377 * get list of applications as found in the filesystem
379 * @return array appName => setupXML
381 public function getInstallableApplications()
383 // create Tinebase tables first
384 $applications = array('Tinebase' => $this->getSetupXml('Tinebase'));
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.');
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);
403 return $applications;
407 * updates installed applications. does nothing if no applications are installed
409 * @param Tinebase_Record_RecordSet $_applications
410 * @return array messages
412 public function updateApplications(Tinebase_Record_RecordSet $_applications = null)
414 if (null === ($user = Setup_Update_Abstract::getSetupFromConfigOrCreateOnTheFly())) {
415 throw new Tinebase_Exception('could not create setup user');
417 Tinebase_Core::set(Tinebase_Core::USER, $user);
419 if ($_applications === null) {
420 $_applications = Tinebase_Application::getInstance()->getApplications();
423 // we need to clone here because we would taint the app cache otherwise
424 $applications = clone($_applications);
426 $this->_updatedApplications = 0;
427 $smallestMajorVersion = NULL;
428 $biggestMajorVersion = NULL;
430 //find smallest major version
431 foreach ($applications as $application) {
432 if ($smallestMajorVersion === NULL || $application->getMajorVersion() < $smallestMajorVersion) {
433 $smallestMajorVersion = $application->getMajorVersion();
435 if ($biggestMajorVersion === NULL || $application->getMajorVersion() > $biggestMajorVersion) {
436 $biggestMajorVersion = $application->getMajorVersion();
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())]);
447 list($major, $minor) = explode('.', $this->getSetupXml('Tinebase')->version[0]);
448 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updating Tinebase to version ' . $major . '.' . $minor);
450 for ($majorVersion = $tinebase->getMajorVersion(); $majorVersion <= $major; $majorVersion++) {
451 $messages = array_merge($messages, $this->updateApplication($tinebase, $majorVersion));
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));
465 'messages' => $messages,
466 'updated' => $this->_updatedApplications,
471 * load the setup.xml file and returns a simplexml object
473 * @param string $_applicationName name of the application
474 * @return SimpleXMLElement
476 public function getSetupXml($_applicationName)
478 $setupXML = $this->_baseDir . ucfirst($_applicationName) . '/Setup/setup.xml';
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.');
485 $xml = simplexml_load_file($setupXML);
493 * @param Tinebase_Model_Application $_application
494 * @throws Setup_Exception
496 public function checkUpdate(Tinebase_Model_Application $_application)
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)) {
504 $this->_backend->checkTable($table);
505 } catch (Setup_Exception $e) {
506 Setup_Core::getLogger()->error(__METHOD__ . '::' . __LINE__ . " Checking table failed with message '{$e->getMessage()}'");
509 throw new Setup_Exception('Table ' . $table->name . ' for application' . $_application->name . " does not exist. \n<strong>Update broken</strong>");
516 * update installed application
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
523 public function updateApplication(Tinebase_Model_Application $_application, $_majorVersion)
525 $setupXml = $this->getSetupXml($_application->name);
528 switch (version_compare($_application->version, $setupXml->version)) {
530 $message = "Executing updates for " . $_application->name . " (starting at " . $_application->version . ")";
532 $messages[] = $message;
533 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' ' . $message);
535 $version = $_application->getMajorAndMinorVersion();
536 $minor = $version['minor'];
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"
545 $_application->version = $nextMajorRelease;
546 Tinebase_Application::getInstance()->updateApplication($_application);
549 $update = new $className($this->_backend);
551 $classMethods = get_class_methods($update);
553 // we must do at least one update
555 $functionName = 'update_' . $minor;
558 $db = Setup_Core::getDb();
559 $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($db);
561 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
562 . ' Updating ' . $_application->name . ' - ' . $functionName
565 $update->$functionName();
567 Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
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());
577 } while(array_search('update_' . $minor, $classMethods) !== false);
580 $messages[] = "<strong> Updated " . $_application->name . " successfully to " . $_majorVersion . '.' . $minor . "</strong>";
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++;
591 Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' No update needed for ' . $_application->name);
595 throw new Setup_Exception('Current application version is higher than version from setup.xml: '
596 . $_application->version . ' > ' . $setupXml->version
601 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Clearing cache after update ...');
602 $this->_enableCaching();
603 Tinebase_Core::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL);
609 * checks if update is required
613 public function updateNeeded($_application)
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);
623 $updateNeeded = version_compare($_application->version, $setupXml->version);
625 if($updateNeeded === -1) {
633 * search for installed and installable applications
637 public function searchApplications()
639 // get installable apps
640 $installable = $this->getInstallableApplications();
642 // get installed apps
643 if (Setup_Core::get(Setup_Core::CHECKDB)) {
645 $installed = Tinebase_Application::getInstance()->getApplications(NULL, 'id')->toArray();
647 // merge to create result array
648 $applications = array();
649 foreach ($installed as $application) {
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.');
656 $depends = (array) $installable[$application['name']]->depends;
657 if (isset($depends['application'])) {
658 $depends = implode(', ', (array) $depends['application']);
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']]);
667 } catch (Zend_Db_Statement_Exception $zse) {
672 foreach ($installable as $name => $setupXML) {
673 $depends = (array) $setupXML->depends;
674 if (isset($depends['application'])) {
675 $depends = implode(', ', (array) $depends['application']);
678 $applications[] = array(
680 'current_version' => (string) $setupXML->version,
681 'install_status' => 'uninstalled',
682 'depends' => $depends,
687 'results' => $applications,
688 'totalcount' => count($applications)
693 * checks if setup is required
697 public function setupRequired()
701 // check if applications table exists / only if db available
702 if (Setup_Core::isRegistered(Setup_Core::DB)) {
704 $applicationTable = Setup_Core::getDb()->describeTable(SQL_TABLE_PREFIX . 'applications');
705 if (empty($applicationTable)) {
706 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Applications table empty');
709 } catch (Zend_Db_Statement_Exception $zdse) {
710 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $zdse->getMessage());
712 } catch (Zend_Db_Adapter_Exception $zdae) {
713 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $zdae->getMessage());
722 * do php.ini environment check
726 public function environmentCheck()
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'
744 foreach ($requiredIniSettings as $variable => $newValue) {
745 $oldValue = ini_get($variable);
747 if ($variable == 'memory_limit') {
748 $required = Tinebase_Helper::convertToBytes($newValue);
749 $set = Tinebase_Helper::convertToBytes($oldValue);
751 if ( $set < $required) {
755 'message' => "You need to set $variable equal or greater than $required (now: $set)." . $this->_helperLink
760 } elseif ($oldValue != $newValue) {
761 if (ini_set($variable, $newValue) === false) {
765 'message' => "You need to set $variable from $oldValue to $newValue." . $this->_helperLink
780 'success' => $success,
785 * get config file default values
789 public function getConfigDefaults()
791 $defaultPath = Setup_Core::guessTempDir();
795 'host' => 'localhost',
796 'dbname' => 'tine20',
797 'username' => 'tine20',
799 'adapter' => 'pdo_mysql',
800 'tableprefix' => 'tine20_',
804 'filename' => $defaultPath . DIRECTORY_SEPARATOR . 'tine20.log',
811 'path' => $defaultPath,
813 'tmpdir' => $defaultPath,
815 'path' => Tinebase_Session::getSessionDir(),
824 * get config file values
828 public function getConfigData()
830 $configArray = Setup_Core::get(Setup_Core::CONFIG)->toArray();
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();
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.");
849 #####################################
855 * save data to config file
857 * @param array $_data
858 * @param boolean $_merge
860 public function saveConfigData($_data, $_merge = TRUE)
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']);
866 if (Setup_Core::configFileExists() && !Setup_Core::configFileWritable()) {
867 throw new Setup_Exception('Config File is not writeable.');
870 if (Setup_Core::configFileExists()) {
872 $filename = Setup_Core::getConfigFilePath();
875 $filename = dirname(__FILE__) . '/../config.inc.php';
878 $config = $this->writeConfigToFile($_data, $_merge, $filename);
880 Setup_Core::set(Setup_Core::CONFIG, $config);
882 Setup_Core::setupLogger();
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);
891 * write config to a file
893 * @param array $_data
894 * @param boolean $_merge
895 * @param string $_filename
896 * @return Zend_Config
898 public function writeConfigToFile($_data, $_merge, $_filename)
900 // merge config data and active config
902 $activeConfig = Setup_Core::get(Setup_Core::CONFIG);
903 $config = new Zend_Config($activeConfig->toArray(), true);
904 $config->merge(new Zend_Config($_data));
906 $config = new Zend_Config($_data);
910 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updating config.inc.php');
911 $writer = new Zend_Config_Writer_Array(array(
913 'filename' => $_filename,
921 * load authentication data
925 public function loadAuthenticationData()
928 'authentication' => $this->_getAuthProviderData(),
929 'accounts' => $this->_getAccountsStorageData(),
930 'redirectSettings' => $this->_getRedirectSettings(),
931 'password' => $this->_getPasswordSettings(),
932 'saveusername' => $this->_getReuseUsernameSettings()
937 * Update authentication data
939 * Needs Tinebase tables to store the data, therefore
940 * installs Tinebase if it is not already installed
942 * @param array $_authenticationData
946 public function saveAuthentication($_authenticationData)
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);
953 $installationOptions = array('authenticationData' => $_authenticationData);
954 $this->installApplications(array('Tinebase'), $installationOptions);
959 * Save {@param $_authenticationData} to config file
961 * @param array $_authenticationData [hash containing settings for authentication and accountsStorage]
964 protected function _updateAuthentication($_authenticationData)
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));
969 $this->_enableCaching();
971 if (isset($_authenticationData['authentication'])) {
972 $this->_updateAuthenticationProvider($_authenticationData['authentication']);
975 if (isset($_authenticationData['accounts'])) {
976 $this->_updateAccountsStorage($_authenticationData['accounts']);
979 if (isset($_authenticationData['redirectSettings'])) {
980 $this->_updateRedirectSettings($_authenticationData['redirectSettings']);
983 if (isset($_authenticationData['password'])) {
984 $this->_updatePasswordSettings($_authenticationData['password']);
987 if (isset($_authenticationData['saveusername'])) {
988 $this->_updateReuseUsername($_authenticationData['saveusername']);
991 if (isset($_authenticationData['acceptedTermsVersion'])) {
992 $this->saveAcceptedTerms($_authenticationData['acceptedTermsVersion']);
997 * enable caching to make sure cache gets cleaned if config options change
999 protected function _enableCaching()
1001 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Activate caching backend if available ...');
1003 Tinebase_Core::setupCache();
1007 * Update authentication provider
1009 * @param array $_data
1012 protected function _updateAuthenticationProvider($_data)
1014 Tinebase_Auth::setBackendType($_data['backend']);
1015 $config = (isset($_data[$_data['backend']])) ? $_data[$_data['backend']] : $_data;
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]);
1024 Tinebase_Auth::setBackendConfiguration($config, null, true);
1025 Tinebase_Auth::saveBackendConfiguration();
1029 * Update accountsStorage
1031 * @param array $_data
1034 protected function _updateAccountsStorage($_data)
1036 $originalBackend = Tinebase_User::getConfiguredBackend();
1037 $newBackend = $_data['backend'];
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();
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");
1047 $db = Setup_Core::getDb();
1048 $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($db);
1049 $this->_migrateFromSqlAccountsStorage();
1050 Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
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());
1057 Tinebase_User::setBackendType($originalBackend);
1058 Tinebase_User::saveBackendConfiguration();
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
1070 protected function _migrateFromSqlAccountsStorage()
1072 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Deleting all user accounts, groups, roles and rights');
1073 Tinebase_User::factory(Tinebase_User::SQL)->deleteAllUsers();
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);
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);
1089 $roles = Tinebase_Acl_Roles::getInstance();
1090 $roles->deleteAllRoles();
1092 // import users (from new backend) / create initial users (SQL)
1093 Tinebase_User::syncUsers(array('syncContactData' => TRUE));
1095 $roles->createInitialRoles();
1096 $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
1097 foreach ($applications as $application) {
1098 Setup_Initialize::initializeApplicationRights($application);
1103 * Update redirect settings
1105 * @param array $_data
1108 protected function _updateRedirectSettings($_data)
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);
1117 Tinebase_Config::getInstance()->set($key, $_data[$key]);
1124 * update pw settings
1126 * @param array $data
1128 protected function _updatePasswordSettings($data)
1130 foreach ($data as $config => $value) {
1131 Tinebase_Config::getInstance()->set($config, $value);
1136 * update pw settings
1138 * @param array $data
1140 protected function _updateReuseUsername($data)
1142 foreach ($data as $config => $value) {
1143 Tinebase_Config::getInstance()->set($config, $value);
1149 * get auth provider data
1153 * @todo get this from config table instead of file!
1155 protected function _getAuthProviderData()
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;
1164 * get Accounts storage data
1168 protected function _getAccountsStorageData()
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;
1177 * Get redirect Settings from config table.
1178 * If Tinebase is not installed, default values will be returned.
1182 protected function _getRedirectSettings()
1185 Tinebase_Config::REDIRECTURL => '',
1186 Tinebase_Config::REDIRECTTOREFERRER => '0'
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, '');
1196 * get password settings
1200 * @todo should use generic mechanism to fetch setup related configs
1202 protected function _getPasswordSettings()
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,
1217 $tinebaseInstalled = $this->isInstalled('Tinebase');
1218 foreach ($configs as $config => $default) {
1219 $result[$config] = ($tinebaseInstalled) ? Tinebase_Config::getInstance()->get($config, $default) : $default;
1226 * get Reuse Username to login textbox
1230 * @todo should use generic mechanism to fetch setup related configs
1232 protected function _getReuseUsernameSettings()
1235 Tinebase_Config::REUSEUSERNAME_SAVEUSERNAME => 0,
1239 $tinebaseInstalled = $this->isInstalled('Tinebase');
1240 foreach ($configs as $config => $default) {
1241 $result[$config] = ($tinebaseInstalled) ? Tinebase_Config::getInstance()->get($config, $default) : $default;
1252 public function getEmailConfig()
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;
1261 $result[$configName] = $config;
1270 * @param array $_data
1273 public function saveEmailConfig($_data)
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));
1278 $this->_enableCaching();
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);
1291 * returns all email config keys
1295 public function getEmailConfigKeys()
1297 return $this->_emailConfigKeys;
1301 * get accepted terms config
1305 public function getAcceptedTerms()
1307 return Tinebase_Config::getInstance()->get(Tinebase_Config::ACCEPTEDTERMSVERSION, 0);
1311 * save acceptedTermsVersion
1316 public function saveAcceptedTerms($_data)
1318 Tinebase_Config::getInstance()->set(Tinebase_Config::ACCEPTEDTERMSVERSION, $_data);
1322 * save config option in db
1324 * @param string $key
1325 * @param string|array $value
1326 * @param string $applicationName
1329 public function setConfigOption($key, $value, $applicationName = 'Tinebase')
1331 $config = Tinebase_Config_Abstract::factory($applicationName);
1334 $config->set($key, $value);
1339 * create new setup user session
1341 * @param string $_username
1342 * @param string $_password
1345 public function login($_username, $_password)
1347 $setupAuth = new Setup_Auth($_username, $_password);
1348 $authResult = Zend_Auth::getInstance()->authenticate($setupAuth);
1350 if ($authResult->isValid()) {
1351 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Valid credentials, setting username in session and registry.');
1352 Tinebase_Session::regenerateId();
1354 Setup_Core::set(Setup_Core::USER, $_username);
1355 Setup_Session::getSessionNamespace()->setupuser = $_username;
1359 Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Invalid credentials! ' . print_r($authResult->getMessages(), TRUE));
1360 Tinebase_Session::expireSessionCookie();
1371 public function logout()
1373 $_SESSION = array();
1375 Tinebase_Session::destroyAndRemoveCookie();
1379 * install list of applications
1381 * @param array $_applications list of application names
1382 * @param array | optional $_options
1385 public function installApplications($_applications, $_options = null)
1387 $this->_clearCache();
1389 // check requirements for initial install / add required apps to list
1390 if (! $this->isInstalled('Tinebase')) {
1392 $minimumRequirements = array('Addressbook', 'Tinebase', 'Admin');
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).'
1400 $_applications[] = $requiredApp;
1404 $setupUser = Setup_Update_Abstract::getSetupFromConfigOrCreateOnTheFly();
1405 if ($setupUser && ! Tinebase_Core::getUser() instanceof Tinebase_Model_User) {
1406 Tinebase_Core::set(Tinebase_Core::USER, $setupUser);
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");
1417 $applications[$applicationName] = $this->getSetupXml($applicationName);
1420 $applications = $this->_sortInstallableApplications($applications);
1422 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Installing applications: ' . print_r(array_keys($applications), true));
1424 foreach ($applications as $name => $xml) {
1426 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Could not install application ' . $name);
1428 $this->_installApplication($xml, $_options);
1434 * install tine from dump file
1437 * @throws Setup_Exception
1440 public function installFromDump($options)
1442 $this->_clearCache();
1444 if ($this->isInstalled('Tinebase')) {
1445 throw new Setup_Exception('Tinebase already installed!');
1448 $mysqlBackupFile = $options['backupDir'] . '/tine20_mysql.sql.bz2';
1449 if (! file_exists($mysqlBackupFile)) {
1450 throw new Setup_Exception("$mysqlBackupFile not found");
1453 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Installing from dump ' . $mysqlBackupFile);
1455 $this->_replaceTinebaseidInDump($mysqlBackupFile);
1456 $this->restore($options);
1458 $setupUser = Setup_Update_Abstract::getSetupFromConfigOrCreateOnTheFly();
1459 if ($setupUser && ! Tinebase_Core::getUser() instanceof Tinebase_Model_User) {
1460 Tinebase_Core::set(Tinebase_Core::USER, $setupUser);
1463 // set the replication master id
1464 $tinebase = Tinebase_Application::getInstance()->getApplicationByName('Tinebase');
1465 $state = $tinebase->state;
1466 if (!is_array($state)) {
1469 $state[Tinebase_Model_Application::STATE_REPLICATION_MASTER_ID] = Tinebase_Timemachine_ModificationLog::getInstance()->getMaxInstanceSeq();
1470 $tinebase->state = $state;
1471 Tinebase_Application::getInstance()->updateApplication($tinebase);
1473 $this->updateApplications();
1479 * replace old Tinebase ID in dump to make sure we have a unique installation ID
1481 * TODO: think about moving the Tinebase ID (and more info) to a metadata.json file in the backup zip
1483 * @param $mysqlBackupFile
1484 * @throws Setup_Exception
1486 protected function _replaceTinebaseidInDump($mysqlBackupFile)
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');
1494 $oldTinebaseId = $matches[1];
1496 $cmd = "bzcat $mysqlBackupFile | sed s/"
1497 . $oldTinebaseId . '/'
1498 . Tinebase_Record_Abstract::generateUID() . "/g | " // g for global!
1499 . "bzip2 > " . $mysqlBackupFile . '.tmp';
1501 Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $cmd);
1504 copy($mysqlBackupFile . '.tmp', $mysqlBackupFile);
1505 unlink($mysqlBackupFile . '.tmp');
1509 * delete list of applications
1511 * @param array $_applications list of application names
1513 public function uninstallApplications($_applications)
1515 if (null === ($user = Setup_Update_Abstract::getSetupFromConfigOrCreateOnTheFly())) {
1516 throw new Tinebase_Exception('could not create setup user');
1518 Tinebase_Core::set(Tinebase_Core::USER, $user);
1520 $this->_clearCache();
1522 $installedApps = Tinebase_Application::getInstance()->getApplications();
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;
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;
1536 // get xml and sort apps first
1537 $applications = array();
1538 foreach ($_applications as $applicationName) {
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;
1547 $applications = $this->_sortUninstallableApplications($applications);
1549 foreach ($applications as $name => $xml) {
1550 $app = Tinebase_Application::getInstance()->getApplicationByName($name);
1551 $this->_uninstallApplication($app);
1554 if (true === $deactivatedForeignKeyCheck) {
1555 $this->_backend->setForeignKeyChecks(1);
1560 * install given application
1562 * @param SimpleXMLElement $_xml
1563 * @param array | optional $_options
1565 * @throws Tinebase_Exception_Backend_Database
1567 protected function _installApplication(SimpleXMLElement $_xml, $_options = null)
1569 if ($this->_backend === NULL) {
1570 throw new Tinebase_Exception_Backend_Database('Need configured and working database backend for install.');
1573 if (!$this->checkDatabasePrefix()) {
1574 throw new Tinebase_Exception_Backend_Database('Tableprefix is too long');
1578 if (Setup_Core::isLogLevel(Zend_Log::INFO)) Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Installing application: ' . $_xml->name);
1580 $createdTables = array();
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
1590 $createdTables[] = $table;
1594 // do we have modelconfig + doctrine
1596 $application = Setup_Core::getApplicationInstance($_xml->name, '', true);
1597 $models = $application->getModels(true /* MCv2only */);
1599 if (count($models) > 0) {
1600 // create tables using doctrine 2
1601 Setup_SchemaTool::createSchema($_xml->name, $models);
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(),
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
1621 $application = Tinebase_Application::getInstance()->addApplication($application);
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);
1628 // insert default records
1629 if (isset($_xml->defaultRecords)) {
1630 foreach ($_xml->defaultRecords[0] as $record) {
1631 $this->_backend->execInsertStatement($record);
1635 Setup_Initialize::initialize($application, $_options);
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);
1645 protected function _createTable($table)
1647 if (Setup_Core::isLogLevel(Zend_Log::DEBUG)) Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Creating table: ' . $table->name);
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());
1661 * look for export & import definitions and put them into the db
1663 * @param Tinebase_Model_Application $_application
1665 public function createImportExportDefinitions($_application)
1667 foreach (array('Import', 'Export') as $type) {
1669 $this->_baseDir . $_application->name .
1670 DIRECTORY_SEPARATOR . $type . DIRECTORY_SEPARATOR . 'definitions';
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)) {
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());
1688 $this->_baseDir . $_application->name .
1689 DIRECTORY_SEPARATOR . $type . DIRECTORY_SEPARATOR . 'templates';
1691 if (file_exists($path)) {
1692 $fileSystem = Tinebase_FileSystem::getInstance();
1694 $basepath = $fileSystem->getApplicationBasePath(
1696 Tinebase_FileSystem::FOLDER_TYPE_SHARED
1697 ) . '/' . strtolower($type);
1699 if (false === $fileSystem->isDir($basepath)) {
1700 $fileSystem->createAclNode($basepath);
1703 $templateAppPath = Tinebase_Model_Tree_Node_Path::createFromPath($basepath . '/templates/' . $_application->name);
1705 if (! $fileSystem->isDir($templateAppPath->statpath)) {
1706 $fileSystem->mkdir($templateAppPath->statpath);
1709 foreach (new DirectoryIterator($path) as $item) {
1710 if (!$item->isFile()) {
1713 if (false === ($content = file_get_contents($item->getPathname()))) {
1714 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
1715 . ' Could not import template: ' . $item->getPathname());
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');
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');
1737 * @param Tinebase_Model_Application $_application
1738 * @throws Setup_Exception
1740 protected function _uninstallApplication(Tinebase_Model_Application $_application, $uninstallAll = false)
1742 if ($this->_backend === null) {
1743 throw new Setup_Exception('No setup backend available');
1746 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Uninstall ' . $_application);
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());
1753 $disabledFK = FALSE;
1754 $db = Tinebase_Core::getDb();
1757 $oldCount = count($applicationTables);
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.');
1767 foreach ($applicationTables as $key => $table) {
1768 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Remove table: $table");
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']);
1780 $this->_backend->dropTable($table);
1782 if ($_application->name != 'Tinebase') {
1783 Tinebase_Application::getInstance()->removeApplicationTable($_application, $table);
1786 unset($applicationTables[$key]);
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);
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]);
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");
1809 if ($oldCount > 0 && count($applicationTables) == $oldCount) {
1810 throw new Setup_Exception('dead lock detected oldCount: ' . $oldCount);
1812 } while (count($applicationTables) > 0);
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");
1821 if ($_application->name != 'Tinebase') {
1822 if (!$uninstallAll) {
1823 Tinebase_Relations::getInstance()->removeApplication($_application->name);
1825 Tinebase_Timemachine_ModificationLog::getInstance()->removeApplication($_application);
1827 // delete containers, config options and other data for app
1828 Tinebase_Application::getInstance()->removeApplicationAuxiliaryData($_application);
1831 // remove application from table of installed applications
1832 Tinebase_Application::getInstance()->deleteApplication($_application);
1835 Setup_Uninitialize::uninitialize($_application);
1837 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Removed app: " . $_application->name);
1841 * sort applications by checking dependencies
1843 * @param array $_applications
1846 protected function _sortInstallableApplications($_applications)
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]);
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();
1868 $depends['application'] = (array) $depends['application'];
1870 foreach ($depends['application'] as $app) {
1871 // don't add tinebase (all apps depend on tinebase)
1872 if ($app != 'Tinebase') {
1873 $appsToSort[$name][] = $app;
1878 $appsToSort[$name] = array();
1882 //Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($appsToSort, true));
1886 while (count($appsToSort) > 0 && $count < MAXLOOPCOUNT) {
1888 foreach($appsToSort as $name => $depends) {
1890 if (empty($depends)) {
1891 // no dependencies left -> copy app to result set
1892 $result[$name] = $_applications[$name];
1893 unset($appsToSort[$name]);
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]);
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));
1915 * sort applications by checking dependencies
1917 * @param array $_applications
1920 protected function _sortUninstallableApplications($_applications)
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();
1934 $depends['application'] = (array) $depends['application'];
1936 foreach ($depends['application'] as $app) {
1937 // don't add tinebase (all apps depend on tinebase)
1938 if ($app != 'Tinebase') {
1939 $appsToSort[$name][] = $app;
1944 $appsToSort[$name] = array();
1951 while (count($appsToSort) > 0 && $count < MAXLOOPCOUNT) {
1953 foreach($appsToSort as $name => $depends) {
1954 //Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " - $count $name - " . print_r($depends, true));
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;
1965 // add it to results
1966 if (!$otherAppDepends) {
1967 $result[$name] = $_applications[$name];
1968 unset($appsToSort[$name]);
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));
1979 // Tinebase is uninstalled last
1980 if (isset($_applications['Tinebase'])) {
1981 $result['Tinebase'] = $_applications['Tinebase'];
1982 unset($_applications['Tinebase']);
1989 * check if an application is installed
1991 * @param string $appname
1994 public function isInstalled($appname)
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);
2012 protected function _clearCache()
2014 // setup cache (via tinebase because it is disabled in setup by default)
2015 Tinebase_Core::setupCache(TRUE);
2017 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Clearing cache ...');
2020 $cache = Setup_Core::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL);
2022 Tinebase_Application::getInstance()->resetClassCache();
2023 Tinebase_Cache_PerRequest::getInstance()->reset();
2025 // deactivate cache again
2026 Tinebase_Core::setupCache(FALSE);
2030 * returns TRUE if filesystem is available
2034 public function isFilesystemAvailable()
2036 if ($this->_isFileSystemAvailable === null) {
2038 $session = Tinebase_Session::getSessionNamespace();
2040 if (isset($session->filesystemAvailable)) {
2041 $this->_isFileSystemAvailable = $session->filesystemAvailable;
2043 return $this->_isFileSystemAvailable;
2045 } catch (Zend_Session_Exception $zse) {
2049 $this->_isFileSystemAvailable = (!empty(Tinebase_Core::getConfig()->filesdir) && is_writeable(Tinebase_Core::getConfig()->filesdir));
2051 if ($session instanceof Zend_Session_Namespace) {
2052 if (Tinebase_Session::isWritable()) {
2053 $session->filesystemAvailable = $this->_isFileSystemAvailable;
2057 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
2058 . ' Filesystem available: ' . ($this->_isFileSystemAvailable ? 'yes' : 'no'));
2061 return $this->_isFileSystemAvailable;
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
2075 public function backup($options)
2077 $config = Setup_Core::getConfig();
2079 $backupDir = isset($options['backupDir']) ? $options['backupDir'] : $config->backupDir;
2081 throw new Exception('backupDir not configured');
2084 if (! isset($options['noTimestamp'])) {
2085 $backupDir .= '/' . date_create('now', new DateTimeZone('UTC'))->format('Y-m-d-H-i-s');
2088 if (!is_dir($backupDir) && !mkdir($backupDir, 0700, true)) {
2089 throw new Exception("$backupDir could not be created");
2092 if ($options['config']) {
2093 $configFile = stream_resolve_include_path('config.inc.php');
2094 $configDir = dirname($configFile);
2096 $files = file_exists("$configDir/index.php") ? 'config.inc.php' : '.';
2097 `cd $configDir; tar cjf $backupDir/tine20_config.tar.bz2 $files`;
2100 if ($options['db']) {
2101 if (! $this->_backend) {
2102 throw new Exception('db not configured, cannot backup');
2105 $backupOptions = array(
2106 'backupDir' => $backupDir,
2107 'structTables' => $this->_getBackupStructureOnlyTables(),
2110 $this->_backend->backup($backupOptions);
2113 $filesDir = isset($config->filesdir) ? $config->filesdir : false;
2114 if ($options['files'] && $filesDir) {
2115 `cd $filesDir; tar cjf $backupDir/tine20_files.tar.bz2 .`;
2120 * returns an array of all tables of all applications that should only backup the structure
2123 * @throws Setup_Exception_NotFound
2125 protected function _getBackupStructureOnlyTables()
2129 // find tables that only backup structure
2130 $applications = Tinebase_Application::getInstance()->getApplications();
2133 * @var $application Tinebase_Model_Application
2135 foreach($applications as $application) {
2136 $tableDef = $this->getSetupXml($application->name);
2137 $structOnlys = $tableDef->xpath('//table/backupStructureOnly[text()="true"]');
2139 foreach($structOnlys as $structOnly) {
2140 $tableName = $structOnly->xpath('./../name/text()');
2141 $tables[] = SQL_TABLE_PREFIX . $tableName[0];
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
2159 * @throws Setup_Exception
2161 public function restore($options)
2163 if (! isset($options['backupDir'])) {
2164 throw new Setup_Exception("you need to specify the backupDir");
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");
2173 $configDir = isset($options['configDir']) ? $options['configDir'] : false;
2175 $configFile = stream_resolve_include_path('config.inc.php');
2177 throw new Setup_Exception("can't detect configDir, please use configDir option");
2179 $configDir = dirname($configFile);
2182 `cd $configDir; tar xf $configBackupFile`;
2185 Setup_Core::setupConfig();
2186 $config = Setup_Core::getConfig();
2188 if (isset($options['db']) && $options['db']) {
2189 $this->_backend->restore($options['backupDir']);
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");
2200 `cd $filesDir; tar xf $filesBackupFile`;
2204 public function compareSchema($options)
2206 if (! isset($options['otherdb'])) {
2207 throw new Exception("you need to specify the otherdb");
2210 return Setup_SchemaTool::compareSchema($options['otherdb']);