From 6df6e4a9ccb26f290606b07cc7240e2531487b4b Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 25 Aug 2011 16:04:58 +0000 Subject: [PATCH] - Allow creation of an ticket in a different initial state via the new 'initial_state_path' attribute. - Support update of CaseLog fields in bulk_modify mode. SVN:trunk[1517] --- application/ajaxwebpage.class.inc.php | 16 +++- application/cmdbabstract.class.inc.php | 109 +++++++++++++++++++---- application/ui.linkswidget.class.inc.php | 2 +- application/webpage.class.inc.php | 4 +- core/dbobject.class.php | 17 ++++ core/metamodel.class.php | 70 +++++++++++++++ dictionaries/dictionary.itop.ui.php | 1 + dictionaries/fr.dictionary.itop.ui.php | 1 + js/wizardhelper.js | 17 ++++ pages/UI.php | 10 +++ pages/ajax.render.php | 23 ++++- 11 files changed, 249 insertions(+), 21 deletions(-) diff --git a/application/ajaxwebpage.class.inc.php b/application/ajaxwebpage.class.inc.php index fb0b59cdd..1ccb602dd 100644 --- a/application/ajaxwebpage.class.inc.php +++ b/application/ajaxwebpage.class.inc.php @@ -193,7 +193,7 @@ EOF if (!empty($this->s_deferred_content)) { echo "\n"; } if (!empty($this->m_sReadyScript)) @@ -229,6 +229,20 @@ EOF parent::add($sHtml); } } + + /** + * Add any text or HTML fragment (identified by an ID) at the end of the body of the page + * This is useful to add hidden content, DIVs or FORMs that should not + * be embedded into each other. + */ + public function add_at_the_end($s_html, $sId = '') + { + if ($sId != '') + { + $this->add_script("$('#{$sId}').remove();"); // Remove any previous instance of the same Id + } + $this->s_deferred_content .= $s_html; + } /** * Adds a script to be executed when the DOM is ready (typical JQuery use) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 275db41dd..7ebb499b8 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -214,7 +214,8 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay { if ($oAttDef instanceof AttributeCaseLog) { - $this->DisplayCaseLog($oPage, $sAttCode, '', '', $bEditMode); + $sComment = (isset($aExtraParams['fieldsComments'][$sAttCode])) ? $aExtraParams['fieldsComments'][$sAttCode] : ''; + $this->DisplayCaseLog($oPage, $sAttCode, $sComment, $sPrefix, $bEditMode); } } @@ -253,7 +254,14 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay $oPage->SetCurrentTab($oAttDef->GetLabel().$sCount); if ($bEditMode) { - $iFlags = $this->GetAttributeFlags($sAttCode); + if ($this->IsNew()) + { + $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); + } + else + { + $iFlags = $this->GetAttributeFlags($sAttCode); + } $sInputId = $this->m_iFormId.'_'.$sAttCode; if (get_class($oAttDef) == 'AttributeLinkedSet') { @@ -448,7 +456,18 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay $sComments = isset($aFieldsComments[$sAttCode]) ? $aFieldsComments[$sAttCode] : ' '; $sInfos = ' '; - $iFlags = $this->GetAttributeFlags($sAttCode); + if ($this->IsNew()) + { + $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); + } + else + { + $iFlags = $this->GetAttributeFlags($sAttCode); + } + if (($iFlags & OPT_ATT_MANDATORY) && $this->IsNew()) + { + $iFlags = $iFlags & ~OPT_ATT_READONLY; // Mandatory fields cannot be read-only when creating an object + } $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0)) { @@ -1728,8 +1747,9 @@ EOF } $aTransitions = $this->EnumTransitions(); - if (count($aTransitions)) + if (!isset($aExtraParams['custom_operation']) && count($aTransitions)) { + // transitions are displayed only for the standard new/modify actions, not for modify_all or any other case... $oSetToCheckRights = DBObjectSet::FromObject($this); $aStimuli = Metamodel::EnumStimuli($sClass); foreach($aTransitions as $sStimulusCode => $aTransitionDef) @@ -1748,9 +1768,31 @@ EOF } $sButtonsPosition = MetaModel::GetConfig()->Get('buttons_position'); - $iTransactionId = utils::GetNewTransactionId(); + $iTransactionId = isset($aExtraParams['transaction_id']) ? $aExtraParams['transaction_id'] : utils::GetNewTransactionId(); $oPage->SetTransactionId($iTransactionId); $oPage->add("
m_iFormId}\" enctype=\"multipart/form-data\" method=\"post\" onSubmit=\"return OnSubmit('form_{$this->m_iFormId}');\">\n"); + $sStatesSelection = ''; + if (!isset($aExtraParams['custom_operation']) && $this->IsNew()) + { + $aInitialStates = MetaModel::EnumInitialStates($sClass); + //$aInitialStates = array('new' => 'foo', 'closed' => 'bar'); + if (count($aInitialStates) > 1) + { + $sStatesSelection = Dict::Format('UI:Create_Class_InState', MetaModel::GetName($sClass)).''; + $oPage->add_ready_script("$('.state_select_{$this->m_iFormId}').change( function() { oWizardHelper$sPrefix.ReloadObjectCreationForm('form_{$this->m_iFormId}', $(this).val()); } );"); + } + } + $sConfirmationMessage = addslashes(Dict::S('UI:NavigateAwayConfirmationMessage')); $oPage->add_ready_script( <<p($sStatesSelection); $oPage->add($sButtons); } @@ -1793,6 +1836,7 @@ EOF if ($sButtonsPosition != 'top') { // bottom or both: display the buttons here + $oPage->p($sStatesSelection); $oPage->add($sButtons); } @@ -1829,6 +1873,7 @@ EOF $sClass = ($oObjectToClone == null) ? $sClass : get_class($oObjectToClone); $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); $aStates = MetaModel::EnumStates($sClass); + $sStatesSelection = ''; if ($oObjectToClone == null) { @@ -1843,6 +1888,7 @@ EOF { $oObj = clone $oObjectToClone; } + // Pre-fill the object with default values, when there is only on possible choice // AND the field is mandatory (otherwise there is always the possiblity to let it empty) $aArgs['this'] = $oObj; @@ -1867,7 +1913,7 @@ EOF $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); // If the field is mandatory, set it to the only possible value - $iFlags = $oObj->GetAttributeFlags($sAttCode); + $iFlags = $oObj->GetInitialStateAttributeFlags($sAttCode); if ((!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY)) { if ($oAttDef->IsExternalKey()) @@ -1977,7 +2023,14 @@ EOF protected function GetFieldAsHtml($sClass, $sAttCode, $sStateAttCode) { $retVal = null; - $iFlags = $this->GetAttributeFlags($sAttCode); + if ($this->IsNew()) + { + $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); + } + else + { + $iFlags = $this->GetAttributeFlags($sAttCode); + } $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) ) { @@ -2189,8 +2242,15 @@ EOF // WARNING: if you change this also check the functions DisplayModifyForm and DisplayCaseLog foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef) { - $aVoid = array(); - $iFlags = $this->GetAttributeFlags($sAttCode, $aVoid, $sTargetState); + if ($this->IsNew()) + { + $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); + } + else + { + $aVoid = array(); + $iFlags = $this->GetAttributeFlags($sAttCode, $aVoid, $sTargetState); + } if ($oAttDef instanceof AttributeCaseLog) { if (!($iFlags & (OPT_ATT_HIDDEN|OPT_ATT_SLAVE|OPT_ATT_READONLY))) @@ -2206,8 +2266,15 @@ EOF { $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); - $aVoid = array(); - $iFlags = $this->GetAttributeFlags($sAttCode, $aVoid, $sTargetState); + if ($this->IsNew()) + { + $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); + } + else + { + $aVoid = array(); + $iFlags = $this->GetAttributeFlags($sAttCode, $aVoid, $sTargetState); + } if ($oAttDef->IsWritable()) { if ( $iFlags & (OPT_ATT_HIDDEN | OPT_ATT_READONLY)) @@ -2532,7 +2599,14 @@ EOF { $oPage->SetCurrentTab(Dict::S('UI:PropertiesTab')); $sClass = get_class($this); - $iFlags = $this->GetAttributeFlags($sAttCode); + if ($this->IsNew()) + { + $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); + } + else + { + $iFlags = $this->GetAttributeFlags($sAttCode); + } if ( $iFlags & OPT_ATT_HIDDEN) { // The case log is hidden do nothing @@ -2544,7 +2618,7 @@ EOF if ((!$bEditMode) || ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE))) { - // Check if the attribute is not read-only becuase of a synchro... + // Check if the attribute is not read-only because of a synchro... $aReasons = array(); $sSynchroIcon = ''; if ($iFlags & OPT_ATT_SLAVE) @@ -2570,12 +2644,17 @@ EOF $sValue = $this->Get($sAttCode); $sDisplayValue = $this->GetEditValue($sAttCode); $aArgs = array('this' => $this, 'formPrefix' => $sPrefix); - $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).''; + $sHTMLValue = ''; + if ($sComment != '') + { + $sHTMLValue = ''.$sComment.'
'; + } + $sHTMLValue .= "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).''; $aFieldsMap[$sAttCode] = $sInputId; } //$aVal = array('label' => ''.$oAttDef->GetLabel().'', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos); - $oPage->add('
'.$oAttDef->GetLabel().' '.$sComment.''); + $oPage->add('
'.$oAttDef->GetLabel().''); $oPage->add($sHTMLValue); $oPage->add('
'); } diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 0b7ebf5d3..39a38b93e 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -243,7 +243,7 @@ EOF $sHtmlValue .= "   m_sAttCode}{$this->m_sNameSuffix}_btnAdd\" type=\"button\" value=\"".Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sRemoteClass))."\" onClick=\"oWidget{$this->m_iInputId}.AddObjects();\">\n"; $sHtmlValue .= "

 

\n"; $sHtmlValue .= "\n"; - $oPage->add_at_the_end($this->GetObjectPickerDialog($oPage)); // To prevent adding forms inside the main form + $oPage->add_at_the_end($this->GetObjectPickerDialog($oPage), "dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}"); // To prevent adding forms inside the main form return $sHtmlValue; } diff --git a/application/webpage.class.inc.php b/application/webpage.class.inc.php index a1901fe0a..825b44cab 100644 --- a/application/webpage.class.inc.php +++ b/application/webpage.class.inc.php @@ -96,11 +96,11 @@ class WebPage } /** - * Add any text or HTML fragment at the end of the body of the page + * Add any text or HTML fragment (identified by an ID) at the end of the body of the page * This is useful to add hidden content, DIVs or FORMs that should not * be embedded into each other. */ - public function add_at_the_end($s_html) + public function add_at_the_end($s_html, $sId = '') { $this->s_deferred_content .= $s_html; } diff --git a/core/dbobject.class.php b/core/dbobject.class.php index f00bbb699..990bbd10c 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -687,6 +687,23 @@ abstract class DBObject return $iFlags | $iSynchroFlags; // Combine both sets of flags } + /** + * Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...) + * for the given attribute for the current state of the object considered as an INITIAL state + * @param string $sAttCode The code of the attribute + * @return integer Flags: the binary combination of the flags applicable to this attribute + */ + public function GetInitialStateAttributeFlags($sAttCode, &$aReasons = array()) + { + $iFlags = 0; + $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); + if (!empty($sStateAttCode)) + { + $iFlags = MetaModel::GetInitialStateAttributeFlags(get_class($this), $this->Get($sStateAttCode), $sAttCode); + } + return $iFlags; // No need to care about the synchro flags since we'll be creating a new object anyway + } + // check if the given (or current) value is suitable for the attribute // return true if successfull // return the error desciption otherwise diff --git a/core/metamodel.class.php b/core/metamodel.class.php index e63cf4308..1c1040eb9 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -958,6 +958,36 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])) } } + /* + * Enumerate all possible initial states, including the default one + */ + public static function EnumInitialStates($sClass) + { + if (array_key_exists($sClass, self::$m_aStates)) + { + $aRet = array(); + // Add the states for which the flag 'is_initial_state' is set to + foreach(self::$m_aStates[$sClass] as $aStateCode => $aProps) + { + if (isset($aProps['initial_state_path'])) + { + $aRet[$aStateCode] = $aProps['initial_state_path']; + } + } + // Add the default initial state + $sMainInitialState = self::GetDefaultState($sClass); + if (!isset($aRet[$sMainInitialState])) + { + $aRet[$sMainInitialState] = array(); + } + return $aRet; + } + else + { + return array(); + } + } + public static function EnumStimuli($sClass) { if (array_key_exists($sClass, self::$m_aStimuli)) @@ -1014,6 +1044,46 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])) return $iFlags; } + /** + * Combines the flags from the all states that compose the initial_state_path + */ + public static function GetInitialStateAttributeFlags($sClass, $sState, $sAttCode) + { + $iFlags = self::GetAttributeFlags($sClass, $sState, $sAttCode); // Be default set the same flags as the 'target' state + $sStateAttCode = self::GetStateAttributeCode($sClass); + if (!empty($sStateAttCode)) + { + $aStates = MetaModel::EnumInitialStates($sClass); + if (array_key_exists($sState, $aStates)) + { + $bReadOnly = (($iFlags & OPT_ATT_READONLY) == OPT_ATT_READONLY); + $bHidden = (($iFlags & OPT_ATT_HIDDEN) == OPT_ATT_HIDDEN); + foreach($aStates[$sState] as $sPrevState) + { + $iPrevFlags = self::GetAttributeFlags($sClass, $sPrevState, $sAttCode); + $bReadOnly = $bReadOnly && (($iPrevFlags & OPT_ATT_READONLY) == OPT_ATT_READONLY); // if it is/was not readonly => then it's not + $bHidden = $bHidden && (($iPrevFlags & OPT_ATT_HIDDEN) == OPT_ATT_HIDDEN); // if it is/was not hidden => then it's not + } + if ($bReadOnly) + { + $iFlags = $iFlags | OPT_ATT_READONLY; + } + else + { + $iFlags = $iFlags & ~OPT_ATT_READONLY; + } + if ($bHidden) + { + $iFlags = $iFlags | OPT_ATT_HIDDEN; + } + else + { + $iFlags = $iFlags & ~OPT_ATT_HIDDEN; + } + } + } + return $iFlags; + } // // Allowed values // diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index 90cde5d9c..1316b8b8a 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -927,5 +927,6 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:FavoriteOrganizations+' => 'Check in the list below the organizations that you want to see in the drop-down menu for a quick access. '. 'Note that this is not a security setting, objects from any organization are still visible and can be accessed by selecting "All Organizations" in the drop-down list.', 'UI:NavigateAwayConfirmationMessage' => 'Any modification will be discarded.', + 'UI:Create_Class_InState' => 'Create the %1$s in state: ', )); ?> diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 1678a6a11..84d2b4cbf 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -777,5 +777,6 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:FavoriteOrganizations+' => 'Cochez dans la liste ci-dessous les organisations que vous voulez voir listées dans le menu principal. '. 'Ceci n\'est pas un réglage de sécurité. Les objets de toutes les organisations sont toujours visibles en choisissant "Toutes les Organisations" dans le menu.', 'UI:NavigateAwayConfirmationMessage' => 'Toute modification sera perdue.', + 'UI:Create_Class_InState' => 'Créer l\'objet %1$s dans l\'état: ', )); ?> diff --git a/js/wizardhelper.js b/js/wizardhelper.js index 57ca59093..a66be5cd8 100644 --- a/js/wizardhelper.js +++ b/js/wizardhelper.js @@ -189,4 +189,21 @@ function WizardHelper(sClass, sFormPrefix, sState) } this.AjaxQueryServer(); } + + this.ReloadObjectCreationForm = function(sFormId, sTargetState) + { + $('#'+sFormId).block(); + this.UpdateWizard(); + this.ResetQuery(); + var sTransactionId = $('input[name=transaction_id]').val(); + $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', + { json_obj: this.ToJSON(), operation: 'obj_creation_form', target_state: sTargetState, transaction_id: sTransactionId }, + function(data) + { + $('#'+sFormId).html(data); + onDelayedReady(); + $('#'+sFormId).unblock(); + } + ); + } } diff --git a/pages/UI.php b/pages/UI.php index 8531ddac9..95f588f9a 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -835,6 +835,10 @@ try if ($oAttDef->IsScalar() && $oAttDef->IsWritable()) { $currValue = $oObj->Get($sAttCode); + if ($oAttDef instanceof AttributeCaseLog) + { + $currValue = ''; + } if (is_object($currValue)) continue; // Skip non scalar values... if(!array_key_exists($currValue, $aValues[$sAttCode])) { @@ -1365,6 +1369,12 @@ EOF else { $oObj = MetaModel::NewObject($sClass); + $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); + if (!empty($sStateAttCode)) + { + $sTargetState = utils::ReadPostedParam('obj_state', ''); + $oObj->Set($sStateAttCode, $sTargetState); + } $oObj->UpdateObjectFromPostedForm(); } if (isset($oObj) && is_object($oObj)) diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 3eeb9301c..31634da52 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -335,7 +335,14 @@ try $sId = $oWizardHelper->GetIdForField($sAttCode); if ($sId != '') { - $iFlags = $oObj->GetAttributeFlags($sAttCode); + if ($oObj->IsNew()) + { + $iFlags = $oObj->GetInitialStateAttributeFlags($sAttCode); + } + else + { + $iFlags = $oObj->GetAttributeFlags($sAttCode); + } if ($iFlags & OPT_ATT_READONLY) { $sHTMLValue = "".$oObj->GetAsHTML($sAttCode); @@ -360,7 +367,19 @@ try } $oPage->add_script("oWizardHelper{$sFormPrefix}.m_oData=".$oWizardHelper->ToJSON().";\noWizardHelper{$sFormPrefix}.UpdateFields();\n"); break; - + + case 'obj_creation_form': + $oPage->SetContentType('text/html'); + $sJson = utils::ReadParam('json_obj', '', false, 'raw_data'); + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + $sClass = $oWizardHelper->GetTargetClass(); + $sTargetState = utils::ReadParam('target_state', ''); + $iTransactionId = utils::ReadParam('transaction_id', ''); + $oObj->Set(MetaModel::GetStateAttributeCode($sClass), $sTargetState); + cmdbAbstractObject::DisplayCreationForm($oPage, $sClass, $oObj, array(), array('action' => utils::GetAbsoluteUrlAppRoot().'pages/UI.php', 'transcation_id' => $iTransactionId)); + break; + // DisplayBlock case 'ajax': $oPage->SetContentType('text/html');