PageRenderTime 24ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 1ms

/application/uiwizard.class.inc.php

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