1<?php 2// Copyright (C) 2010-2012 Combodo SARL 3// 4// This file is part of iTop. 5// 6// iTop is free software; you can redistribute it and/or modify 7// it under the terms of the GNU Affero General Public License as published by 8// the Free Software Foundation, either version 3 of the License, or 9// (at your option) any later version. 10// 11// iTop is distributed in the hope that it will be useful, 12// but WITHOUT ANY WARRANTY; without even the implied warranty of 13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14// GNU Affero General Public License for more details. 15// 16// You should have received a copy of the GNU Affero General Public License 17// along with iTop. If not, see <http://www.gnu.org/licenses/> 18 19 20/** 21 * Class UIWizard 22 * 23 * @copyright Copyright (C) 2010-2012 Combodo SARL 24 * @license http://opensource.org/licenses/AGPL-3.0 25 */ 26 27class UIWizard 28{ 29 protected $m_oPage; 30 protected $m_sClass; 31 protected $m_sTargetState; 32 protected $m_aWizardSteps; 33 34 public function __construct($oPage, $sClass, $sTargetState = '') 35 { 36 $this->m_oPage = $oPage; 37 $this->m_sClass = $sClass; 38 if (empty($sTargetState)) 39 { 40 $sTargetState = MetaModel::GetDefaultState($sClass); 41 } 42 $this->m_sTargetState = $sTargetState; 43 $this->m_aWizardSteps = $this->ComputeWizardStructure(); 44 } 45 46 public function GetObjectClass() { return $this->m_sClass; } 47 public function GetTargetState() { return $this->m_sTargetState; } 48 public function GetWizardStructure() { return $this->m_aWizardSteps; } 49 50 /** 51 * Displays one step of the wizard 52 */ 53 public function DisplayWizardStep($aStep, $iStepIndex, &$iMaxInputId, &$aFieldsMap, $bFinishEnabled = false, $aArgs = array()) 54 { 55 if ($iStepIndex == 1) // one big form that contains everything, to make sure that the uploaded files are posted too 56 { 57 $this->m_oPage->add("<form method=\"post\" enctype=\"multipart/form-data\" action=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php\">\n"); 58 } 59 $this->m_oPage->add("<div class=\"wizContainer\" id=\"wizStep$iStepIndex\" style=\"display:none;\">\n"); 60 $this->m_oPage->add("<a name=\"step$iStepIndex\" />\n"); 61 $aStates = MetaModel::EnumStates($this->m_sClass); 62 $aDetails = array(); 63 $sJSHandlerCode = ''; // Javascript code to be executed each time this step of the wizard is entered 64 foreach($aStep as $sAttCode) 65 { 66 $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); 67 if ($oAttDef->IsWritable()) 68 { 69 $sAttLabel = $oAttDef->GetLabel(); 70 $iOptions = isset($aStates[$this->m_sTargetState]['attribute_list'][$sAttCode]) ? $aStates[$this->m_sTargetState]['attribute_list'][$sAttCode] : 0; 71 72 $aPrerequisites = $oAttDef->GetPrerequisiteAttributes(); 73 if ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) 74 { 75 $aFields[$sAttCode] = array(); 76 foreach($aPrerequisites as $sCode) 77 { 78 $aFields[$sAttCode][$sCode] = ''; 79 } 80 } 81 if (count($aPrerequisites) > 0) 82 { 83 $aOptions[] = 'Prerequisites: '.implode(', ', $aPrerequisites); 84 } 85 86 $sFieldFlag = (($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE)) || (!$oAttDef->IsNullAllowed()) )? ' <span class="hilite">*</span>' : ''; 87 $oDefaultValuesSet = $oAttDef->GetDefaultValue(/* $oObject->ToArgs() */); // @@@ TO DO: get the object's current value if the object exists 88 $sHTMLValue = cmdbAbstractObject::GetFormElementForField($this->m_oPage, $this->m_sClass, $sAttCode, $oAttDef, $oDefaultValuesSet, '', "att_$iMaxInputId", '', $iOptions, $aArgs); 89 $aFieldsMap["att_$iMaxInputId"] = $sAttCode; 90 $aDetails[] = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().$sFieldFlag.'</span>', 'value' => "<span id=\"field_att_$iMaxInputId\">$sHTMLValue</span>"); 91 if ($oAttDef->GetValuesDef() != null) 92 { 93 $sJSHandlerCode .= "\toWizardHelper.RequestAllowedValues('$sAttCode');\n"; 94 } 95 if ($oAttDef->GetDefaultValue() != null) 96 { 97 $sJSHandlerCode .= "\toWizardHelper.RequestDefaultValue('$sAttCode');\n"; 98 } 99 if ($oAttDef->IsLinkSet()) 100 { 101 $sJSHandlerCode .= "\toLinkWidgetatt_$iMaxInputId.Init();"; 102 } 103 $iMaxInputId++; 104 } 105 } 106 //$aDetails[] = array('label' => '', 'value' => '<input type="button" value="Next >>">'); 107 $this->m_oPage->details($aDetails); 108 $sBackButtonDisabled = ($iStepIndex <= 1) ? 'disabled' : ''; 109 $sDisabled = $bFinishEnabled ? '' : 'disabled'; 110 $nbSteps = count($this->m_aWizardSteps['mandatory']) + count($this->m_aWizardSteps['optional']); 111 $this->m_oPage->add("<div style=\"text-align:center\"> 112 <input type=\"button\" value=\"".Dict::S('UI:Button:Back')."\" $sBackButtonDisabled onClick=\"GoToStep($iStepIndex, $iStepIndex - 1)\" /> 113 <input type=\"button\" value=\"".Dict::S('UI:Button:Next')."\" onClick=\"GoToStep($iStepIndex, 1+$iStepIndex)\" /> 114 <input type=\"button\" value=\"".Dict::S('UI:Button:Finish')."\" $sDisabled onClick=\"GoToStep($iStepIndex, 1+$nbSteps)\" /> 115 </div>\n"); 116 $this->m_oPage->add_script(" 117function OnEnterStep{$iStepIndex}() 118{ 119 oWizardHelper.ResetQuery(); 120 oWizardHelper.UpdateWizard(); 121 122$sJSHandlerCode 123 124 oWizardHelper.AjaxQueryServer(); 125} 126"); 127 $this->m_oPage->add("</div>\n\n"); 128 } 129 130 /** 131 * Display the final step of the wizard: a confirmation screen 132 */ 133 public function DisplayFinalStep($iStepIndex, $aFieldsMap) 134 { 135 $oAppContext = new ApplicationContext(); 136 $this->m_oPage->add("<div class=\"wizContainer\" id=\"wizStep$iStepIndex\" style=\"display:none;\">\n"); 137 $this->m_oPage->add("<a name=\"step$iStepIndex\" />\n"); 138 $this->m_oPage->P(Dict::S('UI:Wizard:FinalStepTitle')); 139 $this->m_oPage->add("<input type=\"hidden\" name=\"operation\" value=\"wizard_apply_new\" />\n"); 140 $this->m_oPage->add("<input type=\"hidden\" name=\"transaction_id\" value=\"".utils::GetNewTransactionId()."\" />\n"); 141 $this->m_oPage->add("<input type=\"hidden\" id=\"wizard_json_obj\" name=\"json_obj\" value=\"\" />\n"); 142 $sScript = "function OnEnterStep$iStepIndex() {\n"; 143 foreach($aFieldsMap as $iInputId => $sAttCode) 144 { 145 $sScript .= "\toWizardHelper.UpdateCurrentValue('$sAttCode');\n"; 146 } 147 $sScript .= "\toWizardHelper.Preview('object_preview');\n"; 148 $sScript .= "\t$('#wizard_json_obj').val(oWizardHelper.ToJSON());\n"; 149 $sScript .= "}\n"; 150 $this->m_oPage->add_script($sScript); 151 $this->m_oPage->add("<div id=\"object_preview\">\n"); 152 $this->m_oPage->add("</div>\n"); 153 $this->m_oPage->add($oAppContext->GetForForm()); 154 $this->m_oPage->add("<input type=\"button\" value=\"".Dict::S('UI:Button:Back')."\" onClick=\"GoToStep($iStepIndex, $iStepIndex - 1)\" />"); 155 $this->m_oPage->add("<input type=\"submit\" value=\"Create ".MetaModel::GetName($this->m_sClass)."\" />\n"); 156 $this->m_oPage->add("</div>\n"); 157 $this->m_oPage->add("</form>\n"); 158 } 159 /** 160 * Compute the order of the fields & pages in the wizard 161 * @param $oPage iTopWebPage The current page (used to display error messages) 162 * @param $sClass string Name of the class 163 * @param $sStateCode string Code of the target state of the object 164 * @return hash Two dimensional array: each element represents the list of fields for a given page 165 */ 166 protected function ComputeWizardStructure() 167 { 168 $aWizardSteps = array( 'mandatory' => array(), 'optional' => array()); 169 $aFieldsDone = array(); // Store all the fields that are already covered by a previous step of the wizard 170 171 $aStates = MetaModel::EnumStates($this->m_sClass); 172 $sStateAttCode = MetaModel::GetStateAttributeCode($this->m_sClass); 173 174 $aMandatoryAttributes = array(); 175 // Some attributes are always mandatory independently of the state machine (if any) 176 foreach(MetaModel::GetAttributesList($this->m_sClass) as $sAttCode) 177 { 178 $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); 179 if (!$oAttDef->IsExternalField() && !$oAttDef->IsNullAllowed() && 180 $oAttDef->IsWritable() && ($sAttCode != $sStateAttCode) ) 181 { 182 $aMandatoryAttributes[$sAttCode] = OPT_ATT_MANDATORY; 183 } 184 } 185 186 // Now check the attributes that are mandatory in the specified state 187 if ( (!empty($this->m_sTargetState)) && (count($aStates[$this->m_sTargetState]['attribute_list']) > 0) ) 188 { 189 // Check all the fields that *must* be included in the wizard for this 190 // particular target state 191 $aFields = array(); 192 foreach($aStates[$this->m_sTargetState]['attribute_list'] as $sAttCode => $iOptions) 193 { 194 if ( (isset($aMandatoryAttributes[$sAttCode])) && 195 ($aMandatoryAttributes[$sAttCode] & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) ) 196 { 197 $aMandatoryAttributes[$sAttCode] |= $iOptions; 198 } 199 else 200 { 201 $aMandatoryAttributes[$sAttCode] = $iOptions; 202 } 203 } 204 } 205 206 // Check all the fields that *must* be included in the wizard 207 // i.e. all mandatory, must-change or must-prompt fields that are 208 // not also read-only or hidden. 209 // Some fields may be required (null not allowed) from the database 210 // perspective, but hidden or read-only from the user interface perspective 211 $aFields = array(); 212 foreach($aMandatoryAttributes as $sAttCode => $iOptions) 213 { 214 if ( ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) && 215 !($iOptions & (OPT_ATT_READONLY | OPT_ATT_HIDDEN)) ) 216 { 217 $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); 218 $aPrerequisites = $oAttDef->GetPrerequisiteAttributes(); 219 $aFields[$sAttCode] = array(); 220 foreach($aPrerequisites as $sCode) 221 { 222 $aFields[$sAttCode][$sCode] = ''; 223 } 224 } 225 } 226 227 // Now use the dependencies between the fields to order them 228 // Start from the order of the 'details' 229 $aList = MetaModel::FlattenZlist(MetaModel::GetZListItems($this->m_sClass, 'details')); 230 $index = 0; 231 $aOrder = array(); 232 foreach($aFields as $sAttCode => $void) 233 { 234 $aOrder[$sAttCode] = 999; // At the end of the list... 235 } 236 foreach($aList as $sAttCode) 237 { 238 if (array_key_exists($sAttCode, $aFields)) 239 { 240 $aOrder[$sAttCode] = $index; 241 } 242 $index++; 243 } 244 foreach($aFields as $sAttCode => $aDependencies) 245 { 246 // All fields with no remaining dependencies can be entered at this 247 // step of the wizard 248 if (count($aDependencies) > 0) 249 { 250 $iMaxPos = 0; 251 // Remove this field from the dependencies of the other fields 252 foreach($aDependencies as $sDependentAttCode => $void) 253 { 254 // position the current field after the ones it depends on 255 $iMaxPos = max($iMaxPos, 1+$aOrder[$sDependentAttCode]); 256 } 257 } 258 } 259 asort($aOrder); 260 $aCurrentStep = array(); 261 foreach($aOrder as $sAttCode => $rank) 262 { 263 $aCurrentStep[] = $sAttCode; 264 $aFieldsDone[$sAttCode] = ''; 265 } 266 $aWizardSteps['mandatory'][] = $aCurrentStep; 267 268 269 // Now computes the steps to fill the optional fields 270 $aFields = array(); // reset 271 foreach(MetaModel::ListAttributeDefs($this->m_sClass) as $sAttCode=>$oAttDef) 272 { 273 $iOptions = (isset($aStates[$this->m_sTargetState]['attribute_list'][$sAttCode])) ? $aStates[$this->m_sTargetState]['attribute_list'][$sAttCode] : 0; 274 if ( ($sStateAttCode != $sAttCode) && 275 (!$oAttDef->IsExternalField()) && 276 (($iOptions & (OPT_ATT_HIDDEN | OPT_ATT_READONLY)) == 0) && 277 (!isset($aFieldsDone[$sAttCode])) ) 278 279 { 280 // 'State', external fields, read-only and hidden fields 281 // and fields that are already listed in the wizard 282 // are removed from the 'optional' part of the wizard 283 $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); 284 $aPrerequisites = $oAttDef->GetPrerequisiteAttributes(); 285 $aFields[$sAttCode] = array(); 286 foreach($aPrerequisites as $sCode) 287 { 288 if (!isset($aFieldsDone[$sCode])) 289 { 290 // retain only the dependencies that were not covered 291 // in the 'mandatory' part of the wizard 292 $aFields[$sAttCode][$sCode] = ''; 293 } 294 } 295 } 296 } 297 // Now use the dependencies between the fields to order them 298 while(count($aFields) > 0) 299 { 300 $aCurrentStep = array(); 301 foreach($aFields as $sAttCode => $aDependencies) 302 { 303 // All fields with no remaining dependencies can be entered at this 304 // step of the wizard 305 if (count($aDependencies) == 0) 306 { 307 $aCurrentStep[] = $sAttCode; 308 $aFieldsDone[$sAttCode] = ''; 309 unset($aFields[$sAttCode]); 310 // Remove this field from the dependencies of the other fields 311 foreach($aFields as $sUpdatedCode => $aDummy) 312 { 313 // remove the dependency 314 unset($aFields[$sUpdatedCode][$sAttCode]); 315 } 316 } 317 } 318 if (count($aCurrentStep) == 0) 319 { 320 // This step of the wizard would contain NO field ! 321 $this->m_oPage->add(Dict::S('UI:Error:WizardCircularReferenceInDependencies')); 322 print_r($aFields); 323 break; 324 } 325 $aWizardSteps['optional'][] = $aCurrentStep; 326 } 327 return $aWizardSteps; 328 329 } 330} 331?> 332