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 * @param boolean $getInstalled applications, too
380 * @return array appName => setupXML
382 public function getInstallableApplications($getInstalled = false)
384 // create Tinebase tables first
385 $applications = $getInstalled || ! $this->isInstalled('Tinebase')
386 ? array('Tinebase' => $this->getSetupXml('Tinebase'))
390 $dirIterator = new DirectoryIterator($this->_baseDir);
391 } catch (Exception $e) {
392 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not open base dir: ' . $this->_baseDir);
393 throw new Tinebase_Exception_AccessDenied('Could not open Tine 2.0 root directory.');
396 foreach ($dirIterator as $item) {
397 $appName = $item->getFileName();
398 if ($appName{0} != '.' && $appName != 'Tinebase' && $item->isDir()) {
399 $fileName = $this->_baseDir . $appName . '/Setup/setup.xml' ;
400 if (file_exists($fileName) && ($getInstalled || ! $this->isInstalled($appName))) {
401 $applications[$appName] = $this->getSetupXml($appName);
406 return $applications;
410 * updates installed applications. does nothing if no applications are installed
412 * @param Tinebase_Record_RecordSet $_applications
413 * @return array messages
415 public function updateApplications(Tinebase_Record_RecordSet $_applications = null)
417 if (null === ($user = Setup_Update_Abstract::getSetupFromConfigOrCreateOnTheFly())) {
418 throw new Tinebase_Exception('could not create setup user');
420 Tinebase_Core::set(Tinebase_Core::USER, $user);
422 if ($_applications === null) {
423 $_applications = Tinebase_Application::getInstance()->getApplications();
426 // we need to clone here because we would taint the app cache otherwise
427 $applications = clone($_applications);
429 $this->_updatedApplications = 0;
430 $smallestMajorVersion = NULL;
431 $biggestMajorVersion = NULL;
433 //find smallest major version
434 foreach ($applications as $application) {
435 if ($smallestMajorVersion === NULL || $application->getMajorVersion() < $smallestMajorVersion) {
436 $smallestMajorVersion = $application->getMajorVersion();
438 if ($biggestMajorVersion === NULL || $application->getMajorVersion() > $biggestMajorVersion) {
439 $biggestMajorVersion = $application->getMajorVersion();
445 // update tinebase first (to biggest major version)
446 $tinebase = $applications->filter('name', 'Tinebase')->getFirstRecord();
447 if (! empty($tinebase)) {
448 unset($applications[$applications->getIndexById($tinebase->getId())]);
450 list($major, $minor) = explode('.', $this->getSetupXml('Tinebase')->version[0]);
451 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updating Tinebase to version ' . $major . '.' . $minor);
453 for ($majorVersion = $tinebase->getMajorVersion(); $majorVersion <= $major; $majorVersion++) {
454 $messages = array_merge($messages, $this->updateApplication($tinebase, $majorVersion));
459 for ($majorVersion = $smallestMajorVersion; $majorVersion <= $biggestMajorVersion; $majorVersion++) {
460 foreach ($applications as $application) {
461 if ($application->getMajorVersion() <= $majorVersion) {
462 $messages = array_merge($messages, $this->updateApplication($application, $majorVersion));
468 'messages' => $messages,
469 'updated' => $this->_updatedApplications,
474 * load the setup.xml file and returns a simplexml object
476 * @param string $_applicationName name of the application
477 * @return SimpleXMLElement
479 public function getSetupXml($_applicationName)
481 $setupXML = $this->_baseDir . ucfirst($_applicationName) . '/Setup/setup.xml';
483 if (!file_exists($setupXML)) {
484 throw new Setup_Exception_NotFound(ucfirst($_applicationName)
485 . '/Setup/setup.xml not found. If application got renamed or deleted, re-run setup.php.');
488 $xml = simplexml_load_file($setupXML);
496 * @param Tinebase_Model_Application $_application
497 * @throws Setup_Exception
499 public function checkUpdate(Tinebase_Model_Application $_application)
501 $xmlTables = $this->getSetupXml($_application->name);
502 if(isset($xmlTables->tables)) {
503 foreach ($xmlTables->tables[0] as $tableXML) {
504 $table = Setup_Backend_Schema_Table_Factory::factory('Xml', $tableXML);
505 if (true == $this->_backend->tableExists($table->name)) {
507 $this->_backend->checkTable($table);
508 } catch (Setup_Exception $e) {
509 Setup_Core::getLogger()->error(__METHOD__ . '::' . __LINE__ . " Checking table failed with message '{$e->getMessage()}'");
512 throw new Setup_Exception('Table ' . $table->name . ' for application' . $_application->name . " does not exist. \n<strong>Update broken</strong>");
519 * update installed application
521 * @param Tinebase_Model_Application $_application
522 * @param string $_majorVersion
523 * @return array messages
524 * @throws Setup_Exception if current app version is too high
526 public function updateApplication(Tinebase_Model_Application $_application, $_majorVersion)
528 $setupXml = $this->getSetupXml($_application->name);
531 switch (version_compare($_application->version, $setupXml->version)) {
533 $message = "Executing updates for " . $_application->name . " (starting at " . $_application->version . ")";
535 $messages[] = $message;
536 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' ' . $message);
538 $version = $_application->getMajorAndMinorVersion();
539 $minor = $version['minor'];
541 $className = ucfirst($_application->name) . '_Setup_Update_Release' . $_majorVersion;
542 if(! class_exists($className)) {
543 $nextMajorRelease = ($_majorVersion + 1) . ".0";
544 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
545 . " Update class {$className} does not exists, skipping release {$_majorVersion} for app "
546 . "{$_application->name} and increasing version to $nextMajorRelease"
548 $_application->version = $nextMajorRelease;
549 Tinebase_Application::getInstance()->updateApplication($_application);
552 $update = new $className($this->_backend);
554 $classMethods = get_class_methods($update);
556 // we must do at least one update
558 $functionName = 'update_' . $minor;
561 $db = Setup_Core::getDb();
562 $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($db);
564 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
565 . ' Updating ' . $_application->name . ' - ' . $functionName
568 $update->$functionName();
570 Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
572 } catch (Exception $e) {
573 Tinebase_TransactionManager::getInstance()->rollBack();
574 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getMessage());
575 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getTraceAsString());
580 } while(array_search('update_' . $minor, $classMethods) !== false);
583 $messages[] = "<strong> Updated " . $_application->name . " successfully to " . $_majorVersion . '.' . $minor . "</strong>";
585 // update app version
586 $updatedApp = Tinebase_Application::getInstance()->getApplicationById($_application->getId());
587 $_application->version = $updatedApp->version;
588 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updated ' . $_application->name . " successfully to " . $_application->version);
589 $this->_updatedApplications++;
594 Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' No update needed for ' . $_application->name);
598 throw new Setup_Exception('Current application version is higher than version from setup.xml: '
599 . $_application->version . ' > ' . $setupXml->version
604 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Clearing cache after update ...');
605 $this->_enableCaching();
606 Tinebase_Core::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL);
612 * checks if update is required
616 public function updateNeeded($_application)
619 $setupXml = $this->getSetupXml($_application->name);
620 } catch (Setup_Exception_NotFound $senf) {
621 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' ' . $senf->getMessage() . ' Disabling application "' . $_application->name . '".');
622 Tinebase_Application::getInstance()->setApplicationState(array($_application->getId()), Tinebase_Application::DISABLED);
626 $updateNeeded = version_compare($_application->version, $setupXml->version);
628 if($updateNeeded === -1) {
636 * search for installed and installable applications
640 public function searchApplications()
642 // get installable apps
643 $installable = $this->getInstallableApplications(/* $getInstalled */ true);
645 // get installed apps
646 if (Setup_Core::get(Setup_Core::CHECKDB)) {
648 $installed = Tinebase_Application::getInstance()->getApplications(NULL, 'id')->toArray();
650 // merge to create result array
651 $applications = array();
652 foreach ($installed as $application) {
654 if (! (isset($installable[$application['name']]) || array_key_exists($application['name'], $installable))) {
655 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' App ' . $application['name'] . ' does not exist any more.');
659 $depends = (array) $installable[$application['name']]->depends;
660 if (isset($depends['application'])) {
661 $depends = implode(', ', (array) $depends['application']);
664 $application['current_version'] = (string) $installable[$application['name']]->version;
665 $application['install_status'] = (version_compare($application['version'], $application['current_version']) === -1) ? 'updateable' : 'uptodate';
666 $application['depends'] = $depends;
667 $applications[] = $application;
668 unset($installable[$application['name']]);
670 } catch (Zend_Db_Statement_Exception $zse) {
675 foreach ($installable as $name => $setupXML) {
676 $depends = (array) $setupXML->depends;
677 if (isset($depends['application'])) {
678 $depends = implode(', ', (array) $depends['application']);
681 $applications[] = array(
683 'current_version' => (string) $setupXML->version,
684 'install_status' => 'uninstalled',
685 'depends' => $depends,
690 'results' => $applications,
691 'totalcount' => count($applications)
696 * checks if setup is required
700 public function setupRequired()
704 // check if applications table exists / only if db available
705 if (Setup_Core::isRegistered(Setup_Core::DB)) {
707 $applicationTable = Setup_Core::getDb()->describeTable(SQL_TABLE_PREFIX . 'applications');
708 if (empty($applicationTable)) {
709 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Applications table empty');
712 } catch (Zend_Db_Statement_Exception $zdse) {
713 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $zdse->getMessage());
715 } catch (Zend_Db_Adapter_Exception $zdae) {
716 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $zdae->getMessage());
725 * do php.ini environment check
729 public function environmentCheck()
737 // check php environment
738 $requiredIniSettings = array(
739 'magic_quotes_sybase' => 0,
740 'magic_quotes_gpc' => 0,
741 'magic_quotes_runtime' => 0,
742 'mbstring.func_overload' => 0,
743 'eaccelerator.enable' => 0,
744 'memory_limit' => '48M'
747 foreach ($requiredIniSettings as $variable => $newValue) {
748 $oldValue = ini_get($variable);
750 if ($variable == 'memory_limit') {
751 $required = Tinebase_Helper::convertToBytes($newValue);
752 $set = Tinebase_Helper::convertToBytes($oldValue);
754 if ( $set < $required) {
758 'message' => "You need to set $variable equal or greater than $required (now: $set)." . $this->_helperLink
763 } elseif ($oldValue != $newValue) {
764 if (ini_set($variable, $newValue) === false) {
768 'message' => "You need to set $variable from $oldValue to $newValue." . $this->_helperLink
783 'success' => $success,
788 * get config file default values
792 public function getConfigDefaults()
794 $defaultPath = Setup_Core::guessTempDir();
798 'host' => 'localhost',
799 'dbname' => 'tine20',
800 'username' => 'tine20',
802 'adapter' => 'pdo_mysql',
803 'tableprefix' => 'tine20_',
807 'filename' => $defaultPath . DIRECTORY_SEPARATOR . 'tine20.log',
814 'path' => $defaultPath,
816 'tmpdir' => $defaultPath,
818 'path' => Tinebase_Session::getSessionDir(),
827 * get config file values
831 public function getConfigData()
833 $configArray = Setup_Core::get(Setup_Core::CONFIG)->toArray();
835 #####################################
836 # LEGACY/COMPATIBILITY:
837 # (1) had to rename session.save_path key to sessiondir because otherwise the
838 # generic save config method would interpret the "_" as array key/value seperator
839 # (2) moved session config to subgroup 'session'
840 if (empty($configArray['session']) || empty($configArray['session']['path'])) {
841 foreach (array('session.save_path', 'sessiondir') as $deprecatedSessionDir) {
842 $sessionDir = (isset($configArray[$deprecatedSessionDir]) || array_key_exists($deprecatedSessionDir, $configArray)) ? $configArray[$deprecatedSessionDir] : '';
843 if (! empty($sessionDir)) {
844 if (empty($configArray['session'])) {
845 $configArray['session'] = array();
847 $configArray['session']['path'] = $sessionDir;
848 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " config.inc.php key '{$deprecatedSessionDir}' should be renamed to 'path' and moved to 'session' group.");
852 #####################################
858 * save data to config file
860 * @param array $_data
861 * @param boolean $_merge
863 public function saveConfigData($_data, $_merge = TRUE)
865 if (!empty($_data['setupuser']['password']) && !Setup_Auth::isMd5($_data['setupuser']['password'])) {
866 $password = $_data['setupuser']['password'];
867 $_data['setupuser']['password'] = md5($_data['setupuser']['password']);
869 if (Setup_Core::configFileExists() && !Setup_Core::configFileWritable()) {
870 throw new Setup_Exception('Config File is not writeable.');
873 if (Setup_Core::configFileExists()) {
875 $filename = Setup_Core::getConfigFilePath();
878 $filename = dirname(__FILE__) . '/../config.inc.php';
881 $config = $this->writeConfigToFile($_data, $_merge, $filename);
883 Setup_Core::set(Setup_Core::CONFIG, $config);
885 Setup_Core::setupLogger();
887 if ($doLogin && isset($password)) {
888 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Create session for setup user ' . $_data['setupuser']['username']);
889 $this->login($_data['setupuser']['username'], $password);
894 * write config to a file
896 * @param array $_data
897 * @param boolean $_merge
898 * @param string $_filename
899 * @return Zend_Config
901 public function writeConfigToFile($_data, $_merge, $_filename)
903 // merge config data and active config
905 $activeConfig = Setup_Core::get(Setup_Core::CONFIG);
906 $config = new Zend_Config($activeConfig->toArray(), true);
907 $config->merge(new Zend_Config($_data));
909 $config = new Zend_Config($_data);
913 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updating config.inc.php');
914 $writer = new Zend_Config_Writer_Array(array(
916 'filename' => $_filename,
924 * load authentication data
928 public function loadAuthenticationData()
931 'authentication' => $this->_getAuthProviderData(),
932 'accounts' => $this->_getAccountsStorageData(),
933 'redirectSettings' => $this->_getRedirectSettings(),
934 'password' => $this->_getPasswordSettings(),
935 'saveusername' => $this->_getReuseUsernameSettings()
940 * Update authentication data
942 * Needs Tinebase tables to store the data, therefore
943 * installs Tinebase if it is not already installed
945 * @param array $_authenticationData
949 public function saveAuthentication($_authenticationData)
951 if ($this->isInstalled('Tinebase')) {
952 // NOTE: Tinebase_Setup_Initialiser calls this function again so
953 // we come to this point on initial installation _and_ update
954 $this->_updateAuthentication($_authenticationData);
956 $installationOptions = array('authenticationData' => $_authenticationData);
957 $this->installApplications(array('Tinebase'), $installationOptions);
962 * Save {@param $_authenticationData} to config file
964 * @param array $_authenticationData [hash containing settings for authentication and accountsStorage]
967 protected function _updateAuthentication($_authenticationData)
969 // this is a dangerous TRACE as there might be passwords in here!
970 //if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_authenticationData, TRUE));
972 $this->_enableCaching();
974 if (isset($_authenticationData['authentication'])) {
975 $this->_updateAuthenticationProvider($_authenticationData['authentication']);
978 if (isset($_authenticationData['accounts'])) {
979 $this->_updateAccountsStorage($_authenticationData['accounts']);
982 if (isset($_authenticationData['redirectSettings'])) {
983 $this->_updateRedirectSettings($_authenticationData['redirectSettings']);
986 if (isset($_authenticationData['password'])) {
987 $this->_updatePasswordSettings($_authenticationData['password']);
990 if (isset($_authenticationData['saveusername'])) {
991 $this->_updateReuseUsername($_authenticationData['saveusername']);
994 if (isset($_authenticationData['acceptedTermsVersion'])) {
995 $this->saveAcceptedTerms($_authenticationData['acceptedTermsVersion']);
1000 * enable caching to make sure cache gets cleaned if config options change
1002 protected function _enableCaching()
1004 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Activate caching backend if available ...');
1006 Tinebase_Core::setupCache();
1010 * Update authentication provider
1012 * @param array $_data
1015 protected function _updateAuthenticationProvider($_data)
1017 Tinebase_Auth::setBackendType($_data['backend']);
1018 $config = (isset($_data[$_data['backend']])) ? $_data[$_data['backend']] : $_data;
1020 $excludeKeys = array('adminLoginName', 'adminPassword', 'adminPasswordConfirmation');
1021 foreach ($excludeKeys as $key) {
1022 if ((isset($config[$key]) || array_key_exists($key, $config))) {
1023 unset($config[$key]);
1027 Tinebase_Auth::setBackendConfiguration($config, null, true);
1028 Tinebase_Auth::saveBackendConfiguration();
1032 * Update accountsStorage
1034 * @param array $_data
1037 protected function _updateAccountsStorage($_data)
1039 $originalBackend = Tinebase_User::getConfiguredBackend();
1040 $newBackend = $_data['backend'];
1042 Tinebase_User::setBackendType($_data['backend']);
1043 $config = (isset($_data[$_data['backend']])) ? $_data[$_data['backend']] : $_data;
1044 Tinebase_User::setBackendConfiguration($config, null, true);
1045 Tinebase_User::saveBackendConfiguration();
1047 if ($originalBackend != $newBackend && $this->isInstalled('Addressbook') && $originalBackend == Tinebase_User::SQL) {
1048 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Switching from $originalBackend to $newBackend account storage");
1050 $db = Setup_Core::getDb();
1051 $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($db);
1052 $this->_migrateFromSqlAccountsStorage();
1053 Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
1055 } catch (Exception $e) {
1056 Tinebase_TransactionManager::getInstance()->rollBack();
1057 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getMessage());
1058 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getTraceAsString());
1060 Tinebase_User::setBackendType($originalBackend);
1061 Tinebase_User::saveBackendConfiguration();
1069 * migrate from SQL account storage to another one (for example LDAP)
1070 * - deletes all users, groups and roles because they will be
1071 * imported from new accounts storage backend
1073 protected function _migrateFromSqlAccountsStorage()
1075 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Deleting all user accounts, groups, roles and rights');
1076 Tinebase_User::factory(Tinebase_User::SQL)->deleteAllUsers();
1078 $contactSQLBackend = new Addressbook_Backend_Sql();
1079 $allUserContactIds = $contactSQLBackend->search(new Addressbook_Model_ContactFilter(array('type' => 'user')), null, true);
1080 if (count($allUserContactIds) > 0) {
1081 $contactSQLBackend->delete($allUserContactIds);
1085 Tinebase_Group::factory(Tinebase_Group::SQL)->deleteAllGroups();
1086 $listsSQLBackend = new Addressbook_Backend_List();
1087 $allGroupListIds = $listsSQLBackend->search(new Addressbook_Model_ListFilter(array('type' => 'group')), null, true);
1088 if (count($allGroupListIds) > 0) {
1089 $listsSQLBackend->delete($allGroupListIds);
1092 $roles = Tinebase_Acl_Roles::getInstance();
1093 $roles->deleteAllRoles();
1095 // import users (from new backend) / create initial users (SQL)
1096 Tinebase_User::syncUsers(array('syncContactData' => TRUE));
1098 $roles->createInitialRoles();
1099 $applications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
1100 foreach ($applications as $application) {
1101 Setup_Initialize::initializeApplicationRights($application);
1106 * Update redirect settings
1108 * @param array $_data
1111 protected function _updateRedirectSettings($_data)
1113 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_data, 1));
1114 $keys = array(Tinebase_Config::REDIRECTURL, Tinebase_Config::REDIRECTALWAYS, Tinebase_Config::REDIRECTTOREFERRER);
1115 foreach ($keys as $key) {
1116 if ((isset($_data[$key]) || array_key_exists($key, $_data))) {
1117 if (strlen($_data[$key]) === 0) {
1118 Tinebase_Config::getInstance()->delete($key);
1120 Tinebase_Config::getInstance()->set($key, $_data[$key]);
1127 * update pw settings
1129 * @param array $data
1131 protected function _updatePasswordSettings($data)
1133 foreach ($data as $config => $value) {
1134 Tinebase_Config::getInstance()->set($config, $value);
1139 * update pw settings
1141 * @param array $data
1143 protected function _updateReuseUsername($data)
1145 foreach ($data as $config => $value) {
1146 Tinebase_Config::getInstance()->set($config, $value);
1152 * get auth provider data
1156 * @todo get this from config table instead of file!
1158 protected function _getAuthProviderData()
1160 $result = Tinebase_Auth::getBackendConfigurationWithDefaults(Setup_Core::get(Setup_Core::CHECKDB));
1161 $result['backend'] = (Setup_Core::get(Setup_Core::CHECKDB)) ? Tinebase_Auth::getConfiguredBackend() : Tinebase_Auth::SQL;
1167 * get Accounts storage data
1171 protected function _getAccountsStorageData()
1173 $result = Tinebase_User::getBackendConfigurationWithDefaults(Setup_Core::get(Setup_Core::CHECKDB));
1174 $result['backend'] = (Setup_Core::get(Setup_Core::CHECKDB)) ? Tinebase_User::getConfiguredBackend() : Tinebase_User::SQL;
1180 * Get redirect Settings from config table.
1181 * If Tinebase is not installed, default values will be returned.
1185 protected function _getRedirectSettings()
1188 Tinebase_Config::REDIRECTURL => '',
1189 Tinebase_Config::REDIRECTTOREFERRER => '0'
1191 if (Setup_Core::get(Setup_Core::CHECKDB) && $this->isInstalled('Tinebase')) {
1192 $return[Tinebase_Config::REDIRECTURL] = Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTURL, '');
1193 $return[Tinebase_Config::REDIRECTTOREFERRER] = Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTTOREFERRER, '');
1199 * get password settings
1203 * @todo should use generic mechanism to fetch setup related configs
1205 protected function _getPasswordSettings()
1208 Tinebase_Config::PASSWORD_CHANGE => 1,
1209 Tinebase_Config::PASSWORD_POLICY_ACTIVE => 0,
1210 Tinebase_Config::PASSWORD_POLICY_ONLYASCII => 0,
1211 Tinebase_Config::PASSWORD_POLICY_MIN_LENGTH => 0,
1212 Tinebase_Config::PASSWORD_POLICY_MIN_WORD_CHARS => 0,
1213 Tinebase_Config::PASSWORD_POLICY_MIN_UPPERCASE_CHARS => 0,
1214 Tinebase_Config::PASSWORD_POLICY_MIN_SPECIAL_CHARS => 0,
1215 Tinebase_Config::PASSWORD_POLICY_MIN_NUMBERS => 0,
1216 Tinebase_Config::PASSWORD_POLICY_CHANGE_AFTER => 0,
1220 $tinebaseInstalled = $this->isInstalled('Tinebase');
1221 foreach ($configs as $config => $default) {
1222 $result[$config] = ($tinebaseInstalled) ? Tinebase_Config::getInstance()->get($config, $default) : $default;
1229 * get Reuse Username to login textbox
1233 * @todo should use generic mechanism to fetch setup related configs
1235 protected function _getReuseUsernameSettings()
1238 Tinebase_Config::REUSEUSERNAME_SAVEUSERNAME => 0,
1242 $tinebaseInstalled = $this->isInstalled('Tinebase');
1243 foreach ($configs as $config => $default) {
1244 $result[$config] = ($tinebaseInstalled) ? Tinebase_Config::getInstance()->get($config, $default) : $default;
1255 public function getEmailConfig()
1259 foreach ($this->_emailConfigKeys as $configName => $configKey) {
1260 $config = Tinebase_Config::getInstance()->get($configKey, new Tinebase_Config_Struct(array()))->toArray();
1261 if (! empty($config) && ! isset($config['active'])) {
1262 $config['active'] = TRUE;
1264 $result[$configName] = $config;
1273 * @param array $_data
1276 public function saveEmailConfig($_data)
1278 // this is a dangerous TRACE as there might be passwords in here!
1279 //if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_data, TRUE));
1281 $this->_enableCaching();
1283 foreach ($this->_emailConfigKeys as $configName => $configKey) {
1284 if ((isset($_data[$configName]) || array_key_exists($configName, $_data))) {
1285 // fetch current config first and preserve all values that aren't in $_data array
1286 $currentConfig = Tinebase_Config::getInstance()->get($configKey, new Tinebase_Config_Struct(array()))->toArray();
1287 $newConfig = array_merge($_data[$configName], array_diff_key($currentConfig, $_data[$configName]));
1288 Tinebase_Config::getInstance()->set($configKey, $newConfig);
1294 * returns all email config keys
1298 public function getEmailConfigKeys()
1300 return $this->_emailConfigKeys;
1304 * get accepted terms config
1308 public function getAcceptedTerms()
1310 return Tinebase_Config::getInstance()->get(Tinebase_Config::ACCEPTEDTERMSVERSION, 0);
1314 * save acceptedTermsVersion
1319 public function saveAcceptedTerms($_data)
1321 Tinebase_Config::getInstance()->set(Tinebase_Config::ACCEPTEDTERMSVERSION, $_data);
1325 * save config option in db
1327 * @param string $key
1328 * @param string|array $value
1329 * @param string $applicationName
1332 public function setConfigOption($key, $value, $applicationName = 'Tinebase')
1334 $config = Tinebase_Config_Abstract::factory($applicationName);
1337 $config->set($key, $value);
1342 * create new setup user session
1344 * @param string $_username
1345 * @param string $_password
1348 public function login($_username, $_password)
1350 $setupAuth = new Setup_Auth($_username, $_password);
1351 $authResult = Zend_Auth::getInstance()->authenticate($setupAuth);
1353 if ($authResult->isValid()) {
1354 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Valid credentials, setting username in session and registry.');
1355 Tinebase_Session::regenerateId();
1357 Setup_Core::set(Setup_Core::USER, $_username);
1358 Setup_Session::getSessionNamespace()->setupuser = $_username;
1362 Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Invalid credentials! ' . print_r($authResult->getMessages(), TRUE));
1363 Tinebase_Session::expireSessionCookie();
1374 public function logout()
1376 $_SESSION = array();
1378 Tinebase_Session::destroyAndRemoveCookie();
1382 * install list of applications
1384 * @param array $_applications list of application names
1385 * @param array | optional $_options
1388 public function installApplications($_applications, $_options = null)
1390 $this->_clearCache();
1392 // check requirements for initial install / add required apps to list
1393 if (! $this->isInstalled('Tinebase')) {
1395 $minimumRequirements = array('Addressbook', 'Tinebase', 'Admin');
1397 foreach ($minimumRequirements as $requiredApp) {
1398 if (!in_array($requiredApp, $_applications) && !$this->isInstalled($requiredApp)) {
1399 // Addressbook has to be installed with Tinebase for initial data (user contact)
1400 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
1401 . ' ' . $requiredApp . ' has to be installed first (adding it to list).'
1403 $_applications[] = $requiredApp;
1407 $setupUser = Setup_Update_Abstract::getSetupFromConfigOrCreateOnTheFly();
1408 if ($setupUser && ! Tinebase_Core::getUser() instanceof Tinebase_Model_User) {
1409 Tinebase_Core::set(Tinebase_Core::USER, $setupUser);
1413 // get xml and sort apps first
1414 $applications = array();
1415 foreach ($_applications as $applicationName) {
1416 if ($this->isInstalled($applicationName)) {
1417 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1418 . " skipping installation of application {$applicationName} because it is already installed");
1420 $applications[$applicationName] = $this->getSetupXml($applicationName);
1423 $applications = $this->_sortInstallableApplications($applications);
1425 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Installing applications: ' . print_r(array_keys($applications), true));
1427 foreach ($applications as $name => $xml) {
1429 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Could not install application ' . $name);
1431 $this->_installApplication($xml, $_options);
1437 * install tine from dump file
1440 * @throws Setup_Exception
1443 public function installFromDump($options)
1445 $this->_clearCache();
1447 if ($this->isInstalled('Tinebase')) {
1448 throw new Setup_Exception('Tinebase already installed!');
1451 $mysqlBackupFile = null;
1452 if (isset($options['backupDir'])) {
1453 $mysqlBackupFile = $options['backupDir'] . '/tine20_mysql.sql.bz2';
1454 } else if (isset($options['backupUrl'])) {
1455 // download files first and put them in temp dir
1456 $tempDir = Tinebase_Core::getTempDir();
1458 array('file' => 'tine20_config.tar.bz2', 'param' => 'config'),
1459 array('file' => 'tine20_mysql.sql.bz2', 'param' => 'db'),
1460 array('file' => 'tine20_files.tar.bz2', 'param' => 'files')
1462 if (isset($options[$download['param']])) {
1463 $fileUrl = $options['backupUrl'] . '/' . $download['file'];
1464 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Downloading ' . $fileUrl);
1465 $targetFile = $tempDir . DIRECTORY_SEPARATOR . $download['file'];
1466 if ($download['param'] === 'db') {
1467 $mysqlBackupFile = $targetFile;
1471 fopen($fileUrl, 'r')
1475 $options['backupDir'] = $tempDir;
1477 throw new Setup_Exception("backupDir or backupUrl param required");
1480 if (! $mysqlBackupFile || ! file_exists($mysqlBackupFile)) {
1481 throw new Setup_Exception("$mysqlBackupFile not found");
1484 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Installing from dump ' . $mysqlBackupFile);
1486 $this->_replaceTinebaseidInDump($mysqlBackupFile);
1487 $this->restore($options);
1489 $setupUser = Setup_Update_Abstract::getSetupFromConfigOrCreateOnTheFly();
1490 if ($setupUser && ! Tinebase_Core::getUser() instanceof Tinebase_Model_User) {
1491 Tinebase_Core::set(Tinebase_Core::USER, $setupUser);
1494 // set the replication master id
1495 $tinebase = Tinebase_Application::getInstance()->getApplicationByName('Tinebase');
1496 $state = $tinebase->state;
1497 if (!is_array($state)) {
1500 $state[Tinebase_Model_Application::STATE_REPLICATION_MASTER_ID] = Tinebase_Timemachine_ModificationLog::getInstance()->getMaxInstanceSeq();
1501 $tinebase->state = $state;
1502 Tinebase_Application::getInstance()->updateApplication($tinebase);
1504 $this->updateApplications();
1510 * replace old Tinebase ID in dump to make sure we have a unique installation ID
1512 * TODO: think about moving the Tinebase ID (and more info) to a metadata.json file in the backup zip
1514 * @param $mysqlBackupFile
1515 * @throws Setup_Exception
1517 protected function _replaceTinebaseidInDump($mysqlBackupFile)
1519 // fetch old Tinebase ID
1520 $cmd = "bzcat $mysqlBackupFile | grep \",'Tinebase',\"";
1521 $result = exec($cmd);
1522 if (! preg_match("/'([0-9a-f]+)','Tinebase'/", $result, $matches)) {
1523 throw new Setup_Exception('could not find Tinebase ID in dump');
1525 $oldTinebaseId = $matches[1];
1527 $cmd = "bzcat $mysqlBackupFile | sed s/"
1528 . $oldTinebaseId . '/'
1529 . Tinebase_Record_Abstract::generateUID() . "/g | " // g for global!
1530 . "bzip2 > " . $mysqlBackupFile . '.tmp';
1532 Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $cmd);
1535 copy($mysqlBackupFile . '.tmp', $mysqlBackupFile);
1536 unlink($mysqlBackupFile . '.tmp');
1540 * delete list of applications
1542 * @param array $_applications list of application names
1544 public function uninstallApplications($_applications)
1546 if (null === ($user = Setup_Update_Abstract::getSetupFromConfigOrCreateOnTheFly())) {
1547 throw new Tinebase_Exception('could not create setup user');
1549 Tinebase_Core::set(Tinebase_Core::USER, $user);
1551 $this->_clearCache();
1553 $installedApps = Tinebase_Application::getInstance()->getApplications();
1555 // uninstall all apps if tinebase ist going to be uninstalled
1556 if (count($installedApps) !== count($_applications) && in_array('Tinebase', $_applications)) {
1557 $_applications = $installedApps->name;
1560 // deactivate foreign key check if all installed apps should be uninstalled
1561 $deactivatedForeignKeyCheck = false;
1562 if (count($installedApps) == count($_applications) && get_class($this->_backend) == 'Setup_Backend_Mysql') {
1563 $this->_backend->setForeignKeyChecks(0);
1564 $deactivatedForeignKeyCheck = true;
1567 // get xml and sort apps first
1568 $applications = array();
1569 foreach ($_applications as $applicationName) {
1571 $applications[$applicationName] = $this->getSetupXml($applicationName);
1572 } catch (Setup_Exception_NotFound $senf) {
1573 // application setup.xml not found
1574 Tinebase_Exception::log($senf);
1575 $applications[$applicationName] = null;
1578 $applications = $this->_sortUninstallableApplications($applications);
1580 foreach ($applications as $name => $xml) {
1581 $app = Tinebase_Application::getInstance()->getApplicationByName($name);
1582 $this->_uninstallApplication($app);
1585 if (true === $deactivatedForeignKeyCheck) {
1586 $this->_backend->setForeignKeyChecks(1);
1591 * install given application
1593 * @param SimpleXMLElement $_xml
1594 * @param array | optional $_options
1596 * @throws Tinebase_Exception_Backend_Database
1598 protected function _installApplication(SimpleXMLElement $_xml, $_options = null)
1600 if ($this->_backend === NULL) {
1601 throw new Tinebase_Exception_Backend_Database('Need configured and working database backend for install.');
1604 if (!$this->checkDatabasePrefix()) {
1605 throw new Tinebase_Exception_Backend_Database('Tableprefix is too long');
1609 if (Setup_Core::isLogLevel(Zend_Log::INFO)) Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Installing application: ' . $_xml->name);
1611 $createdTables = array();
1613 // traditional xml declaration
1614 if (isset($_xml->tables)) {
1615 foreach ($_xml->tables[0] as $tableXML) {
1616 $table = Setup_Backend_Schema_Table_Factory::factory('Xml', $tableXML);
1617 if ($this->_createTable($table) !== true) {
1618 // table was gracefully not created, maybe due to missing requirements, just continue
1621 $createdTables[] = $table;
1625 // do we have modelconfig + doctrine
1627 $application = Setup_Core::getApplicationInstance($_xml->name, '', true);
1628 $models = $application->getModels(true /* MCv2only */);
1630 if (count($models) > 0) {
1631 // create tables using doctrine 2
1632 Setup_SchemaTool::createSchema($_xml->name, $models);
1634 // adopt to old workflow
1635 foreach ($models as $model) {
1636 $modelConfiguration = $model::getConfiguration();
1637 $createdTables[] = (object)array(
1638 'name' => Tinebase_Helper::array_value('name', $modelConfiguration->getTable()),
1639 'version' => $modelConfiguration->getVersion(),
1645 $application = new Tinebase_Model_Application(array(
1646 'name' => (string)$_xml->name,
1647 'status' => $_xml->status ? (string)$_xml->status : Tinebase_Application::ENABLED,
1648 'order' => $_xml->order ? (string)$_xml->order : 99,
1649 'version' => (string)$_xml->version
1652 $application = Tinebase_Application::getInstance()->addApplication($application);
1654 // keep track of tables belonging to this application
1655 foreach ($createdTables as $table) {
1656 Tinebase_Application::getInstance()->addApplicationTable($application, (string) $table->name, (int) $table->version);
1659 // insert default records
1660 if (isset($_xml->defaultRecords)) {
1661 foreach ($_xml->defaultRecords[0] as $record) {
1662 $this->_backend->execInsertStatement($record);
1666 Setup_Initialize::initialize($application, $_options);
1668 // look for import definitions and put them into the db
1669 $this->createImportExportDefinitions($application);
1670 } catch (Exception $e) {
1671 Tinebase_Exception::log($e, /* suppress trace */ false);
1676 protected function _createTable($table)
1678 if (Setup_Core::isLogLevel(Zend_Log::DEBUG)) Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Creating table: ' . $table->name);
1681 $result = $this->_backend->createTable($table);
1682 } catch (Zend_Db_Statement_Exception $zdse) {
1683 throw new Tinebase_Exception_Backend_Database('Could not create table: ' . $zdse->getMessage());
1684 } catch (Zend_Db_Adapter_Exception $zdae) {
1685 throw new Tinebase_Exception_Backend_Database('Could not create table: ' . $zdae->getMessage());
1692 * look for export & import definitions and put them into the db
1694 * @param Tinebase_Model_Application $_application
1696 public function createImportExportDefinitions($_application)
1698 foreach (array('Import', 'Export') as $type) {
1700 $this->_baseDir . $_application->name .
1701 DIRECTORY_SEPARATOR . $type . DIRECTORY_SEPARATOR . 'definitions';
1703 if (file_exists($path)) {
1704 foreach (new DirectoryIterator($path) as $item) {
1705 $filename = $path . DIRECTORY_SEPARATOR . $item->getFileName();
1706 if (preg_match("/\.xml/", $filename)) {
1708 Tinebase_ImportExportDefinition::getInstance()->updateOrCreateFromFilename($filename, $_application);
1709 } catch (Exception $e) {
1710 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
1711 . ' Not installing import/export definion from file: ' . $filename
1712 . ' / Error message: ' . $e->getMessage());
1719 $this->_baseDir . $_application->name .
1720 DIRECTORY_SEPARATOR . $type . DIRECTORY_SEPARATOR . 'templates';
1722 if (file_exists($path)) {
1723 $fileSystem = Tinebase_FileSystem::getInstance();
1725 $basepath = $fileSystem->getApplicationBasePath(
1727 Tinebase_FileSystem::FOLDER_TYPE_SHARED
1728 ) . '/' . strtolower($type);
1730 if (false === $fileSystem->isDir($basepath)) {
1731 $fileSystem->createAclNode($basepath);
1734 $templateAppPath = Tinebase_Model_Tree_Node_Path::createFromPath($basepath . '/templates/' . $_application->name);
1736 if (! $fileSystem->isDir($templateAppPath->statpath)) {
1737 $fileSystem->mkdir($templateAppPath->statpath);
1740 foreach (new DirectoryIterator($path) as $item) {
1741 if (!$item->isFile()) {
1744 if (false === ($content = file_get_contents($item->getPathname()))) {
1745 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
1746 . ' Could not import template: ' . $item->getPathname());
1749 if (false === ($file = $fileSystem->fopen($templateAppPath->statpath . '/' . $item->getFileName(), 'w'))) {
1750 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
1751 . ' could not open ' . $templateAppPath->statpath . '/' . $item->getFileName() . ' for writting');
1754 fwrite($file, $content);
1755 if (true !== $fileSystem->fclose($file)) {
1756 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
1757 . ' write to ' . $templateAppPath->statpath . '/' . $item->getFileName() . ' did not succeed');
1768 * @param Tinebase_Model_Application $_application
1769 * @throws Setup_Exception
1771 protected function _uninstallApplication(Tinebase_Model_Application $_application, $uninstallAll = false)
1773 if ($this->_backend === null) {
1774 throw new Setup_Exception('No setup backend available');
1777 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Uninstall ' . $_application);
1779 $applicationTables = Tinebase_Application::getInstance()->getApplicationTables($_application);
1780 } catch (Zend_Db_Statement_Exception $zdse) {
1781 Setup_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . " " . $zdse);
1782 throw new Setup_Exception('Could not uninstall ' . $_application . ' (you might need to remove the tables by yourself): ' . $zdse->getMessage());
1784 $disabledFK = FALSE;
1785 $db = Tinebase_Core::getDb();
1788 $oldCount = count($applicationTables);
1790 if ($_application->name == 'Tinebase') {
1791 $installedApplications = Tinebase_Application::getInstance()->getApplications(NULL, 'id');
1792 if (count($installedApplications) !== 1) {
1793 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Installed apps: ' . print_r($installedApplications->name, true));
1794 throw new Setup_Exception_Dependency('Failed to uninstall application "Tinebase" because of dependencies to other installed applications.');
1798 foreach ($applicationTables as $key => $table) {
1799 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Remove table: $table");
1802 // drop foreign keys which point to current table first
1803 $foreignKeys = $this->_backend->getExistingForeignKeys($table);
1804 foreach ($foreignKeys as $foreignKey) {
1805 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .
1806 " Drop index: " . $foreignKey['table_name'] . ' => ' . $foreignKey['constraint_name']);
1807 $this->_backend->dropForeignKey($foreignKey['table_name'], $foreignKey['constraint_name']);
1811 $this->_backend->dropTable($table);
1813 if ($_application->name != 'Tinebase') {
1814 Tinebase_Application::getInstance()->removeApplicationTable($_application, $table);
1817 unset($applicationTables[$key]);
1819 } catch (Zend_Db_Statement_Exception $e) {
1820 // we need to catch exceptions here, as we don't want to break here, as a table
1821 // might still have some foreign keys
1822 // this works with mysql only
1823 $message = $e->getMessage();
1824 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " Could not drop table $table - " . $message);
1826 // remove app table if table not found in db
1827 if (preg_match('/SQLSTATE\[42S02\]: Base table or view not found/', $message) && $_application->name != 'Tinebase') {
1828 Tinebase_Application::getInstance()->removeApplicationTable($_application, $table);
1829 unset($applicationTables[$key]);
1831 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . " Disabling foreign key checks ... ");
1832 if ($db instanceof Zend_Db_Adapter_Pdo_Mysql) {
1833 $db->query("SET FOREIGN_KEY_CHECKS=0");
1840 if ($oldCount > 0 && count($applicationTables) == $oldCount) {
1841 throw new Setup_Exception('dead lock detected oldCount: ' . $oldCount);
1843 } while (count($applicationTables) > 0);
1846 if ($db instanceof Zend_Db_Adapter_Pdo_Mysql) {
1847 Setup_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . " Enabling foreign key checks again... ");
1848 $db->query("SET FOREIGN_KEY_CHECKS=1");
1852 if ($_application->name != 'Tinebase') {
1853 if (!$uninstallAll) {
1854 Tinebase_Relations::getInstance()->removeApplication($_application->name);
1856 Tinebase_Timemachine_ModificationLog::getInstance()->removeApplication($_application);
1858 // delete containers, config options and other data for app
1859 Tinebase_Application::getInstance()->removeApplicationAuxiliaryData($_application);
1862 // remove application from table of installed applications
1863 Tinebase_Application::getInstance()->deleteApplication($_application);
1866 Setup_Uninitialize::uninitialize($_application);
1868 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Removed app: " . $_application->name);
1872 * sort applications by checking dependencies
1874 * @param array $_applications
1877 protected function _sortInstallableApplications($_applications)
1881 // begin with Tinebase, Admin and Addressbook
1882 $alwaysOnTop = array('Tinebase', 'Admin', 'Addressbook');
1883 foreach ($alwaysOnTop as $app) {
1884 if (isset($_applications[$app])) {
1885 $result[$app] = $_applications[$app];
1886 unset($_applications[$app]);
1890 // get all apps to install ($name => $dependencies)
1891 $appsToSort = array();
1892 foreach($_applications as $name => $xml) {
1893 $depends = (array) $xml->depends;
1894 if (isset($depends['application'])) {
1895 if ($depends['application'] == 'Tinebase') {
1896 $appsToSort[$name] = array();
1899 $depends['application'] = (array) $depends['application'];
1901 foreach ($depends['application'] as $app) {
1902 // don't add tinebase (all apps depend on tinebase)
1903 if ($app != 'Tinebase') {
1904 $appsToSort[$name][] = $app;
1909 $appsToSort[$name] = array();
1913 //Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($appsToSort, true));
1917 while (count($appsToSort) > 0 && $count < MAXLOOPCOUNT) {
1919 foreach($appsToSort as $name => $depends) {
1921 if (empty($depends)) {
1922 // no dependencies left -> copy app to result set
1923 $result[$name] = $_applications[$name];
1924 unset($appsToSort[$name]);
1926 foreach ($depends as $key => $dependingAppName) {
1927 if (in_array($dependingAppName, array_keys($result)) || $this->isInstalled($dependingAppName)) {
1928 // remove from depending apps because it is already in result set
1929 unset($appsToSort[$name][$key]);
1937 if ($count == MAXLOOPCOUNT) {
1938 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ .
1939 " Some Applications could not be installed because of (cyclic?) dependencies: " . print_r(array_keys($appsToSort), TRUE));
1946 * sort applications by checking dependencies
1948 * @param array $_applications
1951 protected function _sortUninstallableApplications($_applications)
1955 // get all apps to uninstall ($name => $dependencies)
1956 $appsToSort = array();
1957 foreach($_applications as $name => $xml) {
1958 if ($name !== 'Tinebase') {
1959 $depends = $xml ? (array) $xml->depends : array();
1960 if (isset($depends['application'])) {
1961 if ($depends['application'] == 'Tinebase') {
1962 $appsToSort[$name] = array();
1965 $depends['application'] = (array) $depends['application'];
1967 foreach ($depends['application'] as $app) {
1968 // don't add tinebase (all apps depend on tinebase)
1969 if ($app != 'Tinebase') {
1970 $appsToSort[$name][] = $app;
1975 $appsToSort[$name] = array();
1982 while (count($appsToSort) > 0 && $count < MAXLOOPCOUNT) {
1984 foreach($appsToSort as $name => $depends) {
1985 //Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " - $count $name - " . print_r($depends, true));
1987 // don't uninstall if another app depends on this one
1988 $otherAppDepends = FALSE;
1989 foreach($appsToSort as $innerName => $innerDepends) {
1990 if(in_array($name, $innerDepends)) {
1991 $otherAppDepends = TRUE;
1996 // add it to results
1997 if (!$otherAppDepends) {
1998 $result[$name] = $_applications[$name];
1999 unset($appsToSort[$name]);
2005 if ($count == MAXLOOPCOUNT) {
2006 Setup_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ .
2007 " Some Applications could not be uninstalled because of (cyclic?) dependencies: " . print_r(array_keys($appsToSort), TRUE));
2010 // Tinebase is uninstalled last
2011 if (isset($_applications['Tinebase'])) {
2012 $result['Tinebase'] = $_applications['Tinebase'];
2013 unset($_applications['Tinebase']);
2020 * check if an application is installed
2022 * @param string $appname
2025 public function isInstalled($appname)
2028 $result = Tinebase_Application::getInstance()->isInstalled($appname);
2029 } catch (Exception $e) {
2030 Setup_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Application ' . $appname . ' is not installed.');
2031 Setup_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $e);
2043 protected function _clearCache()
2045 // setup cache (via tinebase because it is disabled in setup by default)
2046 Tinebase_Core::setupCache(TRUE);
2048 Setup_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Clearing cache ...');
2051 $cache = Setup_Core::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL);
2053 Tinebase_Application::getInstance()->resetClassCache();
2054 Tinebase_Cache_PerRequest::getInstance()->reset();
2056 // deactivate cache again
2057 Tinebase_Core::setupCache(FALSE);
2061 * returns TRUE if filesystem is available
2065 public function isFilesystemAvailable()
2067 if ($this->_isFileSystemAvailable === null) {
2069 $session = Tinebase_Session::getSessionNamespace();
2071 if (isset($session->filesystemAvailable)) {
2072 $this->_isFileSystemAvailable = $session->filesystemAvailable;
2074 return $this->_isFileSystemAvailable;
2076 } catch (Zend_Session_Exception $zse) {
2080 $this->_isFileSystemAvailable = (!empty(Tinebase_Core::getConfig()->filesdir) && is_writeable(Tinebase_Core::getConfig()->filesdir));
2082 if ($session instanceof Zend_Session_Namespace) {
2083 if (Tinebase_Session::isWritable()) {
2084 $session->filesystemAvailable = $this->_isFileSystemAvailable;
2088 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
2089 . ' Filesystem available: ' . ($this->_isFileSystemAvailable ? 'yes' : 'no'));
2092 return $this->_isFileSystemAvailable;
2098 * @param $options array(
2099 * 'backupDir' => string // where to store the backup
2100 * 'noTimestamp => bool // don't append timestamp to backup dir
2101 * 'config' => bool // backup config
2102 * 'db' => bool // backup database
2103 * 'files' => bool // backup files
2106 public function backup($options)
2108 $config = Setup_Core::getConfig();
2110 $backupDir = isset($options['backupDir']) ? $options['backupDir'] : $config->backupDir;
2112 throw new Exception('backupDir not configured');
2115 if (! isset($options['noTimestamp'])) {
2116 $backupDir .= '/' . date_create('now', new DateTimeZone('UTC'))->format('Y-m-d-H-i-s');
2119 if (!is_dir($backupDir) && !mkdir($backupDir, 0700, true)) {
2120 throw new Exception("$backupDir could not be created");
2123 if ($options['config']) {
2124 $configFile = stream_resolve_include_path('config.inc.php');
2125 $configDir = dirname($configFile);
2127 $files = file_exists("$configDir/index.php") ? 'config.inc.php' : '.';
2128 `cd $configDir; tar cjf $backupDir/tine20_config.tar.bz2 $files`;
2131 if ($options['db']) {
2132 if (! $this->_backend) {
2133 throw new Exception('db not configured, cannot backup');
2136 $backupOptions = array(
2137 'backupDir' => $backupDir,
2138 'structTables' => $this->_getBackupStructureOnlyTables(),
2141 $this->_backend->backup($backupOptions);
2144 $filesDir = isset($config->filesdir) ? $config->filesdir : false;
2145 if ($options['files'] && $filesDir) {
2146 `cd $filesDir; tar cjf $backupDir/tine20_files.tar.bz2 .`;
2151 * returns an array of all tables of all applications that should only backup the structure
2154 * @throws Setup_Exception_NotFound
2156 protected function _getBackupStructureOnlyTables()
2160 // find tables that only backup structure
2161 $applications = Tinebase_Application::getInstance()->getApplications();
2164 * @var $application Tinebase_Model_Application
2166 foreach($applications as $application) {
2167 $tableDef = $this->getSetupXml($application->name);
2168 $structOnlys = $tableDef->xpath('//table/backupStructureOnly[text()="true"]');
2170 foreach($structOnlys as $structOnly) {
2171 $tableName = $structOnly->xpath('./../name/text()');
2172 $tables[] = SQL_TABLE_PREFIX . $tableName[0];
2182 * @param $options array(
2183 * 'backupDir' => string // location of backup to restore
2184 * 'config' => bool // restore config
2185 * 'db' => bool // restore database
2186 * 'files' => bool // restore files
2190 * @throws Setup_Exception
2192 public function restore($options)
2194 if (! isset($options['backupDir'])) {
2195 throw new Setup_Exception("you need to specify the backupDir");
2198 if (isset($options['config']) && $options['config']) {
2199 $configBackupFile = $options['backupDir']. '/tine20_config.tar.bz2';
2200 if (! file_exists($configBackupFile)) {
2201 throw new Setup_Exception("$configBackupFile not found");
2204 $configDir = isset($options['configDir']) ? $options['configDir'] : false;
2206 $configFile = stream_resolve_include_path('config.inc.php');
2208 throw new Setup_Exception("can't detect configDir, please use configDir option");
2210 $configDir = dirname($configFile);
2213 `cd $configDir; tar xf $configBackupFile`;
2216 Setup_Core::setupConfig();
2217 $config = Setup_Core::getConfig();
2219 if (isset($options['db']) && $options['db']) {
2220 $this->_backend->restore($options['backupDir']);
2223 $filesDir = isset($config->filesdir) ? $config->filesdir : false;
2224 if (isset($options['files']) && $options['files']) {
2225 $dir = $options['backupDir'];
2226 $filesBackupFile = $dir . '/tine20_files.tar.bz2';
2227 if (! file_exists($filesBackupFile)) {
2228 throw new Setup_Exception("$filesBackupFile not found");
2231 `cd $filesDir; tar xf $filesBackupFile`;
2235 public function compareSchema($options)
2237 if (! isset($options['otherdb'])) {
2238 throw new Exception("you need to specify the otherdb");
2241 return Setup_SchemaTool::compareSchema($options['otherdb']);