/** * Helper to modify an enum value * The change is made in the datamodel definition, but the value has to be changed in the DB as well * Must be called BEFORE DB update, i.e within an implementation of BeforeDatabaseCreation() * This helper does change ONE value at a time * * @param string $sClass A valid class name * @param string $sAttCode The enum attribute code * @param string $sFrom Original value (already INVALID in the current datamodel) * @param string $sTo New value (valid in the current datamodel) * @return void */ public static function RenameEnumValueInDB($sClass, $sAttCode, $sFrom, $sTo) { try { $sOriginClass = MetaModel::GetAttributeOrigin($sClass, $sAttCode); $sTableName = MetaModel::DBGetTable($sOriginClass); $oAttDef = MetaModel::GetAttributeDef($sOriginClass, $sAttCode); if ($oAttDef instanceof AttributeEnum) { $oValDef = $oAttDef->GetValuesDef(); if ($oValDef) { $aNewValues = array_keys($oValDef->GetValues(array(), "")); if (in_array($sTo, $aNewValues)) { $sEnumCol = $oAttDef->Get("sql"); $aFields = CMDBSource::QueryToArray("SHOW COLUMNS FROM `{$sTableName}` WHERE Field = '{$sEnumCol}'"); if (isset($aFields[0]['Type'])) { $sColType = $aFields[0]['Type']; // Note: the parsing should rely on str_getcsv (requires PHP 5.3) to cope with escaped string if (preg_match("/^enum\\(\\'(.*)\\'\\)\$/", $sColType, $aMatches)) { $aCurrentValues = explode("','", $aMatches[1]); } } if (!in_array($sFrom, $aNewValues)) { if (!in_array($sTo, $aCurrentValues)) { $sNullSpec = $oAttDef->IsNullAllowed() ? 'NULL' : 'NOT NULL'; if (strtolower($sTo) == strtolower($sFrom)) { SetupPage::log_info("Changing enum in DB - {$sClass}::{$sAttCode} from '{$sFrom}' to '{$sTo}' (just a change in the case)"); $aTargetValues = array(); foreach ($aCurrentValues as $sValue) { if ($sValue == $sFrom) { $sValue = $sTo; } $aTargetValues[] = $sValue; } $sColumnDefinition = "ENUM(" . implode(",", CMDBSource::Quote($aTargetValues)) . ") {$sNullSpec}"; $sRepair = "ALTER TABLE `{$sTableName}` MODIFY `{$sEnumCol}` {$sColumnDefinition}"; CMDBSource::Query($sRepair); } else { // 1st - Allow both values in the column definition // SetupPage::log_info("Changing enum in DB - {$sClass}::{$sAttCode} from '{$sFrom}' to '{$sTo}'"); $aAllValues = $aCurrentValues; $aAllValues[] = $sTo; $sColumnDefinition = "ENUM(" . implode(",", CMDBSource::Quote($aAllValues)) . ") {$sNullSpec}"; $sRepair = "ALTER TABLE `{$sTableName}` MODIFY `{$sEnumCol}` {$sColumnDefinition}"; CMDBSource::Query($sRepair); // 2nd - Change the old value into the new value // $sRepair = "UPDATE `{$sTableName}` SET `{$sEnumCol}` = '{$sTo}' WHERE `{$sEnumCol}` = BINARY '{$sFrom}'"; CMDBSource::Query($sRepair); $iAffectedRows = CMDBSource::AffectedRows(); SetupPage::log_info("Changing enum in DB - {$iAffectedRows} rows updated"); // 3rd - Remove the useless value from the column definition // $aTargetValues = array(); foreach ($aCurrentValues as $sValue) { if ($sValue == $sFrom) { $sValue = $sTo; } $aTargetValues[] = $sValue; } $sColumnDefinition = "ENUM(" . implode(",", CMDBSource::Quote($aTargetValues)) . ") {$sNullSpec}"; $sRepair = "ALTER TABLE `{$sTableName}` MODIFY `{$sEnumCol}` {$sColumnDefinition}"; CMDBSource::Query($sRepair); SetupPage::log_info("Changing enum in DB - removed useless value '{$sFrom}'"); } } } else { SetupPage::log_warning("Changing enum in DB - {$sClass}::{$sAttCode} - '{$sFrom}' is still a valid value (" . implode(', ', $aNewValues) . ")"); } } else { SetupPage::log_warning("Changing enum in DB - {$sClass}::{$sAttCode} - '{$sTo}' is not a known value (" . implode(', ', $aNewValues) . ")"); } } } } catch (Exception $e) { SetupPage::log_warning("Changing enum in DB - {$sClass}::{$sAttCode} - '{$sTo}' failed. Reason " . $e->getMessage()); } }
/** * Describe (as a text string) the modifications corresponding to this change */ public function GetDescription() { $sResult = ''; $oTargetObjectClass = $this->Get('objclass'); $oTargetObjectKey = $this->Get('objkey'); $oTargetSearch = new DBObjectSearch($oTargetObjectClass); $oTargetSearch->AddCondition('id', $oTargetObjectKey, '='); $oMonoObjectSet = new DBObjectSet($oTargetSearch); if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES) { if (!MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode'))) { return ''; } // Protects against renamed attributes... $oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode')); $sAttName = $oAttDef->GetLabel(); $sLinkClass = $oAttDef->GetLinkedClass(); $aLinkClasses = MetaModel::EnumChildClasses($sLinkClass, ENUM_CHILD_CLASSES_ALL); // Search for changes on the corresponding link // $oSearch = new DBObjectSearch('CMDBChangeOpSetAttribute'); $oSearch->AddCondition('change', $this->Get('change'), '='); $oSearch->AddCondition('objkey', $this->Get('link_id'), '='); if (count($aLinkClasses) == 1) { // Faster than the whole building of the expression below for just one value ?? $oSearch->AddCondition('objclass', $sLinkClass, '='); } else { $oField = new FieldExpression('objclass', $oSearch->GetClassAlias()); $sListExpr = '(' . implode(', ', CMDBSource::Quote($aLinkClasses)) . ')'; $sOQLCondition = $oField->Render() . " IN {$sListExpr}"; $oNewCondition = Expression::FromOQL($sOQLCondition); $oSearch->AddConditionExpression($oNewCondition); } $oSet = new DBObjectSet($oSearch); $aChanges = array(); while ($oChangeOp = $oSet->Fetch()) { $aChanges[] = $oChangeOp->GetDescription(); } if (count($aChanges) == 0) { return ''; } $sItemDesc = MetaModel::GetHyperLink($this->Get('item_class'), $this->Get('item_id')); $sResult = $sAttName . ' - '; $sResult .= Dict::Format('Change:LinkSet:Modified', $sItemDesc); $sResult .= ' : ' . implode(', ', $aChanges); } return $sResult; }
/** * Updates the object form POSTED arguments, and writes it into the DB (applies a stimuli if requested) * @param DBObject $oObj The object to update * $param array $aAttList If set, this will limit the list of updated attributes * @return void */ public function DoUpdateObjectFromPostedForm(DBObject $oObj, $aAttList = null) { $sTransactionId = utils::ReadPostedParam('transaction_id', ''); if (!utils::IsTransactionValid($sTransactionId)) { throw new TransactionException(); } $sClass = get_class($oObj); $sStimulus = trim(utils::ReadPostedParam('apply_stimulus', '')); $sTargetState = ''; if (!empty($sStimulus)) { // Compute the target state $aTransitions = $oObj->EnumTransitions(); if (!isset($aTransitions[$sStimulus])) { throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel())); } $sTargetState = $aTransitions[$sStimulus]['target_state']; } $oObj->UpdateObjectFromPostedForm('', $aAttList, $sTargetState); // Optional: apply a stimulus // if (!empty($sStimulus)) { if (!$oObj->ApplyStimulus($sStimulus)) { throw new Exception("Cannot apply stimulus '{$sStimulus}' to {$oObj->GetName()}"); } } if ($oObj->IsModified()) { // Record the change // $oObj->DBUpdate(); // Trigger ? // $aClasses = MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL); $sClassList = implode(", ", CMDBSource::Quote($aClasses)); $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnPortalUpdate AS t WHERE t.target_class IN ({$sClassList})")); while ($oTrigger = $oSet->Fetch()) { $oTrigger->DoActivate($oObj->ToArgs('this')); } $this->p("<h1>" . Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()) . "</h1>\n"); } $bLockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); if ($bLockEnabled) { // Release the concurrent lock, if any $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, false, 'raw_data'); if ($sOwnershipToken !== null) { // We're done, let's release the lock iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); } } }
public function GetBasicFilterSQLExpr($sOpCode, $value) { $sQValue = CMDBSource::Quote($value); switch ($sOpCode) { case '=': case '!=': return $this->GetSQLExpr() . " {$sOpCode} {$sQValue}"; case 'Contains': return $this->GetSQLExpr() . " LIKE " . CMDBSource::Quote("%{$value}%"); case 'NotLike': return $this->GetSQLExpr() . " NOT LIKE {$sQValue}"; case 'Like': default: return $this->GetSQLExpr() . " LIKE {$sQValue}"; } }
protected static function GetConditionIN($oFilter, $sFilterCode, $condition) { $oField = new FieldExpression($sFilterCode, $oFilter->GetClassAlias()); $sListExpr = '(' . implode(', ', CMDBSource::Quote($condition)) . ')'; $sOQLCondition = $oField->Render() . " IN {$sListExpr}"; $oNewCondition = Expression::FromOQL($sOQLCondition); return $oNewCondition; }
if ($iCol == $iPrimaryKeyCol) { continue; } $sCol = $aInputColumns[$iCol]; if ($aIsDateToTransform[$iCol]) { $sDate = ChangeDateFormat($aRow[$iCol], $sDateFormat); if ($sDate === false) { // Skip this column spec if ($sOutput == 'details') { $oP->add("{$iRow}: Wrong format for date field: '" . $aRow[$iCol] . "' (skipped column)\n"); } } else { $aValuePairs[] = "`{$sCol}` = " . CMDBSource::Quote($sDate); } } else { $aValuePairs[] = "`{$sCol}` = " . CMDBSource::Quote($aRow[$iCol]); } } $sValuePairs = implode(', ', $aValuePairs); $sUpdateQuery = "UPDATE `{$sTable}` SET {$sValuePairs} WHERE {$sReconciliationCondition}"; try { CMDBSource::Query($sUpdateQuery); } catch (Exception $e) { if ($sNoStopOnImportError == '1') { $iCountErrors++; $oP->add("{$iRow}: Import error '" . $e->getMessage() . "' (continuing)...\n"); } else { throw $e; } } } else {
public function AddCondition($sFilterCode, $value, $sOpCode = null) { MyHelpers::CheckKeyInArray('filter code in class: ' . $this->GetClass(), $sFilterCode, MetaModel::GetClassFilterDefs($this->GetClass())); $oFilterDef = MetaModel::GetClassFilterDef($this->GetClass(), $sFilterCode); $oField = new FieldExpression($sFilterCode, $this->GetClassAlias()); if (empty($sOpCode)) { if ($sFilterCode == 'id') { $sOpCode = '='; } else { $oAttDef = MetaModel::GetAttributeDef($this->GetClass(), $sFilterCode); $oNewCondition = $oAttDef->GetSmartConditionExpression($value, $oField, $this->m_aParams); $this->AddConditionExpression($oNewCondition); return; } } MyHelpers::CheckKeyInArray('operator', $sOpCode, $oFilterDef->GetOperators()); // Preserve backward compatibility - quick n'dirty way to change that API semantic // switch ($sOpCode) { case 'SameDay': case 'SameMonth': case 'SameYear': case 'Today': case '>|': case '<|': case '=|': throw new CoreException('Deprecated operator, please consider using OQL (SQL) expressions like "(TO_DAYS(NOW()) - TO_DAYS(x)) AS AgeDays"', array('operator' => $sOpCode)); break; case "IN": if (!is_array($value)) { $value = array($value); } $sListExpr = '(' . implode(', ', CMDBSource::Quote($value)) . ')'; $sOQLCondition = $oField->Render() . " IN {$sListExpr}"; break; case "NOTIN": if (!is_array($value)) { $value = array($value); } $sListExpr = '(' . implode(', ', CMDBSource::Quote($value)) . ')'; $sOQLCondition = $oField->Render() . " NOT IN {$sListExpr}"; break; case 'Contains': $this->m_aParams[$sFilterCode] = "%{$value}%"; $sOperator = 'LIKE'; break; case 'Begins with': $this->m_aParams[$sFilterCode] = "{$value}%"; $sOperator = 'LIKE'; break; case 'Finishes with': $this->m_aParams[$sFilterCode] = "%{$value}"; $sOperator = 'LIKE'; break; default: $this->m_aParams[$sFilterCode] = $value; $sOperator = $sOpCode; } switch ($sOpCode) { case "IN": case "NOTIN": $oNewCondition = Expression::FromOQL($sOQLCondition); break; case 'Contains': case 'Begins with': case 'Finishes with': default: $oRightExpr = new VariableExpression($sFilterCode); $oNewCondition = new BinaryExpression($oField, $sOperator, $oRightExpr); } $this->AddConditionExpression($oNewCondition); }
protected function DBDeleteSingleObject() { if (!MetaModel::DBIsReadOnly()) { $this->OnDelete(); $this->RecordObjDeletion($this->m_iKey); // May cause a reload for storing history information foreach (MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef) { if ($oAttDef->IsHierarchicalKey()) { // Update the left & right indexes for each hierarchical key $sTable = $sTable = MetaModel::DBGetTable(get_class($this), $sAttCode); $sSQL = "SELECT `" . $oAttDef->GetSQLRight() . "` AS `right`, `" . $oAttDef->GetSQLLeft() . "` AS `left` FROM `{$sTable}` WHERE id=" . CMDBSource::Quote($this->m_iKey); $aRes = CMDBSource::QueryToArray($sSQL); $iMyLeft = $aRes[0]['left']; $iMyRight = $aRes[0]['right']; $iDelta = $iMyRight - $iMyLeft + 1; MetaModel::HKTemporaryCutBranch($iMyLeft, $iMyRight, $oAttDef, $sTable); // No new parent for now, insert completely at the right of the tree $sSQL = "SELECT max(`" . $oAttDef->GetSQLRight() . "`) AS max FROM `{$sTable}`"; $aRes = CMDBSource::QueryToArray($sSQL); if (count($aRes) == 0) { $iNewLeft = 1; } else { $iNewLeft = $aRes[0]['max'] + 1; } MetaModel::HKReplugBranch($iNewLeft, $iNewLeft + $iDelta - 1, $oAttDef, $sTable); } } foreach (MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL) as $sParentClass) { $this->DBDeleteSingleTable($sParentClass); } $this->AfterDelete(); $this->m_bIsInDB = false; // Fix for #926: do NOT reset m_iKey as it can be used to have it for reporting purposes (see the REST service to delete objects, reported as bug #926) // Thought the key is not reset, using DBInsert or DBWrite will create an object having the same characteristics and a new ID. DBUpdate is protected } }
/** * Applies the defined parameters into the SQL query * @return string the SQL query to execute */ public function BuildQuery() { $oAppContext = new ApplicationContext(); $sQuery = $this->m_sQuery; $sQuery = str_replace('$DB_PREFIX$', MetaModel::GetConfig()->GetDBSubname(), $sQuery); // put the tables DB prefix (if any) foreach ($this->m_aParams as $sName => $aParam) { if ($aParam['type'] == 'context') { $sSearchPattern = '/\\$CONDITION\\(' . $sName . ',([^\\)]+)\\)\\$/'; $value = $oAppContext->GetCurrentValue($aParam['mapping']); if (empty($value)) { $sSQLExpr = '(1)'; } else { // Special case for managing the hierarchy of organizations if ($aParam['mapping'] == 'org_id' && MetaModel::IsValidClass('Organization')) { $sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization'); if ($sHierarchicalKeyCode != false) { // organizations are in hierarchy... gather all the orgs below the given one... $sOQL = "SELECT Organization AS node JOIN Organization AS root ON node.{$sHierarchicalKeyCode} BELOW root.id WHERE root.id = :value"; $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(), array('value' => $value)); $aOrgIds = array(); while ($oOrg = $oSet->Fetch()) { $aOrgIds[] = $oOrg->GetKey(); } $sSQLExpr = '($1 IN(' . implode(',', $aOrgIds) . '))'; } else { $sSQLExpr = '($1 = ' . CMDBSource::Quote($value) . ')'; } } else { $sSQLExpr = '($1 = ' . CMDBSource::Quote($value) . ')'; } } $sQuery = preg_replace($sSearchPattern, $sSQLExpr, $sQuery); } } return $sQuery; }
protected static function ClauseValues($aValues) { $aSetValues = array(); foreach ($aValues as $sFieldSpec => $value) { $aSetValues[] = "{$sFieldSpec} = " . CMDBSource::Quote($value); } $sSetValues = implode(', ', $aSetValues); return $sSetValues; }
/** * Helper to remove selected objects without calling any handler * Surpasses BulkDelete as it can handle abstract classes, but has the other limitation as it bypasses standard objects handlers * * @param string $oFilter Scope of objects to wipe out * @return The count of deleted objects */ public static function PurgeData($oFilter) { $sTargetClass = $oFilter->GetClass(); $oSet = new DBObjectSet($oFilter); $oSet->OptimizeColumnLoad(array($sTargetClass => array('finalclass'))); $aIdToClass = $oSet->GetColumnAsArray('finalclass', true); $aIds = array_keys($aIdToClass); if (count($aIds) > 0) { $aQuotedIds = CMDBSource::Quote($aIds); $sIdList = implode(',', $aQuotedIds); $aTargetClasses = array_merge(self::EnumChildClasses($sTargetClass, ENUM_CHILD_CLASSES_ALL), self::EnumParentClasses($sTargetClass, ENUM_PARENT_CLASSES_EXCLUDELEAF)); foreach ($aTargetClasses as $sSomeClass) { $sTable = MetaModel::DBGetTable($sSomeClass); $sPKField = MetaModel::DBGetKey($sSomeClass); $sDeleteSQL = "DELETE FROM `{$sTable}` WHERE `{$sPKField}` IN ({$sIdList})"; CMDBSource::DeleteFrom($sDeleteSQL); } } return count($aIds); }
public function Render(&$aArgs = null, $bRetrofitParams = false) { $aRes = array(); foreach ($this->m_aExpressions as $oExpr) { $sCol = $oExpr->Render($aArgs, $bRetrofitParams); // Concat will be globally NULL if one single argument is null ! $aRes[] = "COALESCE({$sCol}, '')"; } $sSep = CMDBSource::Quote($this->m_separator); return "CAST(CONCAT_WS({$sSep}, " . implode(', ', $aRes) . ") AS CHAR)"; }
/** * Helper to modify an enum value * The change is made in the datamodel definition, but the value has to be changed in the DB as well * Must be called BEFORE DB update, i.e within an implementation of BeforeDatabaseCreation() * * @param string $sClass A valid class name * @param string $sAttCode The enum attribute code * @param string $sFrom Original value (already INVALID in the current datamodel) * @param string $sTo New value (valid in the current datamodel) * @return void */ public static function RenameEnumValueInDB($sClass, $sAttCode, $sFrom, $sTo) { $sOriginClass = MetaModel::GetAttributeOrigin($sClass, $sAttCode); $sTableName = MetaModel::DBGetTable($sOriginClass); $oAttDef = MetaModel::GetAttributeDef($sOriginClass, $sAttCode); if ($oAttDef instanceof AttributeEnum) { $oValDef = $oAttDef->GetValuesDef(); if ($oValDef) { $aNewValues = array_keys($oValDef->GetValues(array(), "")); if (in_array($sTo, $aNewValues)) { $aAllValues = $aNewValues; $aAllValues[] = $sFrom; if (!in_array($sFrom, $aNewValues)) { $sEnumCol = $oAttDef->Get("sql"); $sNullSpec = $oAttDef->IsNullAllowed() ? 'NULL' : 'NOT NULL'; if (strtolower($sTo) == strtolower($sFrom)) { SetupPage::log_info("Changing enum in DB - {$sClass}::{$sAttCode} from '{$sFrom}' to '{$sTo}' (just a change in the case)"); $sColumnDefinition = "ENUM(" . implode(",", CMDBSource::Quote($aNewValues)) . ") {$sNullSpec}"; $sRepair = "ALTER TABLE `{$sTableName}` MODIFY `{$sEnumCol}` {$sColumnDefinition}"; CMDBSource::Query($sRepair); } else { // 1st - Allow both values in the column definition // SetupPage::log_info("Changing enum in DB - {$sClass}::{$sAttCode} from '{$sFrom}' to '{$sTo}'"); $sColumnDefinition = "ENUM(" . implode(",", CMDBSource::Quote($aAllValues)) . ") {$sNullSpec}"; $sRepair = "ALTER TABLE `{$sTableName}` MODIFY `{$sEnumCol}` {$sColumnDefinition}"; CMDBSource::Query($sRepair); // 2nd - Change the old value into the new value // $sRepair = "UPDATE `{$sTableName}` SET `{$sEnumCol}` = '{$sTo}' WHERE `{$sEnumCol}` = BINARY '{$sFrom}'"; CMDBSource::Query($sRepair); $iAffectedRows = CMDBSource::AffectedRows(); SetupPage::log_info("Changing enum in DB - {$iAffectedRows} rows updated"); // 3rd - Remove the useless value from the column definition // $sColumnDefinition = "ENUM(" . implode(",", CMDBSource::Quote($aNewValues)) . ") {$sNullSpec}"; $sRepair = "ALTER TABLE `{$sTableName}` MODIFY `{$sEnumCol}` {$sColumnDefinition}"; CMDBSource::Query($sRepair); SetupPage::log_info("Changing enum in DB - removed useless value '{$sFrom}'"); } } else { SetupPage::log_warning("Changing enum in DB - {$sClass}::{$sAttCode} - '{$sFrom}' is still a valid value (" . implode(', ', $aNewValues) . ")"); } } else { SetupPage::log_warning("Changing enum in DB - {$sClass}::{$sAttCode} - '{$sTo}' is not a known value (" . implode(', ', $aNewValues) . ")"); } } } }
protected static function DoUpdateDBSchema($sMode, $aSelectedModules, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment = '', $bOldAddon = false) { SetupPage::log_info("Update Database Schema for environment '{$sTargetEnvironment}'."); $oConfig = new Config(); $aParamValues = array('mode' => $sMode, 'db_server' => $sDBServer, 'db_user' => $sDBUser, 'db_pwd' => $sDBPwd, 'db_name' => $sDBName, 'db_prefix' => $sDBPrefix); $oConfig->UpdateFromParams($aParamValues, $sModulesDir); if ($bOldAddon) { // Old version of the add-on for backward compatibility with pre-2.0 data models $oConfig->SetAddons(array('user rights' => 'addons/userrights/userrightsprofile.db.class.inc.php')); } $oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); $oProductionEnv->InitDataModel($oConfig, true); // load data model only // Migrate application data format // // priv_internalUser caused troubles because MySQL transforms table names to lower case under Windows // This becomes an issue when moving your installation data to/from Windows // Starting 2.0, all table names must be lowercase if ($sMode != 'install') { SetupPage::log_info("Renaming '{$sDBPrefix}priv_internalUser' into '{$sDBPrefix}priv_internaluser' (lowercase)"); // This command will have no effect under Windows... // and it has been written in two steps so as to make it work under windows! CMDBSource::SelectDB($sDBName); try { $sRepair = "RENAME TABLE `{$sDBPrefix}priv_internalUser` TO `{$sDBPrefix}priv_internaluser_other`, `{$sDBPrefix}priv_internaluser_other` TO `{$sDBPrefix}priv_internaluser`"; CMDBSource::Query($sRepair); } catch (Exception $e) { SetupPage::log_info("Renaming '{$sDBPrefix}priv_internalUser' failed (already done in a previous upgrade?)"); } // let's remove the records in priv_change which have no counterpart in priv_changeop SetupPage::log_info("Cleanup of '{$sDBPrefix}priv_change' to remove orphan records"); CMDBSource::SelectDB($sDBName); try { $sTotalCount = "SELECT COUNT(*) FROM `{$sDBPrefix}priv_change`"; $iTotalCount = (int) CMDBSource::QueryToScalar($sTotalCount); SetupPage::log_info("There is a total of {$iTotalCount} records in {$sDBPrefix}priv_change."); $sOrphanCount = "SELECT COUNT(c.id) FROM `{$sDBPrefix}priv_change` AS c left join `{$sDBPrefix}priv_changeop` AS o ON c.id = o.changeid WHERE o.id IS NULL"; $iOrphanCount = (int) CMDBSource::QueryToScalar($sOrphanCount); SetupPage::log_info("There are {$iOrphanCount} useless records in {$sDBPrefix}priv_change (" . sprintf('%.2f', 100.0 * $iOrphanCount / $iTotalCount) . "%)"); if ($iOrphanCount > 0) { SetupPage::log_info("Removing the orphan records..."); $sCleanup = "DELETE FROM `{$sDBPrefix}priv_change` USING `{$sDBPrefix}priv_change` LEFT JOIN `{$sDBPrefix}priv_changeop` ON `{$sDBPrefix}priv_change`.id = `{$sDBPrefix}priv_changeop`.changeid WHERE `{$sDBPrefix}priv_changeop`.id IS NULL;"; CMDBSource::Query($sCleanup); SetupPage::log_info("Cleanup completed successfully."); } else { SetupPage::log_info("Ok, nothing to cleanup."); } } catch (Exception $e) { SetupPage::log_info("Cleanup of orphan records in `{$sDBPrefix}priv_change` failed: " . $e->getMessage()); } } // Module specific actions (migrate the data) // $aAvailableModules = $oProductionEnv->AnalyzeInstallation(MetaModel::GetConfig(), APPROOT . $sModulesDir); foreach ($aAvailableModules as $sModuleId => $aModule) { if ($sModuleId != ROOT_MODULE && in_array($sModuleId, $aSelectedModules) && isset($aAvailableModules[$sModuleId]['installer'])) { $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; SetupPage::log_info("Calling Module Handler: {$sModuleInstallerClass}::BeforeDatabaseCreation(oConfig, {$aModule['version_db']}, {$aModule['version_code']})"); $aCallSpec = array($sModuleInstallerClass, 'BeforeDatabaseCreation'); call_user_func_array($aCallSpec, array(MetaModel::GetConfig(), $aModule['version_db'], $aModule['version_code'])); } } if (!$oProductionEnv->CreateDatabaseStructure(MetaModel::GetConfig(), $sMode)) { throw new Exception("Failed to create/upgrade the database structure for environment '{$sTargetEnvironment}'"); } // priv_change now has an 'origin' field to distinguish between the various input sources // Let's initialize the field with 'interactive' for all records were it's null // Then check if some records should hold a different value, based on a pattern matching in the userinfo field CMDBSource::SelectDB($sDBName); try { $sCount = "SELECT COUNT(*) FROM `{$sDBPrefix}priv_change` WHERE `origin` IS NULL"; $iCount = (int) CMDBSource::QueryToScalar($sCount); if ($iCount > 0) { SetupPage::log_info("Initializing '{$sDBPrefix}priv_change.origin' ({$iCount} records to update)"); // By default all uninitialized values are considered as 'interactive' $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'interactive' WHERE `origin` IS NULL"; CMDBSource::Query($sInit); // CSV Import was identified by the comment at the end $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'csv-import.php' WHERE `userinfo` LIKE '%Web Service (CSV)'"; CMDBSource::Query($sInit); // CSV Import was identified by the comment at the end $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'csv-interactive' WHERE `userinfo` LIKE '%(CSV)' AND origin = 'interactive'"; CMDBSource::Query($sInit); // Syncho data sources were identified by the comment at the end // Unfortunately the comment is localized, so we have to search for all possible patterns $sCurrentLanguage = Dict::GetUserLanguage(); foreach (Dict::GetLanguages() as $sLangCode => $aLang) { Dict::SetUserLanguage($sLangCode); $sSuffix = CMDBSource::Quote('%' . Dict::S('Core:SyncDataExchangeComment')); $aSuffixes[$sSuffix] = true; } Dict::SetUserLanguage($sCurrentLanguage); $sCondition = "`userinfo` LIKE " . implode(" OR `userinfo` LIKE ", array_keys($aSuffixes)); $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'synchro-data-source' WHERE ({$sCondition})"; CMDBSource::Query($sInit); SetupPage::log_info("Initialization of '{$sDBPrefix}priv_change.origin' completed."); } else { SetupPage::log_info("'{$sDBPrefix}priv_change.origin' already initialized, nothing to do."); } } catch (Exception $e) { SetupPage::log_error("Initializing '{$sDBPrefix}priv_change.origin' failed: " . $e->getMessage()); } // priv_async_task now has a 'status' field to distinguish between the various statuses rather than just relying on the date columns // Let's initialize the field with 'planned' or 'error' for all records were it's null CMDBSource::SelectDB($sDBName); try { $sCount = "SELECT COUNT(*) FROM `{$sDBPrefix}priv_async_task` WHERE `status` IS NULL"; $iCount = (int) CMDBSource::QueryToScalar($sCount); if ($iCount > 0) { SetupPage::log_info("Initializing '{$sDBPrefix}priv_async_task.status' ({$iCount} records to update)"); $sInit = "UPDATE `{$sDBPrefix}priv_async_task` SET `status` = 'planned' WHERE (`status` IS NULL) AND (`started` IS NULL)"; CMDBSource::Query($sInit); $sInit = "UPDATE `{$sDBPrefix}priv_async_task` SET `status` = 'error' WHERE (`status` IS NULL) AND (`started` IS NOT NULL)"; CMDBSource::Query($sInit); SetupPage::log_info("Initialization of '{$sDBPrefix}priv_async_task.status' completed."); } else { SetupPage::log_info("'{$sDBPrefix}priv_async_task.status' already initialized, nothing to do."); } } catch (Exception $e) { SetupPage::log_error("Initializing '{$sDBPrefix}priv_async_task.status' failed: " . $e->getMessage()); } SetupPage::log_info("Database Schema Successfully Updated for environment '{$sTargetEnvironment}'."); }