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;