/**
  * 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}";
     }
 }
Пример #5
0
 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;
 }
Пример #6
0
         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);
 }
Пример #8
0
 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
     }
 }
Пример #9
0
 /**
  * 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;
 }
Пример #10
0
 protected static function ClauseValues($aValues)
 {
     $aSetValues = array();
     foreach ($aValues as $sFieldSpec => $value) {
         $aSetValues[] = "{$sFieldSpec} = " . CMDBSource::Quote($value);
     }
     $sSetValues = implode(', ', $aSetValues);
     return $sSetValues;
 }
Пример #11
0
 /**
  * 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);
 }
Пример #12
0
 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}'.");
 }