uiwizard.class.inc.php 12 KB

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