.*?)' *, *\n* *')(?P.*?)(' *\) *;.*)/"; public $initPropel = false; public $initPropelRoot = false; /** * Create a workspace tools object. * Note that workspace might not exist when * this object is created, however most methods requires that a workspace with * this name does exists. * * @author Alexandre Rosenfeld * @access public * @param string $workspaceName name of the workspace */ public function __construct($workspaceName) { $this->name = $workspaceName; $this->path = PATH_DB . $this->name; $this->dbPath = $this->path . '/db.php'; if ($this->workspaceExists()) { $this->getDBInfo(); } } /** * Returns true if the workspace already exists * * @return bool */ public function workspaceExists() { return (file_exists($this->path) && file_exists($this->dbPath)); } /** * Upgrade this workspace to the latest system version * * @param bool $first true if this is the first workspace to be upgrade */ public function upgrade($first = false, $buildCacheView = false, $workSpace = SYS_SYS, $lang = 'en') { $start = microtime(true); CLI::logging("> Updating database...\n"); $this->upgradeDatabase(); $stop = microtime(true); $final = $stop - $start; CLI::logging("<*> Database Upgrade Process took $final seconds.\n"); $start = microtime(true); CLI::logging("> Updating translations...\n"); $this->upgradeTranslation($first); $stop = microtime(true); $final = $stop - $start; CLI::logging("<*> Updating Translations Process took $final seconds.\n"); $start = microtime(true); CLI::logging("> Updating Content...\n"); $this->upgradeContent($workSpace); $stop = microtime(true); $final = $stop - $start; CLI::logging("<*> Updating Content Process took $final seconds.\n"); $start = microtime(true); CLI::logging("> Updating cache view...\n"); $this->upgradeCacheView($buildCacheView, true, $lang); $stop = microtime(true); $final = $stop - $start; CLI::logging("<*> Updating cache view Process took $final seconds.\n"); $start = microtime(true); CLI::logging("> Updating cases directories structure...\n"); $this->upgradeCasesDirectoryStructure($workSpace); $stop = microtime(true); $final = $stop - $start; CLI::logging("<*> Database Upgrade Structure Process took $final seconds.\n"); } /** * Scan the db.php file for database information and return it as an array * * @return array with database information */ public function getDBInfo() { if (!$this->workspaceExists()) { throw new Exception("Could not get db.php in workspace " . $this->name); } if (isset($this->dbInfo)) { return $this->dbInfo; } $sDbFile = file_get_contents($this->dbPath); /* This regular expression will match any "define ('', '');" * with any combination of whitespace between words. * Each match will have these groups: * ((define('()2', ')1 ()3 (');)4 )0 */ preg_match_all($this->dbInfoRegExp, $sDbFile, $matches, PREG_SET_ORDER); $values = array(); foreach ($matches as $match) { $values[$match['key']] = $match['value']; } $this->dbAdapter = $values["DB_ADAPTER"]; $this->dbName = $values["DB_NAME"]; $this->dbHost = $values["DB_HOST"]; $this->dbUser = $values["DB_USER"]; $this->dbPass = $values["DB_PASS"]; return $this->dbInfo = $values; } private function resetDBInfoCallback($matches) { /* This function changes the values of defines while keeping their formatting * intact. * $matches will contain several groups: * ((define('()2', ')1 ()3 (');)4 )0 */ $dbPrefix = array('DB_NAME' => 'wf_', 'DB_USER' => 'wf_', 'DB_RBAC_NAME' => 'rb_', 'DB_RBAC_USER' => 'rb_', 'DB_REPORT_NAME' => 'rp_', 'DB_REPORT_USER' => 'rp_'); $key = isset($matches['key']) ? $matches['key'] : $matches[2]; $value = isset($matches['value']) ? $matches['value'] : $matches[3]; if (array_search($key, array('DB_HOST', 'DB_RBAC_HOST', 'DB_REPORT_HOST')) !== false) { /* Change the database hostname for these keys */ $value = $this->newHost; } elseif (array_key_exists($key, $dbPrefix)) { if ($this->resetDBNames) { /* Change the database name to the new workspace, following the standard * of prefix (either wf_, rp_, rb_) and the workspace name. */ $dbName = $dbPrefix[$key] . $this->name; } else { $dbName = $value; } $this->resetDBDiff[$value] = $dbName; $value = $dbName; } return $matches[1] . $value . $matches[4]; } /** * Reset the database information to that of a newly created workspace. * * This assumes this workspace already has a db.php file, which will be changed * to contain the new information. * This function will reset the database hostname to the system database. * If reseting database names, it will also use the the prefixes rp_, * rb_ and wf_, with the workspace name as database names. * * @param string $newHost the new hostname for the database * @param bool $resetDBNames if true, also reset all database names * @return array contains the new database names as values */ public function resetDBInfo($newHost, $resetDBNames = true) { if (count(explode(":", $newHost)) < 2) { $newHost .= ':3306'; } $this->newHost = $newHost; $this->resetDBNames = $resetDBNames; $this->resetDBDiff = array(); if (!$this->workspaceExists()) { throw new Exception("Could not find db.php in the workspace"); } $sDbFile = file_get_contents($this->dbPath); if ($sDbFile === false) { throw new Exception("Could not read database information from db.php"); } /* Match all defines in the config file. Check updateDBCallback to know what * keys are changed and what groups are matched. * This regular expression will match any "define ('', '');" * with any combination of whitespace between words. */ $sNewDbFile = preg_replace_callback("/( *define *\( *'(?P.*?)' *, *\n* *')(?P.*?)(' *\) *;.*)/", array(&$this, 'resetDBInfoCallback'), $sDbFile); if (file_put_contents($this->dbPath, $sNewDbFile) === false) { throw new Exception("Could not write database information to db.php"); } $newDBNames = $this->resetDBDiff; unset($this->resetDBDiff); unset($this->resetDBNames); //Clear the cached information about db.php unset($this->dbInfo); return $newDBNames; } /** * Get DB information for this workspace, such as hostname, username and password. * * @param string $dbName a db name, such as wf, rp and rb * @return array with all the database information. */ public function getDBCredentials($dbName) { $prefixes = array("wf" => "", "rp" => "REPORT_", "rb" => "RBAC_"); $prefix = $prefixes[$dbName]; $dbInfo = $this->getDBInfo(); return array('adapter' => $dbInfo["DB_ADAPTER"], 'name' => $dbInfo["DB_" . $prefix . "NAME"], 'host' => $dbInfo["DB_" . $prefix . "HOST"], 'user' => $dbInfo["DB_" . $prefix . "USER"], 'pass' => $dbInfo["DB_" . $prefix . "PASS"], 'dsn' => sprintf("%s://%s:%s@%s/%s?encoding=utf8", $dbInfo['DB_ADAPTER'], $dbInfo["DB_" . $prefix . "USER"], $dbInfo["DB_" . $prefix . "PASS"], $dbInfo["DB_" . $prefix . "HOST"], $dbInfo["DB_" . $prefix . "NAME"])); } /** * Initialize a Propel connection to the database * * @param bool $root wheter to also initialize a root connection * @return the Propel connection */ public function initPropel($root = false) { if (($this->initPropel && !$root) || ($this->initPropelRoot && $root)) { return; } $wfDetails = $this->getDBCredentials("wf"); $rbDetails = $this->getDBCredentials("rb"); $rpDetails = $this->getDBCredentials("rp"); $config = array('datasources' => array('workflow' => array('connection' => $wfDetails["dsn"], 'adapter' => $wfDetails["adapter"] ), 'rbac' => array('connection' => $rbDetails["dsn"], 'adapter' => $rbDetails["adapter"] ), 'rp' => array('connection' => $rpDetails["dsn"], 'adapter' => $rpDetails["adapter"] ) ) ); if ($root) { $dbHash = @explode(SYSTEM_HASH, G::decrypt(HASH_INSTALLATION, SYSTEM_HASH)); $dbInfo = $this->getDBInfo(); $host = $dbHash[0]; $user = $dbHash[1]; $pass = $dbHash[2]; $dbName = $dbInfo["DB_NAME"]; $rootConfig = array( 'datasources' => array('root' => array('connection' => "mysql://$user:$pass@$host/$dbName?encoding=utf8", 'adapter' => "mysql")) ); $config["datasources"] = array_merge($config["datasources"], $rootConfig["datasources"]); $this->initPropelRoot = true; } $this->initPropel = true; require_once ("propel/Propel.php"); require_once ("creole/Creole.php"); Propel::initConfiguration($config); } /** * Close the propel connection from initPropel */ private function closePropel() { Propel::close(); $this->initPropel = false; $this->initPropelRoot = false; } /** * Upgrade this workspace Content. */ public function upgradeContent($workSpace = SYS_SYS) { $this->initPropel(true); //require_once 'classes/model/Translation.php'; $translation = new Translation(); $information = $translation->getTranslationEnvironments(); $arrayLang = array(); foreach ($information as $key => $value) { $arrayLang[] = trim($value['LOCALE']); } //require_once ('classes/model/Content.php'); $regenerateContent = new Content(); $regenerateContent->regenerateContent($arrayLang, $workSpace); } /** * Upgrade this workspace translations from all avaliable languages. * * @param bool $first if updating a series of workspace, true if the first */ public function upgradeTranslation($first = true) { $this->initPropel(true); //require_once ('classes/model/Language.php'); G::LoadThirdParty('pear/json', 'class.json'); foreach (System::listPoFiles() as $poFile) { $poName = basename($poFile); $names = explode(".", basename($poFile)); $extension = array_pop($names); $langid = array_pop($names); CLI::logging("Updating database translations with $poName\n"); Language::import($poFile, false, true); if ($first) { CLI::logging("Updating XML form translations with $poName\n"); Language::import($poFile, true, false); } } } /** * Get a connection to this workspace wf database * * @return database connection */ private function getDatabase() { if (isset($this->db) && $this->db->isConnected()) { return $this->db; } G::LoadSystem('database_' . strtolower($this->dbAdapter)); $this->db = new database($this->dbAdapter, $this->dbHost, $this->dbUser, $this->dbPass, $this->dbName); if (!$this->db->isConnected()) { $this->db->logQuery('No available connection to database!'); throw new Exception("Could not connect to database"); } return $this->db; } /** * Close any database opened with getDatabase */ private function closeDatabase() { if (!isset($this->db)) { return; } $this->db->close(); $this->db = null; } /** * Close all currently opened databases */ public function close() { $this->closePropel(); $this->closeDatabase(); } /** * Get the current workspace database schema * * @return array with the database schema */ public function getSchema() { $oDataBase = $this->getDatabase(); $aOldSchema = array(); try { $oDataBase->iFetchType = MYSQL_NUM; $oDataset1 = $oDataBase->executeQuery($oDataBase->generateShowTablesSQL()); } catch (Exception $e) { $oDataBase->logQuery($e->getmessage()); return null; } //going thru all tables in current WF_ database while ($aRow1 = $oDataBase->getRegistry($oDataset1)) { $aPrimaryKeys = array(); $sTable = strtoupper($aRow1[0]); //get description of each table, ( column and primary keys ) //$oDataset2 = $oDataBase->executeQuery( $oDataBase->generateDescTableSQL($aRow1[0]) ); $oDataset2 = $oDataBase->executeQuery($oDataBase->generateDescTableSQL($sTable)); $aOldSchema[$sTable] = array(); $oDataBase->iFetchType = MYSQL_ASSOC; while ($aRow2 = $oDataBase->getRegistry($oDataset2)) { $aOldSchema[$sTable][$aRow2['Field']]['Field'] = $aRow2['Field']; $aOldSchema[$sTable][$aRow2['Field']]['Type'] = $aRow2['Type']; $aOldSchema[$sTable][$aRow2['Field']]['Null'] = $aRow2['Null']; $aOldSchema[$sTable][$aRow2['Field']]['Default'] = $aRow2['Default']; } //get indexes of each table SHOW INDEX FROM `ADDITIONAL_TABLES`; -- WHERE Key_name <> 'PRIMARY' $oDataset2 = $oDataBase->executeQuery($oDataBase->generateTableIndexSQL($aRow1[0])); $oDataBase->iFetchType = MYSQL_ASSOC; while ($aRow2 = $oDataBase->getRegistry($oDataset2)) { if (!isset($aOldSchema[$sTable]['INDEXES'])) { $aOldSchema[$sTable]['INDEXES'] = array(); } if (!isset($aOldSchema[$sTable]['INDEXES'][$aRow2['Key_name']])) { $aOldSchema[$sTable]['INDEXES'][$aRow2['Key_name']] = array(); } $aOldSchema[$sTable]['INDEXES'][$aRow2['Key_name']][] = $aRow2['Column_name']; } $oDataBase->iFetchType = MYSQL_NUM; //this line is neccesary because the next fetch needs to be with MYSQL_NUM } //finally return the array with old schema obtained from the Database if (count($aOldSchema) == 0) { $aOldSchema = null; } return $aOldSchema; } /** * Upgrade the AppCacheView table to the latest system version. * * This recreates the table and populates with data. * * @param bool $checkOnly only check if the upgrade is needed if true * @param string $lang not currently used */ public function upgradeCacheView($fill = true, $checkOnly = false, $lang = "en") { $this->initPropel(true); //require_once ('classes/model/AppCacheView.php'); //check the language, if no info in config about language, the default is 'en' G::LoadClass("configuration"); $oConf = new Configurations(); $oConf->loadConfig($x, 'APP_CACHE_VIEW_ENGINE', '', '', '', ''); $appCacheViewEngine = $oConf->aConfig; //setup the appcacheview object, and the path for the sql files $appCache = new AppCacheView(); $appCache->setPathToAppCacheFiles(PATH_METHODS . 'setup' . PATH_SEP . 'setupSchemas' . PATH_SEP); $userGrants = $appCache->checkGrantsForUser(false); $currentUser = $userGrants['user']; $currentUserIsSuper = $userGrants['super']; //if user does not have the SUPER privilege we need to use the root user and grant the SUPER priv. to normal user. if (!$currentUserIsSuper) { $appCache->checkGrantsForUser(true); $appCache->setSuperForUser($currentUser); $currentUserIsSuper = true; } CLI::logging("-> Creating table\n"); //now check if table APPCACHEVIEW exists, and it have correct number of fields, etc. $res = $appCache->checkAppCacheView(); CLI::logging("-> Update DEL_LAST_INDEX field in APP_DELEGATION table\n"); //Update APP_DELEGATION.DEL_LAST_INDEX data $res = $appCache->updateAppDelegationDelLastIndex($lang, $checkOnly); CLI::logging("-> Verifying roles permissions in RBAC \n"); //Update table RBAC permissions Bootstrap::LoadSystem( 'rbac' ); $RBAC = & RBAC::getSingleton(); $RBAC->initRBAC(); $result = $RBAC->verifyPermissions(); if (count($result) > 1) { foreach($result as $item) { CLI::logging(" $item... \n"); } } else { CLI::logging(" All roles permissions already updated \n"); } CLI::logging("-> Creating triggers\n"); //now check if we have the triggers installed $triggers = array(); $triggers[] = $appCache->triggerAppDelegationInsert($lang, $checkOnly); $triggers[] = $appCache->triggerAppDelegationUpdate($lang, $checkOnly); $triggers[] = $appCache->triggerApplicationUpdate($lang, $checkOnly); $triggers[] = $appCache->triggerApplicationDelete($lang, $checkOnly); $triggers[] = $appCache->triggerContentUpdate($lang, $checkOnly); if ($fill) { CLI::logging("-> Rebuild Cache View with language $lang...\n"); //build using the method in AppCacheView Class $res = $appCache->fillAppCacheView($lang); //set status in config table $confParams = Array('LANG' => $lang, 'STATUS' => 'active'); } $oConf->aConfig = $confParams; $oConf->saveConfig('APP_CACHE_VIEW_ENGINE', '', '', ''); // removing casesList configuration records. TODO: removing these lines that resets all the configurations records $oCriteria = new Criteria(); $oCriteria->add(ConfigurationPeer::CFG_UID, "casesList"); $oCriteria->add(ConfigurationPeer::OBJ_UID, array("todo", "draft", "sent", "unassigned", "paused", "cancelled"), Criteria::NOT_IN); ConfigurationPeer::doDelete($oCriteria); // end of reset } /** * fix the 32K issue, by migrating /files directory structure to an uid tree structure based. * @param $workspace got the site(s) the manager wants to upgrade */ public function upgradeCasesDirectoryStructure ($workspace) { define('PATH_DOCUMENT', PATH_DATA . 'sites/' . $workspace . '/' . 'files/'); $doclevel = explode('/', PATH_DOCUMENT); $length = sizeof(PATH_DOCUMENT); $filesDir = $doclevel[$length - 1]; if (is_dir(PATH_DOCUMENT) && is_writable($filesDir)) { CLI::logging(CLI::error("Error:" . PATH_DOCUMENT . " is not writable... please check the su permissions.\n")); return; } $directory = array(); $blackHoleDir = G::getBlackHoleDir(); $directory = glob(PATH_DOCUMENT . "*", GLOB_ONLYDIR); $dirslength = sizeof($directory); if (! @chdir(PATH_DOCUMENT)) { CLI::logging(CLI::error("Cannot use Document directory. The upgrade must be done as root.\n")); return; } //Start migration for ($index = 0; $index < $dirslength; $index++) { $depthdirlevel = explode('/', $directory[$index]); $lastlength = sizeof($depthdirlevel); $UIdDir = $depthdirlevel[$lastlength - 1]; $lenDir = strlen($UIdDir); if ($lenDir == 32 && $UIdDir != $blackHoleDir) { $len = count(scandir($UIdDir)); if ($len > 2) { //lenght = 2, because the function check . and .. dir links $newDiretory = G::getPathFromUIDPlain($UIdDir); CLI::logging("Migrating $UIdDir to $newDiretory\n"); G::mk_dir($newDiretory); //echo `cp -R $UIdDir/* $newDiretory/`; if (G::recursive_copy($UIdDir, $newDiretory)) { CLI::logging("Removing $UIdDir...\n"); G::rm_dir($UIdDir); rmdir($UIdDir);//remove the diretory itself, G::rm_dir cannot do it } else { CLI::logging(CLI::error("Error: Failure at coping from $UIdDir...\n")); } } else { CLI::logging("$UIdDir is empty, removing it\n"); rmdir($UIdDir);//remove the diretory itself } } } //Start '0' directory migration $black = PATH_DOCUMENT . $blackHoleDir . '/'; if (is_dir($black)) { $newpattern = array(); $file = glob($black . '*.*');//files only $dirlen = count($file); for ($index = 0; $index < $dirlen; $index++) { $levelfile = explode('/', $file[$index]); $lastlevel = sizeof($levelfile); $goalFile = $levelfile[$lastlevel - 1]; $newpattern = G::getPathFromFileUIDPlain($blackHoleDir, $goalFile); CLI::logging("Migrating $blackHoleDir file: $goalFile\n"); G::mk_dir($blackHoleDir . '/' . $newpattern[0]); //echo `cp -R $black$goalFile $black$newpattern[0]/$newpattern[1]`; if (copy($black . $goalFile, $black . $newpattern[0] . '/' . $newpattern[1])) { unlink($file[$index]); } else { CLI::logging(CLI::error("Error: Failure at copy $file[$index] files...\n")); } } } //Set value of 2 to the directory structure version. $this->initPropel(true); G::LoadClass("configuration"); $conf = new Configurations(); if ($conf->exists("ENVIRONMENT_SETTINGS")) { $conf->setDirectoryStructureVer(2); CLI::logging(CLI::info("Version Directory Structure is 2 now.\n")); } else { CLI::logging(CLI::error("Error: found at try to use ENVIRONMENT_SETTINGS row.\n")); return; } } /** * Upgrade this workspace database to the latest plugins schema */ public function upgradePluginsDatabase() { foreach (System::getPlugins() as $pluginName) { $pluginSchema = System::getPluginSchema($pluginName); if ($pluginSchema !== false) { CLI::logging("Updating plugin " . CLI::info($pluginName) . "\n"); $this->upgradeSchema($pluginSchema); } } } /** * Upgrade this workspace database to the latest system schema * * @param bool $checkOnly only check if the upgrade is needed if true * @return array bool upgradeSchema for more information */ public function upgradeDatabase ($checkOnly = false) { G::LoadClass("patch"); $this->initPropel( true ); p11835::isApplicable(); $systemSchema = System::getSystemSchema(); $this->upgradeSchema( $systemSchema ); $this->upgradeData(); p11835::execute(); return true; } /** * Upgrade this workspace database from a schema * * @param array $schema the schema information, such as returned from getSystemSchema * @param bool $checkOnly only check if the upgrade is needed if true * @return array bool the changes if checkOnly is true, else return * true on success */ public function upgradeSchema($schema, $checkOnly = false) { $dbInfo = $this->getDBInfo(); if (strcmp($dbInfo["DB_ADAPTER"], "mysql") != 0) { throw new Exception("Only MySQL is supported"); } $workspaceSchema = $this->getSchema(); $changes = System::compareSchema($workspaceSchema, $schema); $changed = (count($changes['tablesToAdd']) > 0 || count($changes['tablesToAlter']) > 0 || count($changes['tablesWithNewIndex']) > 0 || count($changes['tablesToAlterIndex']) > 0); if ($checkOnly || (!$changed)) { if ($changed) { return $changes; } else { CLI::logging("-> Nothing to change in the data base structure\n"); return $changed; } } $oDataBase = $this->getDatabase(); $oDataBase->iFetchType = MYSQL_NUM; $oDataBase->logQuery(count($changes)); if (!empty($changes['tablesToAdd'])) { CLI::logging("-> " . count($changes['tablesToAdd']) . " tables to add\n"); } foreach ($changes['tablesToAdd'] as $sTable => $aColumns) { $oDataBase->executeQuery($oDataBase->generateCreateTableSQL($sTable, $aColumns)); if (isset($changes['tablesToAdd'][$sTable]['INDEXES'])) { foreach ($changes['tablesToAdd'][$sTable]['INDEXES'] as $indexName => $aIndex) { $oDataBase->executeQuery($oDataBase->generateAddKeysSQL($sTable, $indexName, $aIndex)); } } } if (!empty($changes['tablesToAlter'])) { CLI::logging("-> " . count($changes['tablesToAlter']) . " tables to alter\n"); } foreach ($changes['tablesToAlter'] as $sTable => $aActions) { foreach ($aActions as $sAction => $aAction) { foreach ($aAction as $sColumn => $vData) { switch ($sAction) { case 'DROP': $oDataBase->executeQuery($oDataBase->generateDropColumnSQL($sTable, $vData)); break; case 'ADD': $oDataBase->executeQuery($oDataBase->generateAddColumnSQL($sTable, $sColumn, $vData)); break; case 'CHANGE': $oDataBase->executeQuery($oDataBase->generateChangeColumnSQL($sTable, $sColumn, $vData)); break; } } } } if (!empty($changes['tablesWithNewIndex'])) { CLI::logging("-> " . count($changes['tablesWithNewIndex']) . " indexes to add\n"); } foreach ($changes['tablesWithNewIndex'] as $sTable => $aIndexes) { foreach ($aIndexes as $sIndexName => $aIndexFields) { $oDataBase->executeQuery($oDataBase->generateAddKeysSQL($sTable, $sIndexName, $aIndexFields)); } } if (!empty($changes['tablesToAlterIndex'])) { CLI::logging("-> " . count($changes['tablesToAlterIndex']) . " indexes to alter\n"); } foreach ($changes['tablesToAlterIndex'] as $sTable => $aIndexes) { foreach ($aIndexes as $sIndexName => $aIndexFields) { $oDataBase->executeQuery($oDataBase->generateDropKeySQL($sTable, $sIndexName)); $oDataBase->executeQuery($oDataBase->generateAddKeysSQL($sTable, $sIndexName, $aIndexFields)); } } $this->closeDatabase(); return true; } public function upgradeData() { if (file_exists(PATH_CORE . 'data' . PATH_SEP . 'check.data')) { $checkData = unserialize(file_get_contents(PATH_CORE . 'data' . PATH_SEP . 'check.data')); if (is_array($checkData)) { foreach ($checkData as $checkThis) { $this->updateThisRegistry($checkThis); } } } } public function updateThisRegistry($data) { $dataBase = $this->getDatabase(); $sql = ''; switch ($data['action']) { case 1: $sql = $dataBase->generateInsertSQL($data['table'], $data['data']); $message = "-> Row added in {$data['table']}\n"; break; case 2: $sql = $dataBase->generateUpdateSQL($data['table'], $data['keys'], $data['data']); $message = "-> Row updated in {$data['table']}\n"; break; case 3: $sql = $dataBase->generateDeleteSQL($data['table'], $data['keys'], $data['data']); $message = "-> Row deleted in {$data['table']}\n"; break; case 4: $sql = $dataBase->generateSelectSQL($data['table'], $data['keys'], $data['data']); $dataset = $dataBase->executeQuery($sql); if ($dataBase->getRegistry($dataset)) { $sql = $dataBase->generateDeleteSQL($data['table'], $data['keys'], $data['data']); $dataBase->executeQuery($sql); } $sql = $dataBase->generateInsertSQL($data['table'], $data['data']); $message = "-> Row updated in {$data['table']}\n"; break; } if ($sql != '') { $dataBase->executeQuery($sql); CLI::logging($message); } } /** * Get metadata from this workspace * * @param string $path the directory where to create the sql files * @return array information about this workspace */ public function getMetadata() { $Fields = array_merge(System::getSysInfo(), $this->getDBInfo()); $Fields['WORKSPACE_NAME'] = $this->name; if (isset($this->dbHost)) { //TODO: This code stopped working with the refactoring //require_once ("propel/Propel.php"); //G::LoadClass('dbConnections'); //$dbConns = new dbConnections(''); //$availdb = ''; //foreach( $dbConns->getDbServicesAvailables() as $key => $val ) { //if(!empty($availdb)) // $availdb .= ', '; // $availdb .= $val['name']; //} G::LoadClass('net'); $dbNetView = new NET($this->dbHost); $dbNetView->loginDbServer($this->dbUser, $this->dbPass); try { $sMySQLVersion = $dbNetView->getDbServerVersion('mysql'); } catch (Exception $oException) { $sMySQLVersion = 'Unknown'; } $Fields['DATABASE'] = $dbNetView->dbName($this->dbAdapter) . ' (Version ' . $sMySQLVersion . ')'; $Fields['DATABASE_SERVER'] = $this->dbHost; $Fields['DATABASE_NAME'] = $this->dbName; $Fields['AVAILABLE_DB'] = "Not defined"; //$Fields['AVAILABLE_DB'] = $availdb; } else { $Fields['DATABASE'] = "Not defined"; $Fields['DATABASE_SERVER'] = "Not defined"; $Fields['DATABASE_NAME'] = "Not defined"; $Fields['AVAILABLE_DB'] = "Not defined"; } return $Fields; } /** * Print the system information gathered from getSysInfo */ public static function printSysInfo() { $fields = System::getSysInfo(); $info = array( 'ProcessMaker Version' => $fields['PM_VERSION'], 'System' => $fields['SYSTEM'], 'PHP Version' => $fields['PHP'], 'Server Address' => $fields['SERVER_ADDR'], 'Client IP Address' => $fields['IP'], 'Plugins' => (count($fields['PLUGINS_LIST']) > 0) ? $fields['PLUGINS_LIST'][0] : 'None' ); foreach ($fields['PLUGINS_LIST'] as $k => $v) { if ($k == 0) { continue; } $info[] = $v; } foreach ($info as $k => $v) { if (is_numeric($k)) { $k = ""; } CLI::logging(sprintf("%20s %s\n", $k, pakeColor::colorize($v, 'INFO'))); } } public function printInfo($fields = null) { if (!$fields) { $fields = $this->getMetadata(); } $wfDsn = $fields['DB_ADAPTER'] . '://' . $fields['DB_USER'] . ':' . $fields['DB_PASS'] . '@' . $fields['DB_HOST'] . '/' . $fields['DB_NAME']; $rbDsn = $fields['DB_ADAPTER'] . '://' . $fields['DB_RBAC_USER'] . ':' . $fields['DB_RBAC_PASS'] . '@' . $fields['DB_RBAC_HOST'] . '/' . $fields['DB_RBAC_NAME']; $rpDsn = $fields['DB_ADAPTER'] . '://' . $fields['DB_REPORT_USER'] . ':' . $fields['DB_REPORT_PASS'] . '@' . $fields['DB_REPORT_HOST'] . '/' . $fields['DB_REPORT_NAME']; $info = array('Workspace Name' => $fields['WORKSPACE_NAME'], //'Available Databases' => $fields['AVAILABLE_DB'], 'Workflow Database' => sprintf("%s://%s:%s@%s/%s", $fields['DB_ADAPTER'], $fields['DB_USER'], $fields['DB_PASS'], $fields['DB_HOST'], $fields['DB_NAME']), 'RBAC Database' => sprintf("%s://%s:%s@%s/%s", $fields['DB_ADAPTER'], $fields['DB_RBAC_USER'], $fields['DB_RBAC_PASS'], $fields['DB_RBAC_HOST'], $fields['DB_RBAC_NAME']), 'Report Database' => sprintf("%s://%s:%s@%s/%s", $fields['DB_ADAPTER'], $fields['DB_REPORT_USER'], $fields['DB_REPORT_PASS'], $fields['DB_REPORT_HOST'], $fields['DB_REPORT_NAME']), 'MySql Version' => $fields['DATABASE'] ); foreach ($info as $k => $v) { if (is_numeric($k)) { $k = ""; } CLI::logging(sprintf("%20s %s\n", $k, pakeColor::colorize($v, 'INFO'))); } } /** * Print workspace information * * @param bool $printSysInfo include sys info as well or not */ public function printMetadata($printSysInfo = true) { if ($printSysInfo) { workspaceTools::printSysInfo(); CLI::logging("\n"); } workspaceTools::printInfo($this->getMetadata()); } /** * exports this workspace database to the specified path * * This function is used mainly for backup purposes. * * @param string $path the directory where to create the sql files */ public function exportDatabase($path) { $dbInfo = $this->getDBInfo(); $databases = array("wf", "rp", "rb"); $dbNames = array(); foreach ($databases as $db) { $dbInfo = $this->getDBCredentials($db); $oDbMaintainer = new DataBaseMaintenance($dbInfo["host"], $dbInfo["user"], $dbInfo["pass"]); CLI::logging("Saving database {$dbInfo["name"]}\n"); $oDbMaintainer->connect($dbInfo["name"]); $oDbMaintainer->lockTables(); $oDbMaintainer->setTempDir($path . "/"); $oDbMaintainer->backupDataBase($oDbMaintainer->getTempDir() . $dbInfo["name"] . ".sql"); $oDbMaintainer->unlockTables(); $dbNames[] = $dbInfo; } return $dbNames; } /** * adds files to the backup archive */ private function addToBackup($backup, $filename, $pathRoot, $archiveRoot = "") { if (is_file($filename)) { CLI::logging("-> $filename\n"); $backup->addModify($filename, $archiveRoot, $pathRoot); } else { CLI::logging(" + $filename\n"); $backup->addModify($filename, $archiveRoot, $pathRoot); //foreach (glob($filename . "/*") as $item) { // $this->addToBackup($backup, $item, $pathRoot, $archiveRoot); //} } } /** * Creates a backup archive, which can be used instead of a filename to backup * * @param string $filename the backup filename * @param bool $compress wheter to compress or not */ static public function createBackup($filename, $compress = true) { G::LoadThirdParty('pear/Archive', 'Tar'); if (!file_exists(dirname($filename))) { mkdir(dirname($filename)); } if (file_exists($filename)) { unlink($filename); } $backup = new Archive_Tar($filename); return $backup; } /** * create a backup of this workspace * * Exports the database and copies the files to an archive specified, so this * workspace can later be restored. * * @param string|archive $filename archive filename to use as backup or * archive object created by createBackup * @param bool $compress specifies wheter the backup is compressed or not */ public function backup($backupFile, $compress = true) { /* $filename can be a string, in which case it's used as the filename of * the backup, or it can be a previously created tar, which allows for * multiple workspaces in one backup. */ if (!$this->workspaceExists()) { throw new Exception("Workspace '{$this->name}' not found"); } if (is_string($backupFile)) { $backup = $this->createBackup($backupFile); $filename = $backupFile; } else { $backup = $backupFile; $filename = $backup->_tarname; } if (!file_exists(PATH_DATA . "upgrade/")) { mkdir(PATH_DATA . "upgrade/"); } $tempDirectory = PATH_DATA . "upgrade/" . basename(tempnam(__FILE__, '')); mkdir($tempDirectory); $metadata = $this->getMetadata(); CLI::logging("Backing up database...\n"); $metadata["databases"] = $this->exportDatabase($tempDirectory); $metadata["directories"] = array("{$this->name}.files"); $metadata["version"] = 1; $metaFilename = "$tempDirectory/{$this->name}.meta"; /* Write metadata to file, but make it prettier before. The metadata is just * a JSON codified array. */ if (!file_put_contents($metaFilename, str_replace(array(",", "{", "}" ), array(",\n ", "{\n ", "\n}\n" ), G::json_encode($metadata)))) { throw new Exception("Could not create backup metadata"); } CLI::logging("Copying database to backup...\n"); $this->addToBackup($backup, $tempDirectory, $tempDirectory); CLI::logging("Copying files to backup...\n"); $this->addToBackup($backup, $this->path, $this->path, "{$this->name}.files"); //Remove leftovers. G::rm_dir($tempDirectory); } //TODO: Move to class.dbMaintenance.php /** * create a user in the database * * Create a user specified by the parameters and grant all priviledges for * the database specified, when the user connects from the hostname. * Drops the user if it already exists. * This function only supports MySQL. * * @param string $username username * @param string $password password * @param string $hostname the hostname the user will be connecting from * @param string $database the database to grant permissions */ private function createDBUser($username, $password, $hostname, $database) { mysql_select_db("mysql"); $hostname = array_shift(explode(":", $hostname)); $sqlstmt = "SELECT * FROM user WHERE user = '$username' AND host = '$hostname'"; $result = mysql_query($sqlstmt); if ($result === false) { throw new Exception("Unable to retrieve users: " . mysql_error()); } $users = mysql_num_rows($result); if ($users != 0) { $result = mysql_query("DROP USER '$username'@'$hostname'"); if ($result === false) { throw new Exception("Unable to drop user: " . mysql_error()); } } CLI::logging("Creating user $username for $hostname\n"); $result = mysql_query("CREATE USER '$username'@'$hostname' IDENTIFIED BY '$password'"); if ($result === false) { throw new Exception("Unable to create user $username: " . mysql_error()); } $result = mysql_query("GRANT ALL ON $database.* TO '$username'@'$hostname'"); if ($result === false) { throw new Exception("Unable to grant priviledges to user $username: " . mysql_error()); } } //TODO: Move to class.dbMaintenance.php /** * executes a mysql script * * This function supports scripts with -- comments in the beginning of a line * and multi-line statements. * It does not support other forms of comments (such as /*... or {{...}}). * * @param string $filename the script filename * @param string $database the database to execute this script into */ private function executeSQLScript($database, $filename) { mysql_query("CREATE DATABASE IF NOT EXISTS " . mysql_real_escape_string($database)); mysql_select_db($database); $script = file_get_contents($filename); $lines = explode("\n", $script); $previous = null; foreach ($lines as $j => $line) { // Remove comments from the script $line = trim($line); if (strpos($line, "--") === 0) { $line = substr($line, 0, strpos($line, "--")); } if (empty($line)) { continue; } // Concatenate the previous line, if any, with the current if ($previous) { $line = $previous . " " . $line; } $previous = null; // If the current line doesnt end with ; then put this line together // with the next one, thus supporting multi-line statements. if (strrpos($line, ";") != strlen($line) - 1) { $previous = $line; continue; } $line = substr($line, 0, strrpos($line, ";")); $result = mysql_query($line); if ($result === false) { throw new Exception("Error when running script '$filename', line $j, query '$line': " . mysql_error()); } } } static public function restoreLegacy($directory) { throw new Exception("Use gulliver to restore backups from old versions"); } static public function getBackupInfo($filename) { G::LoadThirdParty('pear/Archive', 'Tar'); $backup = new Archive_Tar($filename); //Get a temporary directory in the upgrade directory $tempDirectory = PATH_DATA . "upgrade/" . basename(tempnam(__FILE__, '')); mkdir($tempDirectory); $metafiles = array(); foreach ($backup->listContent() as $backupFile) { $filename = $backupFile["filename"]; if (strpos($filename, "/") === false && substr_compare($filename, ".meta", - 5, 5, true) === 0) { if (!$backup->extractList(array($filename), $tempDirectory)) { throw new Exception("Could not extract backup"); } $metafiles[] = "$tempDirectory/$filename"; } } CLI::logging("Found " . count($metafiles) . " workspace(s) in backup\n"); foreach ($metafiles as $metafile) { $data = file_get_contents($metafile); $workspaceData = G::json_decode($data); CLI::logging("\n"); workspaceTools::printInfo((array) $workspaceData); } G::rm_dir($tempDirectory); } static public function dirPerms($filename, $owner, $group, $perms) { $chown = @chown($filename, $owner); $chgrp = @chgrp($filename, $group); $chmod = @chmod($filename, $perms); if ($chgrp === false || $chmod === false || $chown === false) { CLI::logging(CLI::error("Failed to set permissions for $filename") . "\n"); } if (is_dir($filename)) { foreach (array_merge(glob($filename . "/*"), glob($filename . "/.*")) as $item) { if (basename($item) == "." || basename($item) == "..") { continue; } workspaceTools::dirPerms($item, $owner, $group, $perms); } } } /** * restore an archive into a workspace * * Restores any database and files included in the backup, either as a new * workspace, or overwriting a previous one * * @param string $filename the backup filename * @param string $newWorkspaceName if defined, supplies the name for the * workspace to restore to */ static public function restore($filename, $srcWorkspace, $dstWorkspace = null, $overwrite = true, $lang = 'en') { G::LoadThirdParty('pear/Archive', 'Tar'); $backup = new Archive_Tar($filename); //Get a temporary directory in the upgrade directory $tempDirectory = PATH_DATA . "upgrade/" . basename(tempnam(__FILE__, '')); $parentDirectory = PATH_DATA . "upgrade"; if (is_writable($parentDirectory)) { mkdir($tempDirectory); } else { throw new Exception("Could not create directory:" . $parentDirectory); } //Extract all backup files, including database scripts and workspace files if (!$backup->extract($tempDirectory)) { throw new Exception("Could not extract backup"); } //Search for metafiles in the new standard (the old standard would contain //txt files). $metaFiles = glob($tempDirectory . "/*.meta"); if (empty($metaFiles)) { $metaFiles = glob($tempDirectory . "/*.txt"); if (!empty($metaFiles)) { return workspaceTools::restoreLegacy($tempDirectory); } else { throw new Exception("No metadata found in backup"); } } else { CLI::logging("Found " . count($metaFiles) . " workspaces in backup:\n"); foreach ($metaFiles as $metafile) { CLI::logging("-> " . basename($metafile) . "\n"); } } if (count($metaFiles) > 1 && (!isset($srcWorkspace))) { throw new Exception("Multiple workspaces in backup but no workspace specified to restore"); } if (isset($srcWorkspace) && !in_array("$srcWorkspace.meta", array_map(BASENAME, $metaFiles))) { throw new Exception("Workspace $srcWorkspace not found in backup"); } $version = System::getVersion(); $version = explode('-', $version); $versionPresent = ( isset($version[0])) ? $version[0] : ''; CLI::logging(CLI::warning(" Note.- If you try to execute a restore from a generated backup on a recent version of Processmaker than version you are using currently to restore it, it may be occur errors on the restore process, it shouldn't be restaured generated backups on later versions than version when the restore is executed") . "\n"); foreach ($metaFiles as $metaFile) { $metadata = G::json_decode(file_get_contents($metaFile)); if ($metadata->version != 1) { throw new Exception("Backup version {$metadata->version} not supported"); } $backupWorkspace = $metadata->WORKSPACE_NAME; if (isset($dstWorkspace)) { $workspaceName = $dstWorkspace; $createWorkspace = true; } else { $workspaceName = $metadata->WORKSPACE_NAME; $createWorkspace = false; } if (isset($srcWorkspace) && strcmp($metadata->WORKSPACE_NAME, $srcWorkspace) != 0) { CLI::logging(CLI::warning("> Workspace $backupWorkspace found, but not restoring.") . "\n"); continue; } else { CLI::logging("> Restoring " . CLI::info($backupWorkspace) . " to " . CLI::info($workspaceName) . "\n"); } $workspace = new workspaceTools($workspaceName); if ($workspace->workspaceExists()) { if ($overwrite) { CLI::logging(CLI::warning("> Workspace $workspaceName already exist, overwriting!") . "\n"); } else { throw new Exception("Destination workspace already exist (use -o to overwrite)"); } } if (file_exists($workspace->path)) { G::rm_dir($workspace->path); } foreach ($metadata->directories as $dir) { CLI::logging("+> Restoring directory '$dir'\n"); if(file_exists("$tempDirectory/$dir" . "/ee")) { G::rm_dir("$tempDirectory/$dir" . "/ee"); } if(file_exists("$tempDirectory/$dir" . "/plugin.singleton")) { G::rm_dir("$tempDirectory/$dir" . "/plugin.singleton"); } if (!rename("$tempDirectory/$dir", $workspace->path)) { throw new Exception("There was an error copying the backup files ($tempDirectory/$dir) to the workspace directory {$workspace->path}."); } } CLI::logging("> Changing file permissions\n"); $shared_stat = stat(PATH_DATA); if ($shared_stat !== false) { workspaceTools::dirPerms($workspace->path, $shared_stat['uid'], $shared_stat['gid'], $shared_stat['mode']); } else { CLI::logging(CLI::error("Could not get the shared folder permissions, not changing workspace permissions") . "\n"); } list ($dbHost, $dbUser, $dbPass) = @explode(SYSTEM_HASH, G::decrypt(HASH_INSTALLATION, SYSTEM_HASH)); CLI::logging("> Connecting to system database in '$dbHost'\n"); $link = mysql_connect($dbHost, $dbUser, $dbPass); @mysql_query("SET NAMES 'utf8';"); @mysql_query("SET FOREIGN_KEY_CHECKS=0;"); if (!$link) { throw new Exception('Could not connect to system database: ' . mysql_error()); } $newDBNames = $workspace->resetDBInfo($dbHost, $createWorkspace); foreach ($metadata->databases as $db) { $dbName = $newDBNames[$db->name]; CLI::logging("+> Restoring database {$db->name} to $dbName\n"); $workspace->executeSQLScript($dbName, "$tempDirectory/{$db->name}.sql"); $workspace->createDBUser($dbName, $db->pass, "localhost", $dbName); $workspace->createDBUser($dbName, $db->pass, "%", $dbName); } $version = explode('-', $metadata->PM_VERSION); $versionOld = ( isset($version[0])) ? $version[0] : ''; CLI::logging(CLI::info("$versionOld < $versionPresent") . "\n"); if ( $versionOld < $versionPresent) { $start = microtime(true); CLI::logging("> Updating database...\n"); $workspace->upgradeDatabase(); $stop = microtime(true); $final = $stop - $start; CLI::logging("<*> Database Upgrade Process took $final seconds.\n"); $start = microtime(true); CLI::logging("> Updating cases directories structure...\n"); $workspace->upgradeCasesDirectoryStructure($workspaceName); $stop = microtime(true); $final = $stop - $start; CLI::logging("<*> Database Upgrade Structure Process took $final seconds.\n"); } $start = microtime(true); CLI::logging("> Updating cache view...\n"); $workspace->upgradeCacheView(true, false, $lang); $stop = microtime(true); $final = $stop - $start; CLI::logging("<*> Updating cache view Process took $final seconds.\n"); mysql_close($link); } CLI::logging("Removing temporary files\n"); G::rm_dir($tempDirectory); CLI::logging(CLI::info("Done restoring") . "\n"); } }