diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 3fc98dc15..76ecbc155 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -632,13 +632,32 @@ EOF } $sLogOffMenu .= "\n\n\n"; - if (MetaModel::DBIsReadOnly()) + $sRestrictions = ''; + if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE)) { - $sApplicationMode = Dict::S('UI:ApplicationReadOnly'); + if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE)) + { + $sRestrictions = Dict::S('UI:AccessRO-All'); + } + } + elseif (!MetaModel::DBHasAccess(ACCESS_USER_WRITE)) + { + $sRestrictions = Dict::S('UI:AccessRO-Users'); + } + + if (strlen($sRestrictions) > 0) + { + $sAdminMessage = trim(utils::GetConfig()->Get('access_message')); + $sApplicationBanner = ''; + $sApplicationBanner .= ' '.$sRestrictions.''; + if (strlen($sAdminMessage) > 0) + { + $sApplicationBanner .= ' '.$sAdminMessage.''; + } } else { - $sApplicationMode = ''; + $sApplicationBanner = ''; } //$sLogOffMenu = ""; @@ -667,8 +686,9 @@ EOF echo '
'; echo '
'; + echo '
'.$sApplicationBanner.'
'; echo ' '; //echo '        
'; diff --git a/application/ui.extkeywidget.class.inc.php b/application/ui.extkeywidget.class.inc.php index 10374b656..b4c32690c 100644 --- a/application/ui.extkeywidget.class.inc.php +++ b/application/ui.extkeywidget.class.inc.php @@ -258,14 +258,7 @@ EOF $oObj->UpdateObject($this->sFormPrefix.$this->iId); $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oObj->DBInsertTracked($oMyChange); diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index d25e1aac7..ea071dfae 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -789,6 +789,272 @@ class BulkChange return $aResult; } + + /** + * Display the history of bulk imports + */ + static function DisplayImportHistory(WebPage $oPage, $bFromAjax = false, $bShowAll = false) + { + $sAjaxDivId = "CSVImportHistory"; + if (!$bFromAjax) + { + $oPage->add('
'); + } + + $oPage->p(Dict::S('UI:History:BulkImports+')); + + $oBulkChangeSearch = DBObjectSearch::FromOQL("SELECT CMDBChange WHERE userinfo LIKE '%(CSV)'"); + + $iQueryLimit = $bShowAll ? 0 : utils::GetConfig()->GetMaxDisplayLimit() + 1; + $oBulkChanges = new DBObjectSet($oBulkChangeSearch, array('date' => false), array(), $iQueryLimit); + + $oAppContext = new ApplicationContext(); + + $bLimitExceeded = false; + if ($oBulkChanges->Count() > utils::GetConfig()->GetMaxDisplayLimit()) + { + $bLimitExceeded = true; + if (!$bShowAll) + { + $iMaxObjects = utils::GetConfig()->GetMinDisplayLimit(); + $oBulkChanges->SetLimit($iMaxObjects); + } + } + $oBulkChanges->Seek(0); + + $aDetails = array(); + while ($oChange = $oBulkChanges->Fetch()) + { + $sDate = ''.$oChange->Get('date').''; + $sUser = $oChange->GetUserName(); + if (preg_match('/^(.*)\\(CSV\\)$/i', $oChange->Get('userinfo'), $aMatches)) + { + $sUser = $aMatches[1]; + } + else + { + $sUser = $oChange->Get('userinfo'); + } + + $oOpSearch = DBObjectSearch::FromOQL("SELECT CMDBChangeOpCreate WHERE change = :change_id"); + $oOpSet = new DBObjectSet($oOpSearch, array(), array('change_id' => $oChange->GetKey())); + $iCreated = $oOpSet->Count(); + + //while ($oCreated = $oOpSet->Fetch()) + //{ + // $oPage->p("Created ".$oCreated->Get('objclass')."::".$oCreated->Get('objkey')); + //} + + $oOpSearch = DBObjectSearch::FromOQL("SELECT CMDBChangeOpSetAttribute WHERE change = :change_id"); + $oOpSet = new DBObjectSet($oOpSearch, array(), array('change_id' => $oChange->GetKey())); + + $aModified = array(); + $aAttList = array(); + while ($oModified = $oOpSet->Fetch()) + { + $sClass = $oModified->Get('objclass'); + $iKey = $oModified->Get('objkey'); + $sAttCode = $oModified->Get('attcode'); + + $aAttList[$sClass][$sAttCode] = true; + $aModified["$sClass::$iKey"] = true; + } + $iModified = count($aModified); + + // Assumption: there is only one class of objects being loaded + // Then the last class found gives us the class for every object + + $aDetails[] = array('date' => $sDate, 'user' => $sUser, 'class' => $sClass, 'created' => $iCreated, 'modified' => $iModified); + + } + + $aConfig = array( 'date' => array('label' => Dict::S('UI:History:Date'), 'description' => Dict::S('UI:History:Date+')), + 'user' => array('label' => Dict::S('UI:History:User'), 'description' => Dict::S('UI:History:User+')), + 'class' => array('label' => Dict::S('Core:AttributeClass'), 'description' => Dict::S('Core:AttributeClass+')), + 'created' => array('label' => Dict::S('UI:History:StatsCreations'), 'description' => Dict::S('UI:History:StatsCreations+')), + 'modified' => array('label' => Dict::S('UI:History:StatsModifs'), 'description' => Dict::S('UI:History:StatsModifs+')), + ); + + if ($bLimitExceeded) + { + if ($bShowAll) + { + // Collapsible list + $oPage->add('

'.Dict::Format('UI:CountOfResults', $oBulkChanges->Count()).'  '.Dict::S('UI:CollapseList').'

'); + } + else + { + // Truncated list + $iMinDisplayLimit = utils::GetConfig()->GetMinDisplayLimit(); + $sCollapsedLabel = Dict::Format('UI:TruncatedResults', $iMinDisplayLimit, $oBulkChanges->Count()); + $sLinkLabel = Dict::S('UI:DisplayAll'); + $oPage->add('

'.$sCollapsedLabel.'  '.$sLinkLabel.'

'); + + $oPage->add_ready_script( +<<GetForLink(); + $oPage->add_script( +<<table($aConfig, $aDetails); + + if (!$bFromAjax) + { + $oPage->add('
'); + } + } + + /** + * Display the details of an import + */ + static function DisplayImportHistoryDetails(iTopWebPage $oPage, $iChange) + { + if ($iChange == 0) + { + throw new Exception("Missing parameter changeid"); + } + $oChange = MetaModel::GetObject('CMDBChange', $iChange, false); + if (is_null($oChange)) + { + throw new Exception("Unknown change: $iChange"); + } + $oPage->add("

".Dict::Format('UI:History:BulkImportDetails', $oChange->Get('date'), $oChange->GetUserName())."

\n"); + + // Assumption : change made one single class of objects + $aObjects = array(); + $aAttributes = array(); // array of attcode => occurences + + $oOpSearch = DBObjectSearch::FromOQL("SELECT CMDBChangeOp WHERE change = :change_id"); + $oOpSet = new DBObjectSet($oOpSearch, array(), array('change_id' => $iChange)); + while ($oOperation = $oOpSet->Fetch()) + { + $sClass = $oOperation->Get('objclass'); + $iKey = $oOperation->Get('objkey'); + $iObjId = "$sClass::$iKey"; + if (!isset($aObjects[$iObjId])) + { + $aObjects[$iObjId] = array(); + $aObjects[$iObjId]['__class__'] = $sClass; + $aObjects[$iObjId]['__id__'] = $iKey; + } + if (get_class($oOperation) == 'CMDBChangeOpCreate') + { + $aObjects[$iObjId]['__created__'] = true; + } + elseif (is_subclass_of($oOperation, 'CMDBChangeOpSetAttribute')) + { + $sAttCode = $oOperation->Get('attcode'); + + if (get_class($oOperation) == 'CMDBChangeOpSetAttributeScalar') + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef->IsExternalKey()) + { + $oOldTarget = MetaModel::GetObject($oAttDef->GetTargetClass(), $oOperation->Get('oldvalue')); + $oNewTarget = MetaModel::GetObject($oAttDef->GetTargetClass(), $oOperation->Get('newvalue')); + $sOldValue = $oOldTarget->GetHyperlink(); + $sNewValue = $oNewTarget->GetHyperlink(); + } + else + { + $sOldValue = $oOperation->GetAsHTML('oldvalue'); + $sNewValue = $oOperation->GetAsHTML('newvalue'); + } + $aObjects[$iObjId][$sAttCode] = $sOldValue.' -> '.$sNewValue; + } + else + { + $aObjects[$iObjId][$sAttCode] = 'n/a'; + } + + if (isset($aAttributes[$sAttCode])) + { + $aAttributes[$sAttCode]++; + } + else + { + $aAttributes[$sAttCode] = 1; + } + } + } + + $aDetails = array(); + foreach($aObjects as $iUId => $aObjData) + { + $aRow = array(); + $oObject = MetaModel::GetObject($aObjData['__class__'], $aObjData['__id__'], false); + if (is_null($oObject)) + { + $aRow['object'] = $aObjData['__class__'].'::'.$aObjData['__id__'].' (deleted)'; + } + else + { + $aRow['object'] = $oObject->GetHyperlink(); + } + if (isset($aObjData['__created__'])) + { + $aRow['operation'] = Dict::S('Change:ObjectCreated'); + } + else + { + $aRow['operation'] = Dict::S('Change:ObjectModified'); + } + foreach ($aAttributes as $sAttCode => $iOccurences) + { + if (isset($aObjData[$sAttCode])) + { + $aRow[$sAttCode] = $aObjData[$sAttCode]; + } + elseif (!is_null($oObject)) + { + // This is the current vaslue: $oObject->GetAsHtml($sAttCode) + // whereas we are displaying the value that was set at the time + // the object was created + // This requires addtional coding...let's do that later + $aRow[$sAttCode] = ''; + } + else + { + $aRow[$sAttCode] = ''; + } + } + $aDetails[] = $aRow; + } + + $aConfig = array(); + $aConfig['object'] = array('label' => MetaModel::GetName($sClass), 'description' => MetaModel::GetClassDescription($sClass)); + $aConfig['operation'] = array('label' => Dict::S('UI:History:Changes'), 'description' => Dict::S('UI:History:Changes+')); + foreach ($aAttributes as $sAttCode => $iOccurences) + { + $aConfig[$sAttCode] = array('label' => MetaModel::GetLabel($sClass, $sAttCode), 'description' => MetaModel::GetDescription($sClass, $sAttCode)); + } + $oPage->table($aConfig, $aDetails); + } } diff --git a/core/cmdbchange.class.inc.php b/core/cmdbchange.class.inc.php index 4f7a0c80c..db2a9f84b 100644 --- a/core/cmdbchange.class.inc.php +++ b/core/cmdbchange.class.inc.php @@ -49,6 +49,34 @@ class CMDBChange extends DBObject MetaModel::Init_AddAttribute(new AttributeDateTime("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); } + + // Helper to keep track of the author of a given change, + // taking into account a variety of cases (contact attached or not, impersonation) + static public function GetCurrentUserName() + { + if (UserRights::IsImpersonated()) + { + $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUserFriendlyName(), UserRights::GetUserFriendlyName()); + } + else + { + $sUserString = UserRights::GetUserFriendlyName(); + } + return $sUserString; + } + + public function GetUserName() + { + if (preg_match('/^(.*)\\(CSV\\)$/i', $this->Get('userinfo'), $aMatches)) + { + $sUser = $aMatches[1]; + } + else + { + $sUser = $this->Get('userinfo'); + } + return $sUser; + } } ?> diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 51ef33192..87de78e8d 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -18,6 +18,11 @@ define('ITOP_VERSION', '$ITOP_VERSION$'); define('ITOP_REVISION', '$WCREV$'); define('ITOP_BUILD_DATE', '$WCNOW$'); +define('ACCESS_USER_WRITE', 1); +define('ACCESS_ADMIN_WRITE', 2); +define('ACCESS_FULL', ACCESS_USER_WRITE | ACCESS_ADMIN_WRITE); +define('ACCESS_READONLY', 0); + /** * Configuration read/write * @@ -143,14 +148,6 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ), - 'database_read_only' => array( - 'type' => 'bool', - 'description' => 'Freeze the data for administration purposes - administrators can still do anything... in appearance!', - 'default' => false, - 'value' => '', - 'source_of_value' => '', - 'show_in_conf_sample' => false, - ), // Levels that trigger a confirmation in the CSV import/synchro wizard 'csv_import_min_object_confirmation' => array( 'type' => 'integer', @@ -184,6 +181,22 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ), + 'access_mode' => array( + 'type' => 'integer', + 'description' => 'Combination of flags (ACCESS_USER_WRITE | ACCESS_ADMIN_WRITE, or ACCESS_FULL)', + 'default' => ACCESS_FULL, + 'value' => ACCESS_FULL, + 'source_of_value' => '', + 'show_in_conf_sample' => true, + ), + 'access_message' => array( + 'type' => 'string', + 'description' => 'Message displayed to the users when there is any access restriction', + 'default' => 'iTop is temporarily frozen, please wait... (the admin team)', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => true, + ), ); public function IsProperty($sPropCode) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 687774695..40de91821 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2547,28 +2547,26 @@ abstract class MetaModel /* * Determines wether the target DB is frozen or not - * 1 - consider the DB property 'status' - * 2 - check the setting 'database_read_only' */ public static function DBIsReadOnly() { - $sStatus = DBProperty::GetProperty('status', null); - if (!is_null($sStatus)) + // Improvement: check the mySQL variable -> Read-only + + if (UserRights::IsAdministrator()) { - switch (strtolower(trim($sStatus))) - { - case 'fullaccess': - $ret = false; - break; - default: - $ret = true; - } + return (!self::DBHasAccess(ACCESS_ADMIN_WRITE)); } else { - $ret = self::$m_oConfig->Get('database_read_only'); + return (!self::DBHasAccess(ACCESS_USER_WRITE)); } - return $ret; + } + + public static function DBHasAccess($iRequested = ACCESS_FULL) + { + $iMode = self::$m_oConfig->Get('access_mode'); + if (($iMode & $iRequested) == 0) return false; + return true; } protected static function MakeDictEntry($sKey, $sValueFromOldSystem, $sDefaultValue, &$bNotInDico) diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index bd3bb81e2..7ef26d117 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -110,6 +110,35 @@ abstract class User extends cmdbAbstractObject abstract public function CanChangePassword(); abstract public function ChangePassword($sOldPassword, $sNewPassword); + /* + * Compute a name in best effort mode + */ + public function GetFriendlyName() + { + if (!MetaModel::IsValidAttCode(get_class($this), 'contactid')) + { + return $this->Get('login'); + } + if ($this->Get('contactid') != 0) + { + $sFirstName = $this->Get('first_name'); + $sLastName = $this->Get('last_name'); + $sEmail = $this->Get('email'); + if (strlen($sFirstName) > 0) + { + return "$sFirstName $sLastName"; + } + elseif (strlen($sEmail) > 0) + { + return "$sLastName <$sEmail>"; + } + else + { + return $sLastName; + } + } + } + /* * Overload the standard behavior */ @@ -490,6 +519,24 @@ class UserRights return $oUser->Get('contactid'); } + // Render the user name in best effort mode + public static function GetUserFriendlyName($sName = '') + { + if (empty($sName)) + { + $oUser = self::$m_oUser; + } + else + { + $oUser = FindUser($sName); + } + if (is_null($oUser)) + { + return ''; + } + return $oUser->GetFriendlyName(); + } + public static function IsImpersonated() { if (is_null(self::$m_oRealUser)) @@ -517,6 +564,15 @@ class UserRights return self::$m_oRealUser->GetKey(); } + public static function GetRealUserFriendlyName() + { + if (is_null(self::$m_oRealUser)) + { + return ''; + } + return self::$m_oRealUser->GetFriendlyName(); + } + protected static function CheckLogin() { if (!self::IsLoggedIn()) @@ -551,8 +607,6 @@ class UserRights // When initializing, we need to let everything pass trough if (!self::CheckLogin()) return true; - if (self::IsAdministrator($oUser)) return true; - if (MetaModel::DBIsReadOnly()) { if ($iActionCode == UR_ACTION_MODIFY) return false; @@ -561,6 +615,8 @@ class UserRights if ($iActionCode == UR_ACTION_BULK_DELETE) return false; } + if (self::IsAdministrator($oUser)) return true; + if (MetaModel::HasCategory($sClass, 'bizmodel')) { // #@# Temporary????? @@ -589,8 +645,6 @@ class UserRights // When initializing, we need to let everything pass trough if (!self::CheckLogin()) return true; - if (self::IsAdministrator($oUser)) return true; - if (MetaModel::DBIsReadOnly()) { if ($iActionCode == UR_ACTION_MODIFY) return false; @@ -599,6 +653,8 @@ class UserRights if ($iActionCode == UR_ACTION_BULK_DELETE) return false; } + if (self::IsAdministrator($oUser)) return true; + if (MetaModel::HasCategory($sClass, 'bizmodel')) { if (is_null($oUser)) @@ -619,8 +675,6 @@ class UserRights // When initializing, we need to let everything pass trough if (!self::CheckLogin()) return true; - if (self::IsAdministrator($oUser)) return true; - if (MetaModel::DBIsReadOnly()) { if ($iActionCode == UR_ACTION_MODIFY) return false; @@ -629,6 +683,8 @@ class UserRights if ($iActionCode == UR_ACTION_BULK_DELETE) return false; } + if (self::IsAdministrator($oUser)) return true; + // this module is forbidden for non admins if (MetaModel::HasCategory($sClass, 'addon/userrights')) return false; diff --git a/css/light-grey.css b/css/light-grey.css index df86f5135..0cdd24416 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -807,6 +807,14 @@ div#logo div { background: url(../images/banner-bkg.png) repeat-x scroll 0 0 transparent; text-align: right; } +#admin-banner { + float: left; + margin-top: 2px; + padding: 8px; + border: 1px solid #c33; + background-color: #fee; + -moz-border-radius: 0.5em; +} #global-search { height: 55px; float: right; @@ -908,4 +916,4 @@ span.form_validation { text-align:center; width: 95%; -moz-border-radius: 0.5em; -} \ No newline at end of file +} diff --git a/dictionaries/de.dictionary.itop.core.php b/dictionaries/de.dictionary.itop.core.php index 70d7f591c..b55bf3bf5 100644 --- a/dictionaries/de.dictionary.itop.core.php +++ b/dictionaries/de.dictionary.itop.core.php @@ -203,7 +203,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( // Used by CMDBChangeOp... & derived classes Dict::Add('DE DE', 'German', 'Deutsch', array( 'Change:ObjectCreated' => 'Objekt erstellt', - 'Change:ObjectDeleted' => 'Objekt erstellt', + 'Change:ObjectDeleted' => 'Object deleted', + 'Change:ObjectModified' => 'Object modified', 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s geändert zu %2$s (vorheriger Wert: %3$s)', 'Change:Text_AppendedTo_AttName' => '%1$s zugefügt an %2$s', 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modifiziert, vorheriger Wert: %2$s', diff --git a/dictionaries/de.dictionary.itop.ui.php b/dictionaries/de.dictionary.itop.ui.php index 45d82e464..784f62402 100644 --- a/dictionaries/de.dictionary.itop.ui.php +++ b/dictionaries/de.dictionary.itop.ui.php @@ -439,6 +439,12 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'UI:History:User+' => 'Benutzer, der die Änderung durchführte', 'UI:History:Changes' => 'Änderungen', 'UI:History:Changes+' => 'Änderungen, die am Objekt durchgeführt wurden', + 'UI:History:StatsCreations' => 'Created', + 'UI:History:StatsCreations+' => 'Count of objects created', + 'UI:History:StatsModifs' => 'Modified', + 'UI:History:StatsModifs+' => 'Count of objects modified', + 'UI:History:StatsDeletes' => 'Deleted', + 'UI:History:StatsDeletes+' => 'Count of objects deleted', 'UI:Loading' => 'Laden...', 'UI:Menu:Actions' => 'Aktionen', 'UI:Menu:New' => 'Neu...', @@ -481,6 +487,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'UI:LogOff:ThankYou' => 'Vielen Dank dafür, dass Sie iTop benutzen!', 'UI:LogOff:ClickHereToLoginAgain' => 'Klicken Sie hier, um sich wieder anzumelden...', 'UI:ChangePwdMenu' => 'Passwort ändern...', + 'UI:AccessRO-All' => 'iTop is read-only', + 'UI:AccessRO-Users' => 'iTop is read-only for end-users', 'UI:Login:RetypePwdDoesNotMatch' => 'Neues Passwort und das wiederholte Passwort entsprechen nicht überein!', 'UI:Button:Login' => 'iTop betreten', 'UI:Login:Error:AccessRestricted' => 'Der iTop-Zugang ist gesperrt. Bitte kontaktieren Sie einen iTop-Administrator.', diff --git a/dictionaries/dictionary.itop.core.php b/dictionaries/dictionary.itop.core.php index 5c1047a2f..67b9eb290 100644 --- a/dictionaries/dictionary.itop.core.php +++ b/dictionaries/dictionary.itop.core.php @@ -203,6 +203,7 @@ Dict::Add('EN US', 'English', 'English', array( Dict::Add('EN US', 'English', 'English', array( 'Change:ObjectCreated' => 'Object created', 'Change:ObjectDeleted' => 'Object deleted', + 'Change:ObjectModified' => 'Object modified', 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s set to %2$s (previous value: %3$s)', 'Change:Text_AppendedTo_AttName' => '%1$s appended to %2$s', 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modified, previous value: %2$s', diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index db93edd04..294582d00 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -399,12 +399,21 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:History:LastModified_On_By' => 'Last modified on %1$s by %2$s.', 'UI:HistoryTab' => 'History', 'UI:NotificationsTab' => 'Notifications', + 'UI:History:BulkImports' => 'History', + 'UI:History:BulkImports+' => 'List of CSV imports (last first)', + 'UI:History:BulkImportDetails' => 'Changes resulting from the CSV import performed on %1$s (by %2$s)', 'UI:History:Date' => 'Date', 'UI:History:Date+' => 'Date of the change', 'UI:History:User' => 'User', 'UI:History:User+' => 'User who made the change', 'UI:History:Changes' => 'Changes', 'UI:History:Changes+' => 'Changes made to the object', + 'UI:History:StatsCreations' => 'Created', + 'UI:History:StatsCreations+' => 'Count of objects created', + 'UI:History:StatsModifs' => 'Modified', + 'UI:History:StatsModifs+' => 'Count of objects modified', + 'UI:History:StatsDeletes' => 'Deleted', + 'UI:History:StatsDeletes+' => 'Count of objects deleted', 'UI:Loading' => 'Loading...', 'UI:Menu:Actions' => 'Actions', 'UI:Menu:New' => 'New...', @@ -447,6 +456,8 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:LogOff:ThankYou' => 'Thank you for using iTop', 'UI:LogOff:ClickHereToLoginAgain' => 'Click here to login again...', 'UI:ChangePwdMenu' => 'Change Password...', + 'UI:AccessRO-All' => 'iTop is read-only', + 'UI:AccessRO-Users' => 'iTop is read-only for end-users', 'UI:Login:RetypePwdDoesNotMatch' => 'New password and retyped new password do not match !', 'UI:Button:Login' => 'Enter iTop', 'UI:Login:Error:AccessRestricted' => 'iTop access is restricted. Please, contact an iTop administrator.', diff --git a/dictionaries/es_cr.dictionary.itop.ui.php b/dictionaries/es_cr.dictionary.itop.ui.php index 22531fe3b..9c04ad709 100644 --- a/dictionaries/es_cr.dictionary.itop.ui.php +++ b/dictionaries/es_cr.dictionary.itop.ui.php @@ -406,12 +406,21 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:NoObject_Class_ToDisplay' => 'No %1$s to display', 'UI:History:LastModified_On_By' => 'Last modified on %1$s by %2$s.', 'UI:HistoryTab' => 'History', + 'UI:History:BulkImports' => 'History', + 'UI:History:BulkImports+' => 'List of CSV imports (last first)', + 'UI:History:BulkImportDetails' => 'Changes resulting from the CSV import performed on %1$s (by %2$s)', 'UI:History:Date' => 'Date', 'UI:History:Date+' => 'Date of the change', 'UI:History:User' => 'User', 'UI:History:User+' => 'User who made the change', 'UI:History:Changes' => 'Changes', 'UI:History:Changes+' => 'Changes made to the object', + 'UI:History:StatsCreations' => 'Created', + 'UI:History:StatsCreations+' => 'Count of objects created', + 'UI:History:StatsModifs' => 'Modified', + 'UI:History:StatsModifs+' => 'Count of objects modified', + 'UI:History:StatsDeletes' => 'Deleted', + 'UI:History:StatsDeletes+' => 'Count of objects deleted', 'UI:Loading' => 'Loading...', 'UI:Menu:Actions' => 'Actions', 'UI:Menu:New' => 'New...', @@ -451,6 +460,8 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:Login:IncorrectOldPassword' => 'Error: the old password is incorrect', 'UI:LogOffMenu' => 'Log off', 'UI:ChangePwdMenu' => 'Change Password...', + 'UI:AccessRO-All' => 'iTop is read-only', + 'UI:AccessRO-Users' => 'iTop is read-only for end-users', 'UI:Login:RetypePwdDoesNotMatch' => 'New password and retyped new password do not match !', 'UI:Button:Login' => 'Enter iTop', 'UI:Login:Error:AccessRestricted' => 'iTop access is restricted. Please, contact an iTop administrator.', diff --git a/dictionaries/fr.dictionary.itop.core.php b/dictionaries/fr.dictionary.itop.core.php index 575a66aa1..b5d0e6434 100644 --- a/dictionaries/fr.dictionary.itop.core.php +++ b/dictionaries/fr.dictionary.itop.core.php @@ -204,6 +204,7 @@ Dict::Add('FR FR', 'French', 'Français', array( Dict::Add('FR FR', 'French', 'Français', array( 'Change:ObjectCreated' => 'Elément créé', 'Change:ObjectDeleted' => 'Elément effacé', + 'Change:ObjectModified' => 'Elément modifié', 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s modifié en %2$s (ancienne valeur: %3$s)', 'Change:Text_AppendedTo_AttName' => '%1$s ajouté à %2$s', 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modifié, ancienne valeur: %2$s', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index e189be551..80ddb3f8b 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -400,12 +400,21 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:History:LastModified_On_By' => 'Dernière modification par %2$s le %1$s.', 'UI:HistoryTab' => 'Historique', 'UI:NotificationsTab' => 'Notifications', + 'UI:History:BulkImports' => 'Historique', + 'UI:History:BulkImports+' => 'Liste des imports CSV (le dernier est en haut de la liste)', + 'UI:History:BulkImportDetails' => 'Changements résultant de l\'import CSV du %1$s (auteur: %2$s)', 'UI:History:Date' => 'Date', 'UI:History:Date+' => 'Date de modification', 'UI:History:User' => 'Utilisateur', 'UI:History:User+' => 'Utilisateur qui a fait la modification', 'UI:History:Changes' => 'Changements', 'UI:History:Changes+' => 'Changements sur cet objet', + 'UI:History:StatsCreations' => 'Créés', + 'UI:History:StatsCreations+' => 'Nombre d\'objets créés', + 'UI:History:StatsModifs' => 'Modifiés', + 'UI:History:StatsModifs+' => 'Nombre d\'objets modifiés', + 'UI:History:StatsDeletes' => 'Effacés', + 'UI:History:StatsDeletes+' => 'Nombre d\'objets effacés', 'UI:Loading' => 'Chargement...', 'UI:Menu:Actions' => 'Actions', 'UI:Menu:New' => 'Créer...', @@ -448,6 +457,8 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:LogOff:ThankYou' => 'Merci d\'avoir utilisé iTop', 'UI:LogOff:ClickHereToLoginAgain' => 'Cliquez ici pour vous reconnecter...', 'UI:ChangePwdMenu' => 'Changer de mot de passe...', + 'UI:AccessRO-All' => 'iTop est en lecture seule', + 'UI:AccessRO-Users' => 'iTop est en lecture seule pour les utilisateurs finaux', 'UI:Login:RetypePwdDoesNotMatch' => 'Les deux saisies du nouveau mot de passe ne sont pas identiques !', 'UI:Button:Login' => 'Entrer dans iTop', 'UI:Login:Error:AccessRestricted' => 'L\'accès à iTop est soumis à autorisation. Merci de contacter votre administrateur iTop.', diff --git a/dictionaries/pt_br.dictionary.itop.ui.php b/dictionaries/pt_br.dictionary.itop.ui.php index 3ad8ccd73..0262db4f6 100644 --- a/dictionaries/pt_br.dictionary.itop.ui.php +++ b/dictionaries/pt_br.dictionary.itop.ui.php @@ -408,12 +408,19 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'UI:NoObject_Class_ToDisplay' => 'Nenhum %1$s para mostrar', 'UI:History:LastModified_On_By' => 'Ultima modificacao de %1$s por %2$s.', 'UI:HistoryTab' => 'Historico', + 'UI:History:BulkImports' => 'History', + 'UI:History:BulkImports+' => 'List of CSV imports (last first)', + 'UI:History:BulkImportDetails' => 'Changes resulting from the CSV import performed on %1$s (by %2$s)', 'UI:History:Date' => 'Data', 'UI:History:Date+' => 'Data da alteração', 'UI:History:User' => 'Usuario', 'UI:History:User+' => 'Usuario que fez alteração', 'UI:History:Changes' => 'Alterações', 'UI:History:Changes+' => 'Alterações feita no objeto', + 'UI:History:StatsModifs' => 'Modified', + 'UI:History:StatsModifs+' => 'Count of objects modified', + 'UI:History:StatsDeletes' => 'Deleted', + 'UI:History:StatsDeletes+' => 'Count of objects deleted', 'UI:Loading' => 'Carregando...', 'UI:Menu:Actions' => 'Ações', 'UI:Menu:New' => 'Novo...', @@ -453,6 +460,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'UI:Login:IncorrectOldPassword' => 'Erro: senha incorreta', 'UI:LogOffMenu' => 'Sair', 'UI:ChangePwdMenu' => 'Alterar senha...', + 'UI:AccessRO-All' => 'iTop is read-only', + 'UI:AccessRO-Users' => 'iTop is read-only for end-users', 'UI:Login:RetypePwdDoesNotMatch' => 'A nova senha nao confere!', 'UI:Button:Login' => 'Enter iTop', 'UI:Login:Error:AccessRestricted' => 'iTop accesso restrito. Por favor, contate o suporte.', diff --git a/images/locked.png b/images/locked.png new file mode 100644 index 000000000..b00530a32 Binary files /dev/null and b/images/locked.png differ diff --git a/images/unlocked.png b/images/unlocked.png new file mode 100644 index 000000000..69a09fd82 Binary files /dev/null and b/images/unlocked.png differ diff --git a/modules/authent-local/model.authent-local.php b/modules/authent-local/model.authent-local.php index 172d7abc2..c4ce53fa5 100644 --- a/modules/authent-local/model.authent-local.php +++ b/modules/authent-local/model.authent-local.php @@ -93,14 +93,7 @@ class UserLocal extends UserInternal $this->Set('password', $sNewPassword); $oChange = MetaModel::NewObject("CMDBChange"); $oChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oChange->Set("userinfo", $sUserString); $oChange->DBInsert(); $this->DBUpdateTracked($oChange, true); diff --git a/pages/UI.php b/pages/UI.php index 5ef83c13a..e6a42faf7 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -130,14 +130,7 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed) // $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $oMyChange->DBInsert(); @@ -918,14 +911,7 @@ try $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oObj->DBUpdateTracked($oMyChange); @@ -1055,14 +1041,7 @@ try { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oObj->DBInsertTracked($oMyChange); @@ -1111,14 +1090,7 @@ try $sClassLabel = MetaModel::GetName($sClass); $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oObj->DBInsertTracked($oMyChange); @@ -1276,14 +1248,7 @@ EOF { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oObj->DBUpdateTracked($oMyChange); @@ -1328,14 +1293,7 @@ EOF $sLinkingAttCode = utils::ReadPostedParam('linking_attcode', ''); $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index c51c3c662..41579f8c4 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -206,6 +206,11 @@ try } break; + case 'displayCSVHistory': + $bShowAll = (utils::ReadParam('showall', 'false') == 'true'); + BulkChange::DisplayImportHistory($oPage, true, $bShowAll); + break; + case 'details': $key = utils::ReadParam('id', 0); $oFilter = new DBObjectSearch($sClass); diff --git a/pages/csvimport.php b/pages/csvimport.php index 6b9aef28d..984488987 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -35,7 +35,6 @@ try require_once(APPROOT.'/application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed - $oAppContext = new ApplicationContext(); $iStep = utils::ReadParam('step', 1); $oPage = new iTopWebPage(Dict::S('UI:Title:BulkImport')); @@ -344,14 +343,7 @@ try // We're doing it for real, let's create a change $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $sUserString .= ' (CSV)'; $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); @@ -1246,7 +1238,7 @@ EOF ); $oPage->add_ready_script('DoPreview();'); } - + /** * Prompt for the data to be loaded (either via a file or a copy/paste) * @param WebPage $oPage The current web page @@ -1412,10 +1404,19 @@ $('#select_template_class').change( function() { }); EOF ); + + $oPage->SetCurrentTabContainer('tabs1'); + $oPage->SetCurrentTab(Dict::S('UI:History:BulkImports')); + BulkChange::DisplayImportHistory($oPage); } switch($iStep) { + case 10: + $iChange = (int)utils::ReadParam('changeid', 0); + BulkChange::DisplayImportHistoryDetails($oPage, $iChange); + break; + case 5: LoadData($oPage); break; diff --git a/portal/index.php b/portal/index.php index 31f10776c..f61b2c255 100644 --- a/portal/index.php +++ b/portal/index.php @@ -326,14 +326,7 @@ function DoCreateRequest($oP, $oUserOrg) { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oRequest->DBInsertTracked($oMyChange); @@ -622,14 +615,7 @@ function DoCloseRequest($oP, UserRequest $oRequest) { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oRequest->DBUpdateTracked($oMyChange); diff --git a/webservices/import.php b/webservices/import.php index 4e04bc7aa..dee36f50b 100644 --- a/webservices/import.php +++ b/webservices/import.php @@ -483,14 +483,7 @@ try { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); if (strlen($sComment) > 0) { $sMoreInfo = 'Web Service (CSV) - '.$sComment;