浏览代码

Implementation of bulk modify and bulk apply stimulus... to be tested !

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@1145 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 14 年之前
父节点
当前提交
56daa0a515

+ 89 - 33
application/cmdbabstract.class.inc.php

@@ -76,7 +76,15 @@ interface iDisplay
 abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
 {
 	protected $m_iFormId; // The ID of the form used to edit the object (when in edition mode !)
+	static $iGlobalFormId = 1;
 
+	/**
+	 * returns what will be the next ID for the forms
+	 */
+	public static function GetNextFormId()
+	{
+		return 1 + self::$iGlobalFormId;
+	}
 	public static function GetUIPage()
 	{
 		return '../pages/UI.php';
@@ -380,7 +388,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
 							$aReasons = array();
 							$iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
 							$sSynchroIcon = '';
-							if ($iSynchroFlags & OPT_ATT_READONLY)
+							if ($iSynchroFlags & OPT_ATT_SLAVE)
 							{
 								$sSynchroIcon = "&nbsp;<img id=\"synchro_$iInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
 								$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' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$iListId}', this.checked);\"></input>", 'description' => Dict::S('UI:SelectAllToggle+'));
+				$aAttribs['form::select'] = array('label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$iListId}:not(:disabled)', this.checked);\"></input>", 'description' => Dict::S('UI:SelectAllToggle+'));
 			}
 			else
 			{
@@ -596,13 +604,21 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
 			}
 			if ($bSelectMode)
 			{
+				if (array_key_exists('selection_enabled', $aExtraParams) && isset($aExtraParams['selection_enabled'][$oObj->GetKey()]))
+				{
+					$sDisabled = ($aExtraParams['selection_enabled'][$oObj->GetKey()]) ? '' : ' disabled="disabled"';
+				}
+				else
+				{
+					$sDisabled = '';
+				}
 				if ($bSingleSelectMode)
 				{
-					$aRow['form::select'] = "<input type=\"radio\" class=\"selectList{$iListId}\" name=\"selectObject\" value=\"".$oObj->GetKey()."\"></input>";
+					$aRow['form::select'] = "<input type=\"radio\" $sDisabled class=\"selectList{$iListId}\" name=\"selectObject\" value=\"".$oObj->GetKey()."\"></input>";
 				}
 				else
 				{
-				$aRow['form::select'] = "<input type=\"checkBox\" class=\"selectList{$iListId}\" name=\"selectObject[]\" value=\"".$oObj->GetKey()."\"></input>";
+				$aRow['form::select'] = "<input type=\"checkBox\" $sDisabled class=\"selectList{$iListId}\" name=\"selectObject[]\" value=\"".$oObj->GetKey()."\"></input>";
 				}
 			}
 			foreach($aList as $sAttCode)
@@ -1238,6 +1254,7 @@ EOF
 				$sSeconds = "<input title=\"$sHelpText\" type=\"text\" size=\"2\" name=\"attr_{$sFieldPrefix}{$sAttCode}[s]{$sNameSuffix}\" value=\"{$aVal['seconds']}\" id=\"{$iId}_s\"/>";
 				$sHidden = "<input type=\"hidden\" id=\"{$iId}\" value=\"$value\"/>";
 				$sHTMLValue = Dict::Format('UI:DurationForm_Days_Hours_Minutes_Seconds', $sDays, $sHours, $sMinutes, $sSeconds).$sHidden."&nbsp;".$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 .= "<!-- iFlags: $iFlags bMandatory: $bMandatory -->\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] : '&nbsp;';
+						$sInfos = '&nbsp;';
 						$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 = "&nbsp;<img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
 												$sTip = '';
 												foreach($aReasons as $aRow)
@@ -1490,9 +1511,10 @@ EOF
 											}
 
 											// Attribute is read-only
-											$sHTMLValue = $this->GetAsHTML($sAttCode).$sSynchroIcon;
+											$sHTMLValue = $this->GetAsHTML($sAttCode);;
 											$sHTMLValue .= '<input type="hidden" id="'.$sInputId.'" name="attr_'.$sPrefix.$sAttCode.'" value="'.htmlentities($this->Get($sAttCode), ENT_QUOTES, 'UTF-8').'"/>';
 											$aFieldsMap[$sAttCode] = $sInputId;
+											$sComments = $sSynchroIcon;
 										}
 										else
 										{
@@ -1503,13 +1525,13 @@ EOF
 											$aFieldsMap[$sAttCode] = $sInputId;
 											
 										}
-										$aVal = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => $sHTMLValue);
+										$aVal = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos);
 									}
 								}
 							}
 							else
 							{
-								$aVal = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => $this->GetAsHTML($sAttCode));			
+								$aVal = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => $this->GetAsHTML($sAttCode), 'comments' => $sComments, 'infos' => $sInfos);			
 							}
 						}
 						if ($aVal != null)
@@ -1548,22 +1570,48 @@ EOF
 			$oPage->add("<input type=\"hidden\" name=\"$sName\" value=\"$value\">\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("<input type=\"hidden\" name=\"id\" value=\"$iKey\">\n");
-			$oPage->add("<input type=\"hidden\" name=\"operation\" value=\"apply_modify\">\n");			
-//			$oPage->add("<button type=\"button\" id=\"btn_cancel_{$sPrefix}\" class=\"action\" onClick=\"BackToDetails('$sClass', $iKey)\"><span>".Dict::S('UI:Button:Cancel')."</span></button>&nbsp;&nbsp;&nbsp;&nbsp;\n");
+			$oPage->add("<input type=\"hidden\" name=\"operation\" value=\"{$sOperation}\">\n");			
 			$oPage->add("<button type=\"button\" class=\"action cancel\"><span>".Dict::S('UI:Button:Cancel')."</span></button>&nbsp;&nbsp;&nbsp;&nbsp;\n");
-			$oPage->add("<button type=\"submit\" class=\"action\"><span>".Dict::S('UI:Button:Apply')."</span></button>\n");
+			$oPage->add("<button type=\"submit\" class=\"action\"><span>{$sApplyButton}</span></button>\n");
 		}
 		else
 		{
 			// The object does not exist in the database it's a creation
-			$oPage->add("<input type=\"hidden\" name=\"operation\" value=\"apply_new\">\n");			
-//			$oPage->add("<button type=\"button\" id=\"btn_cancel_{$sPrefix}\" class=\"action\" onClick=\"BackToDetails('$sClass', $iKey)\"><span>".Dict::S('UI:Button:Cancel')."</span></button>&nbsp;&nbsp;&nbsp;&nbsp;\n");
+			$oPage->add("<input type=\"hidden\" name=\"operation\" value=\"$sOperation\">\n");			
 			$oPage->add("<button type=\"button\" class=\"action cancel\">".Dict::S('UI:Button:Cancel')."</button>&nbsp;&nbsp;&nbsp;&nbsp;\n");
-			$oPage->add("<button type=\"submit\" class=\"action\"><span>".Dict::S('UI:Button:Create')."</span></button>\n");
+			$oPage->add("<button type=\"submit\" class=\"action\"><span>{$sApplyButton}</span></button>\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(
 <<<EOF
 		// Create the object once at the beginning of the page...
-		var oWizardHelper$sPrefix = new WizardHelper('$sClass', '$sPrefix');
+		var oWizardHelper$sPrefix = new WizardHelper('$sClass', '$sPrefix', '$sState');
 		oWizardHelper$sPrefix.SetFieldsMap($sJsonFieldsMap);
 		oWizardHelper$sPrefix.SetFieldsCount($iFieldsCount);
 EOF
@@ -1917,11 +1966,14 @@ EOF
 	 */
 	public function UpdateObject($sFormPrefix = '')
 	{
+		$aErrors = array();
 		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
 		{
 			if ($oAttDef->IsLinkSet() && $oAttDef->IsIndirect())
 			{
-				$aLinks = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", '');
+				$aLinks = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null);
+				if (is_null($aLinks)) continue;
+				
 				$sLinkedClass = $oAttDef->GetLinkedClass();
 				$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
 				$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
@@ -1965,10 +2017,18 @@ EOF
 			else if ($oAttDef->IsWritable())
 			{
 				$iFlags = $this->GetAttributeFlags($sAttCode);
-				if ($iFlags & (OPT_ATT_HIDDEN | OPT_ATT_READONLY))
+				if ( $iFlags & (OPT_ATT_HIDDEN | OPT_ATT_READONLY))
 				{
 					// Non-visible, or read-only attribute, do nothing
 				}
+				elseif($iFlags & OPT_ATT_SLAVE)
+				{
+					$value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null);
+					if (!is_null($value) && ($value != $this->Get($sAttCode)))
+					{
+						$aErrors[] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel());
+					}
+				}
 				elseif ($oAttDef->GetEditClass() == 'Document')
 				{
 					// There should be an uploaded file with the named attr_<attCode>
@@ -1993,14 +2053,9 @@ EOF
 				elseif ($oAttDef->GetEditClass() == 'Duration')
 				{
 					$rawValue = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null);
-					if (!is_array($rawValue))
-					{
-						$iValue = null;
-					}
-					else
-					{
-						$iValue = (((24*$rawValue['d'])+$rawValue['h'])*60 +$rawValue['m'])*60 + $rawValue['s'];
-					}		
+					if (!is_array($rawValue)) continue;
+
+					$iValue = (((24*$rawValue['d'])+$rawValue['h'])*60 +$rawValue['m'])*60 + $rawValue['s'];
 					$this->Set($sAttCode, $iValue);
 					$previousValue = $this->Get($sAttCode);
 					if ($previousValue !== $iValue)
@@ -2023,6 +2078,7 @@ EOF
 				}
 			}
 		}
+		return $aErrors;
 	}
 
 	protected function DBInsertTracked_Internal($bDoNotReload = false)

+ 41 - 1
application/displayblock.class.inc.php

@@ -1092,8 +1092,48 @@ class MenuBlock extends DisplayBlock
 				// many objects in the set, possible actions are: new / modify all / delete all
 				$sUrl = utils::GetAbsoluteUrl();
 				if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext{$sDefault}"); }
-				//if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Modify All...', 'url' => "../pages/$sUIPage?operation=modify_all&filter=$sFilter&$sContext"); }
+				if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:ModifyAll'), 'url' => "../pages/$sUIPage?operation=select_for_modify_all&class=$sClass&filter=$sFilter&sContext"); }
 				if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:BulkDelete'), 'url' => "../pages/$sUIPage?operation=select_for_deletion&filter=$sFilter&$sContext"); }
+
+				// Stimuli
+				$aStates = MetaModel::EnumStates($sClass);
+				if (count($aStates) > 0)
+				{
+					// Life cycle actions may be available... if all objects are in the same state
+					$oSet->Rewind();
+					$aStates = array();
+					while($oObj = $oSet->Fetch())
+					{
+						$aStates[$oObj->GetState()] = $oObj->GetState();
+					}
+					$oSet->Rewind();
+					if (count($aStates) == 1)
+					{
+						// All objects are in the same state...
+						$sState = array_pop($aStates);
+						$aTransitions = Metamodel::EnumTransitions($sClass, $sState);
+						if (count($aTransitions))
+						{
+							$this->AddMenuSeparator($aActions);
+							$aStimuli = Metamodel::EnumStimuli($sClass);
+							foreach($aTransitions as $sStimulusCode => $aTransitionDef)
+							{
+								$oChecker = new StimulusChecker($this->m_oFilter, $sState, $sStimulusCode);
+								$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? $oChecker->IsAllowed() : UR_ALLOWED_NO;
+								switch($iActionAllowed)
+								{
+									case UR_ALLOWED_YES:
+									case UR_ALLOWED_DEPENDS:
+									$aActions[] = array('label' => $aStimuli[$sStimulusCode]->GetLabel(), 'url' => "../pages/UI.php?operation=select_bulk_stimulus&stimulus=$sStimulusCode&state=$sState&class=$sClass&filter=$sFilter&$sContext");
+									break;
+									
+									default:
+									// Do nothing
+								}
+							}
+						}
+					}
+				}
 				$this->AddMenuSeparator($aActions);
 				$aActions[] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("$sUrl?operation=search&filter=$sFilter&$sContext"));
 				$aActions[] = array ('label' => Dict::S('UI:Menu:CSVExport'), 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext");

+ 15 - 0
application/itopwebpage.class.inc.php

@@ -399,6 +399,21 @@ EOF
 		}
 		
 		var oUserPreferences = $sUserPrefs;
+
+		// For disabling the CKEditor at init time when the corresponding textarea is disabled !
+		CKEDITOR.plugins.add( 'disabler',
+		{
+		    init : function( editor )
+		    {
+		        editor.on( 'instanceReady', function(e)
+		        {
+		        	e.removeListener();
+			        $('#'+ editor.name).trigger('update');
+                });
+		    }
+		    
+		});
+
 EOF
 );
 		

+ 24 - 16
application/ui.extkeywidget.class.inc.php

@@ -145,6 +145,7 @@ class UIExtKeyWidget
 <<<EOF
 		oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sClass}', '{$this->sAttCode}', '{$this->sNameSuffix}', $sSelectMode, $sWizHelper);
 		oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
+		$('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
 
 EOF
 );
@@ -168,20 +169,22 @@ EOF
 	
 			// the input for the auto-complete
 			$sHTMLValue = "<input count=\"".count($this->aAllowedValues)."\" type=\"text\" id=\"label_$this->iId\" size=\"30\" maxlength=\"$iFieldSize\" value=\"$sDisplayValue\"/>&nbsp;";
-			$sHTMLValue .= "<a class=\"no-arrow\" href=\"javascript:oACWidget_{$this->iId}.Search();\"><img style=\"border:0;vertical-align:middle;\" src=\"../images/mini_search.gif\" /></a>&nbsp;";
+			$sHTMLValue .= "<a class=\"no-arrow\" href=\"javascript:oACWidget_{$this->iId}.Search();\"><img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;\" src=\"../images/mini_search.gif\" /></a>&nbsp;";
 	
 			// another hidden input to store & pass the object's Id
 			$sHTMLValue .= "<input type=\"hidden\" id=\"$this->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(
 <<<EOF
 		oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sClass}', '{$this->sAttCode}', '{$this->sNameSuffix}', $sSelectMode, $sWizHelper);
 		oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
 		$('#label_$this->iId').autocomplete('./ajax.render.php', { scroll:true, minChars:{$iMinChars}, formatItem:formatItem, autoFill:false, matchContains:true, keyHolder:'#{$this->iId}', extraParams:{operation:'autocomplete', sclass:'{$this->sClass}',attCode:'{$this->sAttCode}'}});
 		$('#label_$this->iId').blur(function() { $(this).search(); } );
 		$('#label_$this->iId').result( function(event, data, formatted) { OnAutoComplete('{$this->iId}', event, data, formatted); } );
-		$('#ac_dlg_$this->iId').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, title: '{$this->sTitle}', resizeStop: oACWidget_{$this->iId}.UpdateSizes, close: oACWidget_{$this->iId}.OnClose });
+		$('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
+		$('#ac_dlg_$this->iId').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, title: '$sDialogTitle', resizeStop: oACWidget_{$this->iId}.UpdateSizes, close: oACWidget_{$this->iId}.OnClose });
 
 EOF
 );
@@ -190,7 +193,7 @@ EOF
 		}
 		if ($bCreate)
 		{
-			$sHTMLValue .= "<a class=\"no-arrow\" href=\"javascript:oACWidget_{$this->iId}.CreateObject();\"><img style=\"border:0;vertical-align:middle;\" src=\"../images/mini_add.gif\" /></a>&nbsp;";
+			$sHTMLValue .= "<a class=\"no-arrow\" href=\"javascript:oACWidget_{$this->iId}.CreateObject();\"><img id=\"mini_add_{$this->iId}\" style=\"border:0;vertical-align:middle;\" src=\"../images/mini_add.gif\" /></a>&nbsp;";
 			$oPage->add_at_the_end('<div id="ajax_'.$this->iId.'"></div>');
 		}
 		$sHTMLValue .= "<span id=\"v_{$this->iId}\"></span>";
@@ -259,11 +262,12 @@ EOF
 	 */
 	public function GetObjectCreationForm(WebPage $oPage)
 	{
+		$sDialogTitle = addslashes($this->sTitle);
 		$oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">');
 		$oPage->add("<h1>".MetaModel::GetClassIcon($this->sTargetClass)."&nbsp;".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sTargetClass))."</h1>\n");
 	 	cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, null, array(), array('formPrefix' => $this->iId, 'noRelations' => true));	
 		$oPage->add('</div></div></div>');
-			$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: $(window).width()*0.8, height: 'auto', autoOpen: false, modal: true, title: '$this->sTitle'});\n");
+			$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: $(window).width()*0.8, height: 'auto', autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
 		$oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');");
 		$oPage->add_ready_script("$('#dcr_{$this->iId} form').bind('submit.uilinksWizard', oACWidget_{$this->iId}.DoCreateObject);");
 	}
@@ -274,17 +278,21 @@ EOF
 	public function DoCreateObject($oPage)
 	{
 		$oObj = MetaModel::NewObject($this->sTargetClass);
-		$oObj->UpdateObject($this->sFormPrefix.$this->iId);
-		$oMyChange = MetaModel::NewObject("CMDBChange");
-		$oMyChange->Set("date", time());
-		$sUserString = CMDBChange::GetCurrentUserName();
-		$oMyChange->Set("userinfo", $sUserString);
-		$iChangeId = $oMyChange->DBInsert();
-		$oObj->DBInsertTracked($oMyChange);
-	
-		return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
-		
-		//return array('name' => 'test', 'id' => '42');
+		$aErrors = $oObj->UpdateObject($this->sFormPrefix.$this->iId);
+		if (count($aErrors) == 0)
+		{
+			$oMyChange = MetaModel::NewObject("CMDBChange");
+			$oMyChange->Set("date", time());
+			$sUserString = CMDBChange::GetCurrentUserName();
+			$oMyChange->Set("userinfo", $sUserString);
+			$iChangeId = $oMyChange->DBInsert();
+			$oObj->DBInsertTracked($oMyChange);
+			return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
+		}
+		else
+		{
+			return array('name' => implode(' ', $aErrors), 'id' => 0);		
+		}
 	}
 }
 ?>

+ 4 - 3
application/ui.htmleditorwidget.class.inc.php

@@ -61,14 +61,14 @@ class UIHTMLEditorWidget
 		$sHelpText = $this->m_sHelpText;
 		$sValidationField = $this->m_sValidationField;
 
-		$sHtmlValue = "<table><tr><td><textarea class=\"htmlEditor\" title=\"$sHelpText\" name=\"attr_{$this->m_sFieldPrefix}{$sCode}\" rows=\"14\" cols=\"110\" id=\"$iId\">$sValue</textarea></td><td>$sValidationField</td></tr></table>";
+		$sHtmlValue = "<table><tr><td><textarea class=\"htmlEditor\" title=\"$sHelpText\" name=\"attr_{$this->m_sFieldPrefix}{$sCode}\" rows=\"10\" cols=\"10\" id=\"$iId\">$sValue</textarea></td><td>$sValidationField</td></tr></table>";
 
 		// 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;
 	}

+ 17 - 2
application/ui.passwordwidget.class.inc.php

@@ -57,13 +57,28 @@ class UIPasswordWidget
 		$sChangedValue = (($sPasswordValue != '*****') || ($sConfirmPasswordValue != '*****')) ? 1 : 0;
 		$sHtmlValue = '';
 		$sHtmlValue = '<input type="password" maxlength="255" name="attr_'.$sCode.'" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/>&nbsp;<span class="form_validation" id="v_'.$this->iId.'"></span><br/>';
-		$sHtmlValue .= '<input type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'_confirmed"/> '.Dict::S('UI:PasswordConfirm').' <input type="button" value="'.Dict::S('UI:Button:ResetPassword').'" onClick="ResetPwd(\''.$this->iId.'\');">';
+		$sHtmlValue .= '<input type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'_confirmed"/> '.Dict::S('UI:PasswordConfirm').' <input id="'.$this->iId.'_reset" type="button" value="'.Dict::S('UI:Button:ResetPassword').'" onClick="ResetPwd(\''.$this->iId.'\');">';
 		$sHtmlValue .= '<input type="hidden" id="'.$this->iId.'_changed" name="attr_'.$sCode.'_changed" value="'.$sChangedValue.'"/>';
 
 		$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;
 	}
 }

+ 3 - 0
application/webpage.class.inc.php

@@ -258,6 +258,9 @@ class WebPage
 			{
 				$sHtml .= "<td class=\"label\">".$aAttrib['label']."</td><td>".$aAttrib['value']."</td>\n";
 			}
+			$sComment = (isset($aAttrib['comments'])) ? $aAttrib['comments'] : '&nbsp;';
+			$sInfo = (isset($aAttrib['infos'])) ? $aAttrib['infos'] : '&nbsp;';
+			$sHtml .= "<td>$sComment</td><td>$sInfo</td>\n";
     		$sHtml .= "</tr>\n";
 		}
 		$sHtml .= "</tbody>\n";

+ 5 - 0
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;
 	}
 	

+ 1 - 1
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);
 				}

+ 103 - 0
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;
 }
 
 ?>

+ 13 - 2
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());

+ 165 - 0
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;
+	}		
+}
 ?>

+ 29 - 3
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;
+}

+ 21 - 0
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',
 ));
 ?>

二进制
images/transp-lock.png


+ 21 - 0
js/extkeywidget.js

@@ -45,6 +45,7 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
 	
 	this.Search = function()
 	{
+		if($('#'+me.id).attr('disabled')) return; // Disabled, do nothing
 		$('#ac_dlg_'+me.id).dialog('open');
 		this.UpdateSizes();
 		this.UpdateButtons();
@@ -189,6 +190,7 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
 	
 	this.CreateObject = function(oWizHelper)
 	{
+		if($('#'+me.id).attr('disabled')) return; // Disabled, do nothing
 		// Query the server to get the form to create a target object
 		if (me.bSelectMode)
 		{
@@ -309,4 +311,23 @@ function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper)
 		}
 		return false; // do NOT submit the form
 	}
+	
+	this.Update = function()
+	{
+		if ($('#'+me.id).attr('disabled'))
+		{
+			$('#v_'+me.id).html('');
+			$('#label_'+me.id).attr('disabled', 'disabled');
+			$('#label_'+me.id).css({'background': 'transparent'});
+			$('#mini_add_'+me.id).hide();
+			$('#mini_search_'+me.id).hide();
+		}
+		else
+		{
+			$('#label_'+me.id).attr('disabled', '');
+			$('#label_'+me.id).css({'background': '#fff url(../images/ac-background.gif) no-repeat right'});
+			$('#mini_add_'+me.id).show();
+			$('#mini_search_'+me.id).show();
+		}
+	}
 }

+ 24 - 17
js/forms-json-utils.js

@@ -174,26 +174,33 @@ function ReportFieldValidationStatus(sFieldId, sFormId, bValid)
 function ValidateField(sFieldId, sPattern, bMandatory, sFormId, nullValue)
 {
 	var bValid = true;
-	var currentVal = $('#'+sFieldId).val();
-
-	if (currentVal == '$$NULL$$') // Convention to indicate a non-valid value since it may have to be passed as text
+	if ($('#'+sFieldId).attr('disabled'))
 	{
-		bValid = false;
-	}
-	else if (bMandatory && (currentVal == nullValue))
-	{
-		bValid = false;
+		bValid = true; // disabled fields are not checked
 	}
-	else if (currentVal == nullValue)
-	{
-		// An empty field is Ok...
-		bValid = true;
-	}
-	else if (sPattern != '')
+	else
 	{
-		re = new RegExp(sPattern);
-		//console.log('Validating field: '+sFieldId + ' current value: '+currentVal + ' pattern: '+sPattern );
-		bValid = re.test(currentVal);
+		var currentVal = $('#'+sFieldId).val();
+
+		if (currentVal == '$$NULL$$') // Convention to indicate a non-valid value since it may have to be passed as text
+		{
+			bValid = false;
+		}
+		else if (bMandatory && (currentVal == nullValue))
+		{
+			bValid = false;
+		}
+		else if (currentVal == nullValue)
+		{
+			// An empty field is Ok...
+			bValid = true;
+		}
+		else if (sPattern != '')
+		{
+			re = new RegExp(sPattern);
+			//console.log('Validating field: '+sFieldId + ' current value: '+currentVal + ' pattern: '+sPattern );
+			bValid = re.test(currentVal);
+		}
 	}
 	ReportFieldValidationStatus(sFieldId, sFormId, bValid);
 	//console.log('Form: '+sFormId+' Validating field: '+sFieldId + ' current value: '+currentVal+' pattern: '+sPattern+' result: '+bValid );

+ 72 - 0
js/utils.js

@@ -212,3 +212,75 @@ function CheckAll(sSelector, bValue)
 		this.checked = value;
 	});
 }
+
+/**
+ * Toggle (enabled/disabled) the specified field of a form
+ */
+function ToogleField(value, field_id)
+{
+	if (value)
+	{
+		$('#'+field_id).removeAttr('disabled');
+	}
+	else
+	{
+		$('#'+field_id).attr('disabled', 'disabled');
+	}
+	$('#'+field_id).trigger('update');
+	$('#'+field_id).trigger('validate');
+}
+
+/**
+ * For the fields that cannot be visually disabled, they can be blocked
+ * @return
+ */
+function BlockField(field_id, bBlocked)
+{
+	if (bBlocked)
+	{
+		$('#'+field_id).block({ message: ' ** disabled ** '});
+	}
+	else
+	{
+		$('#'+field_id).unblock();
+	}
+}
+
+/**
+ * Updates (enables/disables) a "duration" field
+ */
+function ToggleDurationField(field_id)
+{
+	// Toggle all the subfields that compose the "duration" input
+	aSubFields = new Array('d', 'h', 'm', 's');
+	
+	if ($('#'+field_id).attr('disabled'))
+	{
+		for(var i=0; i<aSubFields.length; i++)
+		{
+			$('#'+field_id+'_'+aSubFields[i]).attr('disabled', 'disabled');
+		}
+	}
+	else
+	{
+		for(var i=0; i<aSubFields.length; i++)
+		{
+			$('#'+field_id+'_'+aSubFields[i]).removeAttr('disabled');
+		}
+	}
+}
+
+/**
+ * PropagateCheckBox
+ */
+function PropagateCheckBox(bCurrValue, aFieldsList, bCheck)
+{
+	if (bCurrValue == bCheck)
+	{
+		for(var i=0;i<aFieldsList.length;i++)
+		{
+			$('#enable_'+aFieldsList[i]).attr('checked', bCheck);
+			ToogleField(bCheck, aFieldsList[i]);
+		}
+	}
+}

+ 12 - 5
js/wizardhelper.js

@@ -17,7 +17,7 @@ if (!Array.prototype.indexOf) // Emulation of the indexOf function for IE and ol
 	};
 }
 
-function WizardHelper(sClass, sFormPrefix)
+function WizardHelper(sClass, sFormPrefix, sState)
 {
 	this.m_oData = { 'm_sClass' : '',
 					 'm_oFieldsMap': {},
@@ -27,7 +27,8 @@ function WizardHelper(sClass, sFormPrefix)
 					 'm_oDefaultValue': {},
 					 'm_oAllowedValues': {},
 					 'm_iFieldsCount' : 0,
-					 'm_sFormPrefix' : sFormPrefix
+					 'm_sFormPrefix' : sFormPrefix,
+					 'm_sState': sState
 					};
 	this.m_oData.m_sClass = sClass;
 	
@@ -92,10 +93,16 @@ function WizardHelper(sClass, sFormPrefix)
 		// Set the full HTML for the input field
 		for(i=0; i<this.m_oData.m_aAllowedValuesRequested.length; i++)
 		{
-			sAttCode = this.m_oData.m_aAllowedValuesRequested[i];
-			sFieldId = this.m_oData.m_oFieldsMap[sAttCode];
+			var sAttCode = this.m_oData.m_aAllowedValuesRequested[i];
+			var sFieldId = this.m_oData.m_oFieldsMap[sAttCode];
+			var bDisabled = $('#'+sFieldId).attr('disabled');
 			//console.log('Setting #field_'+sFieldId+' to: '+this.m_oData.m_oAllowedValues[sAttCode]);
 			$('#field_'+sFieldId).html(this.m_oData.m_oAllowedValues[sAttCode]);
+			if (bDisabled)
+			{
+				$('#'+sFieldId).attr('disabled', 'disabled');
+				//$('#'+sFieldId).trigger('update'); // Propagate the disable
+			}
 			aRefreshed.push(sFieldId);
 		}
 		// Set the actual value of the input
@@ -113,7 +120,7 @@ function WizardHelper(sClass, sFormPrefix)
 		// For each "refreshed" field, asynchronously trigger a change in case there are dependent fields to update
 		for(i=0; i<aRefreshed.length; i++)
 		{
-			var sString = "$('#"+aRefreshed[i]+"').trigger('change');"
+			var sString = "$('#"+aRefreshed[i]+"').trigger('change').trigger('update');"
 			window.setTimeout(sString, 1); // Synchronous 'trigger' does nothing, call it asynchronously
 		}
 	}

文件差异内容过多而无法显示
+ 752 - 120
pages/UI.php


+ 1 - 1
portal/index.php

@@ -274,7 +274,7 @@ function RequestCreationForm($oP, $oUserOrg)
 		$sJsonFieldsMap = json_encode($aFieldsMap);
 		$oP->add_ready_script(
 <<<EOF
-		// Create the object once at the beginning of the page...
+		// Create the object once at the beginning of the page... no state specified => new
 		var oWizardHelper = new WizardHelper('UserRequest', '');
 		oWizardHelper.SetFieldsMap($sJsonFieldsMap);
 		oWizardHelper.SetFieldsCount($iFieldsCount);

部分文件因为文件数量过多而无法显示