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 &gt;&gt;">');
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