From bec8b57fe15ab110b2320cf1d0e232ea27035408 Mon Sep 17 00:00:00 2001
From: Denis Flaven $sMessage $sMessage current value for $sAttCode : $currValue ".Dict::Format('UI:BulkModify_Count_DistinctValues', $iCount)."
";
$sTip = '';
@@ -391,7 +399,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$oPage->add_ready_script("$('#synchro_$iInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
}
- $val['value'] .= $sSynchroIcon;
+ $val['comments'] = $sSynchroIcon;
// The field is visible, add it to the current column
$aDetails[$sTab][$sColIndex][] = $val;
$iInputId++;
@@ -555,7 +563,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
{
if (!$bSingleSelectMode)
{
- $aAttribs['form::select'] = array('label' => "", 'description' => Dict::S('UI:SelectAllToggle+'));
+ $aAttribs['form::select'] = array('label' => "", 'description' => Dict::S('UI:SelectAllToggle+'));
}
else
{
@@ -596,13 +604,21 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
}
if ($bSelectMode)
{
- if ($bSingleSelectMode)
+ if (array_key_exists('selection_enabled', $aExtraParams) && isset($aExtraParams['selection_enabled'][$oObj->GetKey()]))
{
- $aRow['form::select'] = "GetKey()."\">";
+ $sDisabled = ($aExtraParams['selection_enabled'][$oObj->GetKey()]) ? '' : ' disabled="disabled"';
}
else
{
- $aRow['form::select'] = "GetKey()."\">";
+ $sDisabled = '';
+ }
+ if ($bSingleSelectMode)
+ {
+ $aRow['form::select'] = "GetKey()."\">";
+ }
+ else
+ {
+ $aRow['form::select'] = "GetKey()."\">";
}
}
foreach($aList as $sAttCode)
@@ -1238,6 +1254,7 @@ EOF
$sSeconds = "";
$sHidden = "";
$sHTMLValue = Dict::Format('UI:DurationForm_Days_Hours_Minutes_Seconds', $sDays, $sHours, $sMinutes, $sSeconds).$sHidden." ".$sValidationField;
+ $oPage->add_ready_script("$('#{$iId}').bind('update', function(evt, sFormId) { return ToggleDurationField('$iId'); });");
break;
case 'Password':
@@ -1304,6 +1321,7 @@ EOF
$iMaxComboLength = $oAttDef->GetMaximumComboLength();
$oWidget = new UIExtKeyWidget($sAttCode, $sClass, $oAttDef->GetLabel(), $aAllowedValues, $value, $iId, $bMandatory, $sNameSuffix, $sFieldPrefix, $sFormPrefix);
$sHTMLValue = $oWidget->Display($oPage, $aArgs);
+ $sHTMLValue .= "\n";
break;
case 'String':
@@ -1362,14 +1380,15 @@ EOF
public function DisplayModifyForm(WebPage $oPage, $aExtraParams = array())
{
- static $iGlobalFormId = 1;
- $iGlobalFormId++;
+ self::$iGlobalFormId++;
$sPrefix = '';
if (isset($aExtraParams['formPrefix']))
{
$sPrefix = $aExtraParams['formPrefix'];
}
- $this->m_iFormId = $sPrefix.$iGlobalFormId;
+ $aFieldsComments = (isset($aExtraParams['fieldsComments'])) ? $aExtraParams['fieldsComments'] : array();
+
+ $this->m_iFormId = $sPrefix.self::$iGlobalFormId;
$sClass = get_class($this);
$oAppContext = new ApplicationContext();
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
@@ -1448,6 +1467,8 @@ EOF
foreach($aFields as $sAttCode)
{
$aVal = null;
+ $sComments = isset($aFieldsComments[$sAttCode]) ? $aFieldsComments[$sAttCode] : ' ';
+ $sInfos = ' ';
$iFlags = $this->GetAttributeFlags($sAttCode);
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0))
@@ -1458,7 +1479,7 @@ EOF
{
// State attribute is always read-only from the UI
$sHTMLValue = $this->GetStateLabel();
- $aVal = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue);
+ $aVal = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos);
}
else
{
@@ -1471,15 +1492,15 @@ EOF
}
else
{
- if ($iFlags & OPT_ATT_READONLY)
+ if ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE))
{
// Check if the attribute is not read-only becuase of a synchro...
$aReasons = array();
- $iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
$sSynchroIcon = '';
- if ($iSynchroFlags & OPT_ATT_READONLY)
+ if ($iFlags & OPT_ATT_SLAVE)
{
+ $iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
$sSynchroIcon = "
";
$sTip = '';
foreach($aReasons as $aRow)
@@ -1490,9 +1511,10 @@ EOF
}
// Attribute is read-only
- $sHTMLValue = $this->GetAsHTML($sAttCode).$sSynchroIcon;
+ $sHTMLValue = $this->GetAsHTML($sAttCode);;
$sHTMLValue .= '';
$aFieldsMap[$sAttCode] = $sInputId;
+ $sComments = $sSynchroIcon;
}
else
{
@@ -1503,13 +1525,13 @@ EOF
$aFieldsMap[$sAttCode] = $sInputId;
}
- $aVal = array('label' => ''.$oAttDef->GetLabel().'', 'value' => $sHTMLValue);
+ $aVal = array('label' => ''.$oAttDef->GetLabel().'', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos);
}
}
}
else
{
- $aVal = array('label' => ''.$oAttDef->GetLabel().'', 'value' => $this->GetAsHTML($sAttCode));
+ $aVal = array('label' => ''.$oAttDef->GetLabel().'', 'value' => $this->GetAsHTML($sAttCode), 'comments' => $sComments, 'infos' => $sInfos);
}
}
if ($aVal != null)
@@ -1548,22 +1570,48 @@ EOF
$oPage->add("\n");
}
$oPage->add($oAppContext->GetForForm());
+ // Custom operation for the form ?
+ if (isset($aExtraParams['custom_operation']))
+ {
+ $sOperation = $aExtraParams['custom_operation'];
+ }
+ else if ($iKey > 0)
+ {
+ $sOperation = 'apply_modify';
+ }
+ else
+ {
+ $sOperation = 'apply_new';
+ }
+
+ // Custom label for the apply button ?
+ if (isset($aExtraParams['custom_button']))
+ {
+ $sApplyButton = $aExtraParams['custom_button'];
+ }
+ else if ($iKey > 0)
+ {
+ $sApplyButton = Dict::S('UI:Button:Apply');
+ }
+ else
+ {
+ $sApplyButton = Dict::S('UI:Button:Create');
+ }
+
if ($iKey > 0)
{
- // The object already exists in the database, it's modification
+ // The object already exists in the database, it's a modification
$oPage->add("\n");
- $oPage->add("\n");
-// $oPage->add(" \n");
+ $oPage->add("\n");
$oPage->add(" \n");
- $oPage->add("\n");
+ $oPage->add("\n");
}
else
{
// The object does not exist in the database it's a creation
- $oPage->add("\n");
-// $oPage->add(" \n");
+ $oPage->add("\n");
$oPage->add(" \n");
- $oPage->add("\n");
+ $oPage->add("\n");
}
// Hook the cancel button via jQuery so that it can be unhooked easily as well if needed
$sDefaultUrl = '../pages/UI.php?operation=cancel';
@@ -1572,11 +1620,12 @@ EOF
$iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap);
+ $sState = $this->GetState();
$oPage->add_script(
<<
";
+ $sHTMLValue .= "iId}.Search();\">iId}\" style=\"border:0;vertical-align:middle;\" src=\"../images/mini_search.gif\" /> ";
// another hidden input to store & pass the object's Id
$sHTMLValue .= "iId\" name=\"{$sAttrFieldPrefix}{$this->sFieldPrefix}{$this->sAttCode}{$this->sNameSuffix}\" value=\"$this->value\" />\n";
// Scripts to start the autocomplete and bind some events to it
- $oPage->add_ready_script(
+ $sDialogTitle = addslashes($this->sTitle);
+ $oPage->add_ready_script(
<<
";
+ $sHTMLValue .= "iId}.CreateObject();\">iId}\" style=\"border:0;vertical-align:middle;\" src=\"../images/mini_add.gif\" /> ";
$oPage->add_at_the_end('');
}
$sHTMLValue .= "iId}\">";
@@ -259,11 +262,12 @@ EOF
*/
public function GetObjectCreationForm(WebPage $oPage)
{
+ $sDialogTitle = addslashes($this->sTitle);
$oPage->add('
".MetaModel::GetClassIcon($this->sTargetClass)." ".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sTargetClass))."
\n");
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, null, array(), array('formPrefix' => $this->iId, 'noRelations' => true));
$oPage->add('
";
+ $sHtmlValue = "$sValidationField
";
// Replace the text area with CKEditor
// To change the default settings of the editor,
// a) edit the file /js/ckeditor/config.js
// b) or override some of the configuration settings, using the second parameter of ckeditor()
$sLanguage = strtolower(trim(UserRights::GetUserLanguage()));
- $oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, { language : '$sLanguage' , contentsLanguage : '$sLanguage' });"); // Transform $iId into a CKEdit
+ $oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, { language : '$sLanguage' , contentsLanguage : '$sLanguage', extraPlugins: 'disabler' });"); // Transform $iId into a CKEdit
// Please read...
// ValidateCKEditField triggers a timer... calling itself indefinitely
@@ -78,7 +78,8 @@ class UIHTMLEditorWidget
// The most relevant solution would be to implement a plugin to CKEdit, and handle the internal events like: setData, insertHtml, insertElement, loadSnapshot, key, afterUndo, afterRedo
// Could also be bound to 'instanceReady.ckeditor'
- $oPage->add_ready_script("$('#$iId').bind('validate', function(evt, sFormId) { return ValidateCKEditField('$iId', '', {$this->m_sMandatory}, sFormId, '') } );");
+ $oPage->add_ready_script("$('#$iId').bind('validate', function(evt, sFormId) { return ValidateCKEditField('$iId', '', {$this->m_sMandatory}, sFormId, '') } );\n");
+ $oPage->add_ready_script("$('#$iId').bind('update', function() { BlockField('cke_$iId', $('#$iId').attr('disabled')); } );\n");
return $sHtmlValue;
}
diff --git a/application/ui.passwordwidget.class.inc.php b/application/ui.passwordwidget.class.inc.php
index 4bea0a559..fc33208cf 100644
--- a/application/ui.passwordwidget.class.inc.php
+++ b/application/ui.passwordwidget.class.inc.php
@@ -57,13 +57,28 @@ class UIPasswordWidget
$sChangedValue = (($sPasswordValue != '*****') || ($sConfirmPasswordValue != '*****')) ? 1 : 0;
$sHtmlValue = '';
$sHtmlValue = ' $sValidationField
';
- $sHtmlValue .= ' '.Dict::S('UI:PasswordConfirm').' ';
+ $sHtmlValue .= ' '.Dict::S('UI:PasswordConfirm').' ';
$sHtmlValue .= '';
$oPage->add_ready_script("$('#$this->iId').bind('keyup change', function(evt) { return PasswordFieldChanged('$this->iId') } );"); // Bind to a custom event: validate
$oPage->add_ready_script("$('#$this->iId').bind('keyup change validate', function(evt, sFormId) { return ValidatePasswordField('$this->iId', sFormId) } );"); // Bind to a custom event: validate
$oPage->add_ready_script("$('#{$this->iId}_confirm').bind('keyup change', function(evt, sFormId) { return ValidatePasswordField('$this->iId', sFormId) } );"); // Bind to a custom event: validate
-
+ $oPage->add_ready_script("$('#{$this->iId}').bind('update', function(evt, sFormId)
+ {
+ if ($(this).attr('disabled'))
+ {
+ $('#{$this->iId}_confirm').attr('disabled', 'disabled');
+ $('#{$this->iId}_changed').attr('disabled', 'disabled');
+ $('#{$this->iId}_reset').attr('disabled', 'disabled');
+ }
+ else
+ {
+ $('#{$this->iId}_confirm').removeAttr('disabled');
+ $('#{$this->iId}_changed').removeAttr('disabled');
+ $('#{$this->iId}_reset').removeAttr('disabled');
+ }
+ }
+ );"); // Bind to a custom event: update to handle enabling/disabling
return $sHtmlValue;
}
}
diff --git a/application/webpage.class.inc.php b/application/webpage.class.inc.php
index caa30a2ce..e17dc532e 100644
--- a/application/webpage.class.inc.php
+++ b/application/webpage.class.inc.php
@@ -258,6 +258,9 @@ class WebPage
{
$sHtml .= "".$aAttrib['label']." ".$aAttrib['value']." \n";
}
+ $sComment = (isset($aAttrib['comments'])) ? $aAttrib['comments'] : ' ';
+ $sInfo = (isset($aAttrib['infos'])) ? $aAttrib['infos'] : ' ';
+ $sHtml .= "$sComment $sInfo \n";
$sHtml .= "\n";
}
$sHtml .= "\n";
diff --git a/application/wizardhelper.class.inc.php b/application/wizardhelper.class.inc.php
index db231ca9a..08f9e8f99 100644
--- a/application/wizardhelper.class.inc.php
+++ b/application/wizardhelper.class.inc.php
@@ -121,6 +121,11 @@ class WizardHelper
}
}
}
+ if (isset($this->m_aData['m_sState']) && !empty($this->m_aData['m_sState']))
+ {
+ $oObj->Set(MetaModel::GetStateAttributeCode($this->m_aData['m_sClass']), $this->m_aData['m_sState']);
+ }
+
return $oObj;
}
diff --git a/core/dbobject.class.php b/core/dbobject.class.php
index 274258fb0..79253af2d 100644
--- a/core/dbobject.class.php
+++ b/core/dbobject.class.php
@@ -1543,7 +1543,7 @@ abstract class DBObject
{
if (($oSyncAttr->Get('attcode') == $sAttCode) && ($oSyncAttr->Get('update') == 1) && ($oSyncAttr->Get('update_policy') == 'master_locked'))
{
- $iFlags |= OPT_ATT_READONLY;
+ $iFlags |= OPT_ATT_SLAVE;
$sUrl = $oSource->GetApplicationUrl($this, $oReplica);
$aReason[] = array('name' => $oSource->GetName(), 'description' => $oSource->Get('description'), 'url_application' => $sUrl);
}
diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php
index faeab6b1b..023668022 100644
--- a/core/dbobjectset.class.php
+++ b/core/dbobjectset.class.php
@@ -526,6 +526,109 @@ class DBObjectSet
}
return $aRelatedObjs;
}
+
+ /**
+ * Builds an object that contains the values that are common to all the objects
+ * in the set. If for a given attribute, objects in the set have various values
+ * then the resulting object will contain null for this value.
+ * @param $aValues Hash Output: the distribution of the values, in the set, for each attribute
+ * @return Object
+ */
+ public function ComputeCommonObject(&$aValues)
+ {
+ $sClass = $this->GetClass();
+ $aList = MetaModel::FlattenZlist(MetaModel::GetZListItems($sClass, 'details'));
+ $aValues = array();
+ foreach($aList as $sAttCode)
+ {
+ $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
+ if ($oAttDef->IsScalar())
+ {
+ $aValues[$sAttCode] = array();
+ }
+ }
+ $this->Rewind();
+ while($oObj = $this->Fetch())
+ {
+ foreach($aList as $sAttCode)
+ {
+ $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
+ if ($oAttDef->IsScalar() && $oAttDef->IsWritable())
+ {
+ $currValue = $oObj->Get($sAttCode);
+ if (is_object($currValue)) continue; // Skip non scalar values...
+ if(!array_key_exists($currValue, $aValues[$sAttCode]))
+ {
+ $aValues[$sAttCode][$currValue] = array('count' => 1, 'display' => $oObj->GetAsHTML($sAttCode));
+ }
+ else
+ {
+ $aValues[$sAttCode][$currValue]['count']++;
+ }
+ }
+ }
+ }
+
+ foreach($aValues as $sAttCode => $aMultiValues)
+ {
+ if (count($aMultiValues) > 1)
+ {
+ uasort($aValues[$sAttCode], 'HashCountComparison');
+ }
+ }
+
+
+ // Now create an object that has values for the homogenous values only
+ $oCommonObj = new $sClass(); // @@ What if the class is abstract ?
+ $aComments = array();
+
+ $iFormId = cmdbAbstractObject::GetNextFormId(); // Identifier that prefixes all the form fields
+ $sReadyScript = '';
+ $aDependsOn = array();
+ $sFormPrefix = '2_';
+ foreach($aList as $sAttCode)
+ {
+ $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
+ if ($oAttDef->IsScalar() && $oAttDef->IsWritable())
+ {
+ if ($oAttDef->GetEditClass() == 'One Way Password')
+ {
+ $oCommonObj->Set($sAttCode, null);
+ }
+ else
+ {
+ $iCount = count($aValues[$sAttCode]);
+ if ($iCount == 1)
+ {
+ // Homogenous value
+ reset($aValues[$sAttCode]);
+ $aKeys = array_keys($aValues[$sAttCode]);
+ $currValue = $aKeys[0]; // The only value is the first key
+ $oCommonObj->Set($sAttCode, $currValue);
+ }
+ else
+ {
+ // Non-homogenous value
+ $oCommonObj->Set($sAttCode, null);
+ }
+ }
+ }
+ }
+ $this->Rewind();
+ return $oCommonObj;
+ }
+}
+
+/**
+ * Helper function to perform a custom sort of a hash array
+ */
+function HashCountComparison($a, $b) // Sort descending on 'count'
+{
+ if ($a['count'] == $b['count'])
+ {
+ return 0;
+ }
+ return ($a['count'] > $b['count']) ? -1 : 1;
}
?>
diff --git a/core/metamodel.class.php b/core/metamodel.class.php
index c18acb527..12f157c96 100644
--- a/core/metamodel.class.php
+++ b/core/metamodel.class.php
@@ -98,6 +98,13 @@ define('OPT_ATT_MUSTCHANGE', 8);
* @package iTopORM
*/
define('OPT_ATT_MUSTPROMPT', 16);
+/**
+ * Specifies that the attribute is in 'slave' mode compared to one data exchange task:
+ * it should not be edited inside iTop anymore
+ *
+ * @package iTopORM
+ */
+define('OPT_ATT_SLAVE', 32);
/**
* DB Engine -should be moved into CMDBSource
@@ -3542,9 +3549,13 @@ abstract class MetaModel
// Note: load the dictionary as soon as possible, because it might be
// needed when some error occur
- foreach (self::$m_oConfig->GetDictionaries() as $sModule => $sToInclude)
+ if (!Dict::InCache())
{
- self::Plugin($sConfigFile, 'dictionaries', $sToInclude);
+ foreach (self::$m_oConfig->GetDictionaries() as $sModule => $sToInclude)
+ {
+ self::Plugin($sConfigFile, 'dictionaries', $sToInclude);
+ }
+ Dict::InitCache();
}
// Set the language... after the dictionaries have been loaded!
Dict::SetDefaultLanguage(self::$m_oConfig->GetDefaultLanguage());
diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php
index f9275a0b8..b2b3da512 100644
--- a/core/userrights.class.inc.php
+++ b/core/userrights.class.inc.php
@@ -816,4 +816,169 @@ class UserRights
}
}
+/**
+ * Helper class to get the number/list of items for which a given action is allowed/possible
+ */
+class ActionChecker
+{
+ var $oFilter;
+ var $iActionCode;
+ var $iAllowedCount = null;
+ var $aAllowedIDs = null;
+
+ public function __construct(DBObjectSearch $oFilter, $iActionCode)
+ {
+ $this->oFilter = $oFilter;
+ $this->iActionCode = $iActionCode;
+ $this->iAllowedCount = null;
+ $this->aAllowedIDs = null;
+ }
+
+ /**
+ * returns the number of objects for which the action is allowed
+ * @return integer The number of "allowed" objects 0..N
+ */
+ public function GetAllowedCount()
+ {
+ if ($this->iAllowedCount == null) $this->CheckObjects();
+ return $this->iAllowedCount;
+ }
+
+ /**
+ * If IsAllowed returned UR_ALLOWED_DEPENDS, this methods returns
+ * an array of ObjKey => Status (true|false)
+ * @return array
+ */
+ public function GetAllowedIDs()
+ {
+ if ($this->aAllowedIDs == null) $this->IsAllowed();
+ return $this->aAllowedIDs;
+ }
+
+ /**
+ * Check if the speficied stimulus is allowed for the set of objects
+ * @return UR_ALLOWED_YES, UR_ALLOWED_NO or UR_ALLOWED_DEPENDS
+ */
+ public function IsAllowed()
+ {
+ $sClass = $this->oFilter->GetClass();
+ $oSet = new DBObjectSet($this->oFilter);
+ $iActionAllowed = UserRights::IsActionAllowed($sClass, $oSet, $this->iActionCode);
+ if ($iActionAllowed == UR_ALLOWED_DEPENDS)
+ {
+ // Check for each object if the action is allowed or not
+ $this->aAllowedIDs = array();
+ $oSet->Rewind();
+ $this->iAllowedCount = 0;
+ while($oObj = $oSet->Fetch())
+ {
+ $oObjSet = DBObjectSet::FromArray($sClass, array($oObj));
+ if (UserRights::IsActionAllowed($sClass, $this->iActionCode, $oObjSet) == UR_ALLOWED_NO)
+ {
+ $this->aAllowedIDs[$oObj->GetKey()] = false;
+ }
+ else
+ {
+ // Assume UR_ALLOWED_YES, since there is just one object !
+ $this->aAllowedIDs[$oObj->GetKey()] = true;
+ $this->iAllowedCount++;
+ }
+ }
+ }
+ else if ($iActionAllowed == UR_ALLOWED_YES)
+ {
+ $this->iAllowedCount = $oSet->Count();
+ $this->aAllowedIDs = array(); // Optimization: not filled when Ok for all objects
+ }
+ else // UR_ALLOWED_NO
+ {
+ $this->iAllowedCount = 0;
+ $this->aAllowedIDs = array();
+ }
+ return $iActionAllowed;
+ }
+}
+
+/**
+ * Helper class to get the number/list of items for which a given stimulus can be applied (allowed & possible)
+ */
+class StimulusChecker extends ActionChecker
+{
+ var $sState = null;
+
+ public function __construct(DBObjectSearch $oFilter, $sState, $iStimulusCode)
+ {
+ parent::__construct($oFilter, $iStimulusCode);
+ $this->sState = $sState;
+ }
+
+ /**
+ * Check if the speficied stimulus is allowed for the set of objects
+ * @return UR_ALLOWED_YES, UR_ALLOWED_NO or UR_ALLOWED_DEPENDS
+ */
+ public function IsAllowed()
+ {
+ $sClass = $this->oFilter->GetClass();
+ $oSet = new DBObjectSet($this->oFilter);
+ $iActionAllowed = UserRights::IsStimulusAllowed($sClass, $oSet, $this->iActionCode);
+ if ($iActionAllowed == UR_ALLOWED_NO)
+ {
+ $this->iAllowedCount = 0;
+ $this->aAllowedIDs = array();
+ }
+ else // Even if UR_ALLOWED_YES, we need to check if each object is in the appropriate state
+ {
+ // Hmmm, may not be needed right now because we limit the "multiple" action to object in
+ // the same state... may be useful later on if we want to extend this behavior...
+
+ // Check for each object if the action is allowed or not
+ $this->aAllowedIDs = array();
+ $oSet->Rewind();
+ $iAllowedCount = 0;
+ $iActionAllowed = UR_ALLOWED_DEPENDS;
+ while($oObj = $oSet->Fetch())
+ {
+ $aTransitions = $oObj->EnumTransitions();
+ if (array_key_exists($this->iActionCode, $aTransitions))
+ {
+ // Temporary optimization possible: since the current implementation
+ // of IsActionAllowed does not perform a 'per instance' check, we could
+ // skip this second validation phase and assume it would return UR_ALLOWED_YES
+ $oObjSet = DBObjectSet::FromArray($sClass, array($oObj));
+ if (UserRights::IsActionAllowed($sClass, $this->iActionCode, $oObjSet) == UR_ALLOWED_NO)
+ {
+ $this->aAllowedIDs[$oObj->GetKey()] = false;
+ }
+ else
+ {
+ // Assume UR_ALLOWED_YES, since there is just one object !
+ $this->aAllowedIDs[$oObj->GetKey()] = true;
+ $this->iState = $oObj->GetState();
+ $this->iAllowedCount++;
+ }
+ }
+ else
+ {
+ $this->aAllowedIDs[$oObj->GetKey()] = false;
+ }
+ }
+ }
+
+ if ($this->iAllowedCount == $oSet->Count())
+ {
+ $iActionAllowed = UR_ALLOWED_YES;
+ }
+ if ($this->iAllowedCount == 0)
+ {
+ $iActionAllowed = UR_ALLOWED_NO;
+ }
+
+ return $iActionAllowed;
+ }
+
+ public function GetState()
+ {
+ return $this->iState;
+ }
+}
?>
diff --git a/css/light-grey.css b/css/light-grey.css
index c5b8862cc..f8afe7a3e 100644
--- a/css/light-grey.css
+++ b/css/light-grey.css
@@ -126,9 +126,9 @@ th.headerSortDown {
td {
font-family: Tahoma, Verdana, Arial, Helvetica;
- font-size: 8pt;
+ font-size: 12px;
color:#696969;
- background-color: #ffffff;
+ nobackground-color: #ffffff;
padding: 0px;
}
@@ -142,7 +142,7 @@ td.label {
font-family: Tahoma, Verdana, Arial, Helvetica;
font-size: 12px;
color: #000000;
- background-color:#f6f6f6;
+ nobackground-color:#f6f6f6;
padding: 0.25em;
font-weight:bold;
}
@@ -938,3 +938,29 @@ span.form_validation {
vertical-align:middle;
text-align:center;
}
+.mono_value {
+ display: inline-block;
+ background-color: #3c3;
+ color: #fff;
+ font-weight:bold;
+ padding: 3px;
+ padding-left: 5px;
+ padding-right: 5px;
+ margin-left:3px;
+ -moz-border-radius: 10px;
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+}
+.multi_values {
+ display: inline-block;
+ background-color: #c33;
+ color: #fff;
+ font-weight:bold;
+ padding: 3px;
+ padding-left: 5px;
+ padding-right: 5px;
+ margin-left:3px;
+ -moz-border-radius: 10px;
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+}
\ No newline at end of file
diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php
index dddc5dfc0..cb3ab7a24 100644
--- a/dictionaries/dictionary.itop.ui.php
+++ b/dictionaries/dictionary.itop.ui.php
@@ -885,5 +885,26 @@ When associated with a trigger, each action is given an "order" number, specifyi
'Portal:Attachment_No_To_Ticket_Name' => 'Attachment #%1$d to %2$s (%3$s)',
'Enum:Undefined' => 'Undefined',
'UI:DurationForm_Days_Hours_Minutes_Seconds' => '%1$s Days %2$s Hours %3$s Minutes %4$s Seconds',
+ 'UI:ModifyAllPageTitle' => 'Modify All',
+ 'UI:Modify_N_ObjectsOf_Class' => 'Modifying %1$d objects of class %2$s',
+ 'UI:Modify_M_ObjectsOf_Class_OutOf_N' => 'Modifying %1$d objects of class %2$s out of %3$d',
+ 'UI:Menu:ModifyAll' => 'Modify...',
+ 'UI:Button:ModifyAll' => 'Modify All',
+ 'UI:Button:PreviewModifications' => 'Preview Modifications >>',
+ 'UI:ModifiedObject' => 'Object Modified',
+ 'UI:BulkModifyStatus' => 'Operation',
+ 'UI:BulkModifyStatus+' => 'Status of the operation',
+ 'UI:BulkModifyErrors' => 'Errors (if any)',
+ 'UI:BulkModifyErrors+' => 'Errors preventing the modification',
+ 'UI:BulkModifyStatusOk' => 'Ok',
+ 'UI:BulkModifyStatusError' => 'Error',
+ 'UI:BulkModifyStatusModified' => 'Modified',
+ 'UI:BulkModifyStatusSkipped' => 'Skipped',
+ 'UI:BulkModify_Count_DistinctValues' => '%1$d distinct values:',
+ 'UI:BulkModify:Value_Exists_N_Times' => '%1$s, %2$d time(s)',
+ 'UI:BulkModify:N_MoreValues' => '%1$d more values...',
+ 'UI:AttemptingToSetAReadOnlyAttribute_Name' => 'Attempting to set the read-only field: %1$s',
+ 'UI:FailedToApplyStimuli' => 'The action has failed.',
+ 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: Modifying %2$d objects of class %3$s',
));
?>
diff --git a/images/transp-lock.png b/images/transp-lock.png
index 91bb10cfc1a6f32a99cf2d6357491e5ec202078d..5628d0daf8f8b9abc5f2b85a1e72c0427cc1a12f 100644
GIT binary patch
delta 302
zcmV+}0nz@31C|4jF%Je}OGiWi{{a60|De66laVeU2o@9@FcLq+MUhr0fAL8~K~y-)
zt&|~>!axv3Uj|OF0-p;|5eW7G$cP9`I}~R^;W{E>4zL%9K%}@pd;}+esukOlnxrVG
z-PtE6`LAZWrzb1Rk{A&=0DE8qd_Modify All...
\n");
+
+ DisplayMultipleSelectionForm($oP, $oFilter, 'form_for_modify_all', $oChecker);
+ break;
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ case 'form_for_modify_all': // Form to modify multiple objects (bulk modify)
+ $sFilter = utils::ReadParam('filter', '');
+ $sClass = utils::ReadParam('class', '');
+ $aSelectedObj = utils::ReadParam('selectObject', array());
+ if (count($aSelectedObj) > 0)
+ {
+ $iAllowedCount = count($aSelectedObj);
+ $sSelectedObj = implode(',', $aSelectedObj);
+
+ $sOQL = "SELECT $sClass WHERE id IN (".$sSelectedObj.")";
+ $oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL));
+
+ // Compute the distribution of the values for each field to determine which of the "scalar" fields are homogenous
+ $aList = MetaModel::FlattenZlist(MetaModel::GetZListItems($sClass, 'details'));
+ $aValues = array();
+ foreach($aList as $sAttCode)
+ {
+ $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
+ if ($oAttDef->IsScalar())
+ {
+ $aValues[$sAttCode] = array();
+ }
+ }
+ while($oObj = $oSet->Fetch())
+ {
+ foreach($aList as $sAttCode)
+ {
+ $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
+ if ($oAttDef->IsScalar() && $oAttDef->IsWritable())
+ {
+ $currValue = $oObj->Get($sAttCode);
+ if (is_object($currValue)) continue; // Skip non scalar values...
+ if(!array_key_exists($currValue, $aValues[$sAttCode]))
+ {
+ $aValues[$sAttCode][$currValue] = array('count' => 1, 'display' => $oObj->GetAsHTML($sAttCode));
+ }
+ else
+ {
+ $aValues[$sAttCode][$currValue]['count']++;
+ }
+ }
+ }
+ }
+ // Now create an object that has values for the homogenous values only
+ $oDummyObj = new $sClass(); // @@ What if the class is abstract ?
+ $aComments = array();
+ function MyComparison($a, $b) // Sort descending
+ {
+ if ($a['count'] == $b['count'])
+ {
+ return 0;
+ }
+ return ($a['count'] > $b['count']) ? -1 : 1;
+ }
+
+ $iFormId = cmdbAbstractObject::GetNextFormId(); // Identifier that prefixes all the form fields
+ $sReadyScript = '';
+ $aDependsOn = array();
+ $sFormPrefix = '2_';
+ foreach($aList as $sAttCode)
+ {
+ $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
+ $aPrerequisites = MetaModel::GetPrequisiteAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one
+ if (count($aPrerequisites) > 0)
+ {
+ // When 'enabling' a field, all its prerequisites must be enabled too
+ $sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aPrerequisites)."']";
+ $oP->add_ready_script("$('#enable_{$sFormPrefix}{$sAttCode}').bind('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, true); } );\n");
+ }
+ $aDependents = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one
+ if (count($aDependents) > 0)
+ {
+ // When 'disabling' a field, all its dependent fields must be disabled too
+ $sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aDependents)."']";
+ $oP->add_ready_script("$('#enable_{$sFormPrefix}{$sAttCode}').bind('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, false); } );\n");
+ }
+ if ($oAttDef->IsScalar() && $oAttDef->IsWritable())
+ {
+ if ($oAttDef->GetEditClass() == 'One Way Password')
+ {
+
+ $sTip = "Unknown values";
+ $sReadyScript .= "$('#multi_values_$sAttCode').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );";
+
+ $oDummyObj->Set($sAttCode, null);
+ $aComments[$sAttCode] = '';
+ $aComments[$sAttCode] .= '";
+ $index = 0;
+ foreach($aMultiValues as $sCurrValue => $aVal)
+ {
+ $sDisplayValue = empty($aVal['display']) ? ''.Dict::S('Enum:Undefined').'' : str_replace(array("\n", "\r"), " ", $aVal['display']);
+ $sTip .= "
'.($bResult ? '': implode('
', $aErrors)).'
', + '@class' => $sCSSClass, + ); + if ($bResult && (!$bPreview)) + { + $oObj->DBUpdateTracked($oMyChange); + } + } + $oP->Table($aHeaders, $aRows); + if ($bPreview) + { + // Form to submit: + $oP->add("\n"); + } + else + { + $oP->add("\n"); + } + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'new': // Form to create a new object $sClass = utils::ReadParam('class', ''); $sStateCode = utils::ReadParam('state', ''); $bCheckSubClass = utils::ReadParam('checkSubclass', true); @@ -737,14 +1067,6 @@ try { throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); } - // Note: code duplicated to the case 'apply_modify' when a data integrity issue has been found - $oP->add_linked_script("../js/json.js"); - $oP->add_linked_script("../js/forms-json-utils.js"); - $oP->add_linked_script("../js/wizardhelper.js"); - $oP->add_linked_script("../js/wizard.utils.js"); - $oP->add_linked_script("../js/linkswidget.js"); - $oP->add_linked_script("../js/extkeywidget.js"); - $oP->add_linked_script("../js/jquery.blockUI.js"); $aArgs = utils::ReadParam('default', array()); $aContext = $oAppContext->GetAsHash(); @@ -840,7 +1162,9 @@ try } break; - case 'apply_modify': + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'apply_modify': // Applying the modifications to an existing object $sClass = utils::ReadPostedParam('class', ''); $sClassLabel = MetaModel::GetName($sClass); $id = utils::ReadPostedParam('id', ''); @@ -894,14 +1218,6 @@ try $bDisplayDetails = false; // Found issues, explain and give the user a second chance // - // Note: code duplicated from the case 'modify' - $oP->add_linked_script("../js/json.js"); - $oP->add_linked_script("../js/forms-json-utils.js"); - $oP->add_linked_script("../js/wizardhelper.js"); - $oP->add_linked_script("../js/wizard.utils.js"); - $oP->add_linked_script("../js/linkswidget.js"); - $oP->add_linked_script("../js/extkeywidget.js"); - $oP->add_linked_script("../js/jquery.blockUI.js"); $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetName(), $sClassLabel)); $oP->add("".Dict::Format('UI:BulkModify_Count_DistinctValues', count($aValues[$sAttCode]))."
'.implode('
',$aErrors)."\n"; + } + } + else + { + $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); + $sError = ''.Dict::S('UI:FailedToApplyStimuli')."
\n"; + } + } + else + { + $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); + $sError = '
'.implode('
',$aErrors)."\n"; + } + } + } + catch(Exception $e) + { + $sError = $e->getMessage(); + $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); + } + $aRows[] = array( + 'object' => $oObj->GetHyperlink(), + 'status' => $sStatus, + 'errors' => $sError, + ); + } + $oP->Table($aHeaders, $aRows); + // Back to the list + $sURL = "./UI.php?operation=search&filter=$sFilter&".$oAppContext->GetForLink(); + $oP->add(''); + } + break; + + case 'stimulus': // Form displayed when applying a stimulus (state change) $sClass = utils::ReadParam('class', ''); $id = utils::ReadParam('id', ''); $sStimulus = utils::ReadParam('stimulus', ''); @@ -1094,13 +1726,6 @@ try $aTransition = $aTransitions[$sStimulus]; $sTargetState = $aTransition['target_state']; $aTargetStates = MetaModel::EnumStates($sClass); - $oP->add_linked_script("../js/json.js"); - $oP->add_linked_script("../js/forms-json-utils.js"); - $oP->add_linked_script("../js/wizardhelper.js"); - $oP->add_linked_script("../js/wizard.utils.js"); - $oP->add_linked_script("../js/linkswidget.js"); - $oP->add_linked_script("../js/extkeywidget.js"); - $oP->add_linked_script("../js/jquery.blockUI.js"); $oP->add("$sActionDetails
\n"); - //$oP->p(Dict::Format('UI:Apply_Stimulus_On_Object_In_State_ToTarget_State', $sActionLabel, $oObj->GetName(), $oObj->GetStateLabel(), $sTargetState)); - //$oP->add("', $aErrors)); + } } $oObj->DisplayDetails($oP); } @@ -1234,7 +1877,9 @@ EOF } break; - case 'modify_links': + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'modify_links': // ?? still used ?? $sClass = utils::ReadParam('class', ''); $sLinkAttr = utils::ReadParam('link_attr', ''); $sTargetClass = utils::ReadParam('target_class', ''); @@ -1249,7 +1894,9 @@ EOF $oWizard->Display($oP, array('StartWithAdd' => $bAddObjects)); break; - case 'do_modify_links': + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'do_modify_links': // ?? still used ?? $aLinks = utils::ReadPostedParam('linkId', array()); $sLinksToRemove = trim(utils::ReadPostedParam('linksToRemove', '')); $aLinksToRemove = array(); @@ -1341,7 +1988,9 @@ EOF $oObj->DisplayDetails($oP); break; - case 'swf_navigator': + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'swf_navigator': // Graphical display of the relations "impact" / "depends on" $sClass = utils::ReadParam('class', ''); $id = utils::ReadParam('id', 0); $sRelation = utils::ReadParam('relation', 'impact'); @@ -1400,12 +2049,16 @@ EOF $oP->SetCurrentTab(''); break; - case 'cancel': + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'cancel': // An action was cancelled $oP->set_title(Dict::S('UI:OperationCancelled')); $oP->add('