form.class.inc.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. <?php
  2. // Copyright (C) 2010-2016 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. namespace Combodo\iTop\Form;
  19. use Combodo\iTop\Form\Field\SubFormField;
  20. use \Exception;
  21. use \Dict;
  22. use \Combodo\iTop\Form\Field\Field;
  23. /**
  24. * Description of Form
  25. *
  26. * @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
  27. */
  28. class Form
  29. {
  30. protected $sId;
  31. protected $sTransactionId;
  32. protected $aFields;
  33. protected $aDependencies;
  34. protected $bValid;
  35. protected $aErrorMessages;
  36. /**
  37. * Default constructor
  38. *
  39. * @param string $sId
  40. */
  41. public function __construct($sId)
  42. {
  43. $this->sId = $sId;
  44. $this->sTransactionId = null;
  45. $this->aFields = array();
  46. $this->aDependencies = array();
  47. $this->bValid = true;
  48. $this->aErrorMessages = array();
  49. }
  50. /**
  51. *
  52. * @return string
  53. */
  54. public function GetId()
  55. {
  56. return $this->sId;
  57. }
  58. /**
  59. *
  60. * @param string $sId
  61. * @return \Combodo\iTop\Form\Form
  62. */
  63. public function SetId($sId)
  64. {
  65. // Setting id for the form itself
  66. $this->sId = $sId;
  67. // Then setting formpath to its fields
  68. foreach ($this->aFields as $oField)
  69. {
  70. $oField->SetFormPath($sId);
  71. }
  72. return $this;
  73. }
  74. /**
  75. *
  76. * @return string
  77. */
  78. public function GetTransactionId()
  79. {
  80. return $this->sTransactionId;
  81. }
  82. /**
  83. *
  84. * @param string $sTransactionId
  85. * @return \Combodo\iTop\Form\Form
  86. */
  87. public function SetTransactionId($sTransactionId)
  88. {
  89. $this->sTransactionId = $sTransactionId;
  90. return $this;
  91. }
  92. /**
  93. *
  94. * @return array
  95. */
  96. public function GetFields()
  97. {
  98. return $this->aFields;
  99. }
  100. /**
  101. *
  102. * @return array
  103. */
  104. public function GetDependencies()
  105. {
  106. return $this->aDependencies;
  107. }
  108. /**
  109. * Returns a hash array of "Field id" => "Field value"
  110. *
  111. * @return array
  112. */
  113. public function GetCurrentValues()
  114. {
  115. $aValues = array();
  116. foreach ($this->aFields as $sId => $oField)
  117. {
  118. $aValues[$sId] = $oField->GetCurrentValue();
  119. }
  120. return $aValues;
  121. }
  122. /**
  123. *
  124. * @param array $aValues Must be a hash array of "Field id" => "Field value"
  125. * @return \Combodo\iTop\Form\Form
  126. */
  127. public function SetCurrentValues($aValues)
  128. {
  129. foreach ($aValues as $sId => $value)
  130. {
  131. $oField = $this->GetField($sId);
  132. $oField->SetCurrentValue($value);
  133. }
  134. return $this;
  135. }
  136. /**
  137. * Returns the current validation state of the form (true|false).
  138. * It DOESN'T make the validation, see Validate() instead.
  139. *
  140. * @return boolean
  141. */
  142. public function GetValid()
  143. {
  144. return $this->bValid;
  145. }
  146. /**
  147. * Note : Function is protected as bValid should not be set from outside
  148. *
  149. * @param boolean $bValid
  150. * @return \Combodo\iTop\Form\Form
  151. */
  152. protected function SetValid($bValid)
  153. {
  154. $this->bValid = $bValid;
  155. return $this;
  156. }
  157. /**
  158. *
  159. * @return array
  160. */
  161. public function GetErrorMessages()
  162. {
  163. return $this->aErrorMessages;
  164. }
  165. /**
  166. * Note : Function is protected as aErrorMessages should not be set from outside
  167. *
  168. * @param array $aErrorMessages
  169. * @param string $sFieldId
  170. * @return \Combodo\iTop\Form\Form
  171. */
  172. protected function SetErrorMessages($aErrorMessages, $sFieldId = null)
  173. {
  174. if ($sFieldId === null)
  175. {
  176. $this->aErrorMessages = $aErrorMessages;
  177. }
  178. else
  179. {
  180. $this->aErrorMessages[$sFieldId] = $aErrorMessages;
  181. }
  182. return $this;
  183. }
  184. /**
  185. * If $sFieldId is not set, the $sErrorMessage will be added to the general form messages
  186. *
  187. * Note : Function is protected as aErrorMessages should not be add from outside
  188. *
  189. * @param string $sErrorMessage
  190. * @param string $sFieldId
  191. * @return \Combodo\iTop\Form\Form
  192. */
  193. protected function AddErrorMessage($sErrorMessage, $sFieldId = '_main')
  194. {
  195. if (!isset($this->aErrorMessages[$sFieldId]))
  196. {
  197. $this->aErrorMessages[$sFieldId] = array();
  198. }
  199. $this->aErrorMessages[$sFieldId][] = $sErrorMessage;
  200. return $this;
  201. }
  202. /**
  203. * Note : Function is protected as aErrorMessages should not be set from outside
  204. *
  205. * @return \Combodo\iTop\Form\Form
  206. */
  207. protected function EmptyErrorMessages()
  208. {
  209. $this->aErrorMessages = array();
  210. return $this;
  211. }
  212. /**
  213. *
  214. * @param string $sId
  215. * @return \Combodo\iTop\Form\Field\Field
  216. * @throws Exception
  217. */
  218. public function GetField($sId)
  219. {
  220. if (!array_key_exists($sId, $this->aFields))
  221. {
  222. throw new Exception('Field with ID "' . $sId . '" was not found in the Form.');
  223. }
  224. return $this->aFields[$sId];
  225. }
  226. /**
  227. *
  228. * @param string $sId
  229. * @return boolean
  230. */
  231. public function HasField($sId)
  232. {
  233. return array_key_exists($sId, $this->aFields);
  234. }
  235. /**
  236. *
  237. * @param \Combodo\iTop\Form\Field\Field $oField
  238. * @param array $aDependsOnIds
  239. * @return \Combodo\iTop\Form\Form
  240. */
  241. public function AddField(Field $oField, $aDependsOnIds = array())
  242. {
  243. $oField->SetFormPath($this->sId);
  244. $this->aFields[$oField->GetId()] = $oField;
  245. return $this;
  246. }
  247. /**
  248. *
  249. * @param string $sId
  250. * @return \Combodo\iTop\Form\Form
  251. */
  252. public function RemoveField($sId)
  253. {
  254. if (array_key_exists($sId, $this->aFields))
  255. {
  256. unset($this->aFields[$sId]);
  257. }
  258. return $this;
  259. }
  260. /**
  261. * Returns a array (list) of the fields ordered by their dependencies.
  262. *
  263. * @return array
  264. */
  265. public function GetOrderedFields()
  266. {
  267. // TODO : Do this so it flatten the array
  268. return $this->aFields;
  269. }
  270. /**
  271. * Returns an array of field ids the $sFieldId depends on.
  272. *
  273. * @param string $sFieldId
  274. * @return array
  275. * @throws Exception
  276. */
  277. public function GetFieldDependencies($sFieldId)
  278. {
  279. if (!array_key_exists($sFieldId, $this->aDependencies))
  280. {
  281. throw new Exception('Field with ID "' . $sFieldId . '" had no dependancies declared in the Form.');
  282. }
  283. return $this->aDependencies[$sFieldId];
  284. }
  285. /**
  286. *
  287. * @param string $sFieldId
  288. * @param array $aDependsOnIds
  289. * @return \Combodo\iTop\Form\Form
  290. */
  291. public function AddFieldDependencies($sFieldId, array $aDependsOnIds)
  292. {
  293. foreach ($aDependsOnIds as $sDependsOnId)
  294. {
  295. $this->AddFieldDependency($sFieldId, $sDependsOnId);
  296. }
  297. return $this;
  298. }
  299. /**
  300. *
  301. * @param string $sFieldId
  302. * @param string $sDependsOnId
  303. * @return \Combodo\iTop\Form\Form
  304. */
  305. public function AddFieldDependency($sFieldId, $sDependsOnId)
  306. {
  307. if (!array_key_exists($sFieldId, $this->aDependencies))
  308. {
  309. $this->aDependencies[$sFieldId] = array();
  310. }
  311. $this->aDependencies[$sFieldId][] = $sDependsOnId;
  312. return $this;
  313. }
  314. /**
  315. * Returns a hash array of the fields impacts on other fields. Key being the field that impacts the fields stored in the value as a regular array
  316. * (It kind of reversed the dependencies array)
  317. *
  318. * eg :
  319. * - 'service' => array('subservice', 'template')
  320. * - 'subservice' => array()
  321. * - ...
  322. *
  323. * @return array
  324. */
  325. public function GetFieldsImpacts()
  326. {
  327. $aRes = array();
  328. foreach ($this->aDependencies as $sImpactedFieldId => $aDependentFieldsIds)
  329. {
  330. foreach ($aDependentFieldsIds as $sDependentFieldId)
  331. {
  332. if (!array_key_exists($sDependentFieldId, $aRes))
  333. {
  334. $aRes[$sDependentFieldId] = array();
  335. }
  336. $aRes[$sDependentFieldId][] = $sImpactedFieldId;
  337. }
  338. }
  339. return $aRes;
  340. }
  341. /**
  342. * @param $sFormPath
  343. * @return Form|null
  344. */
  345. public function FindSubForm($sFormPath)
  346. {
  347. $ret = null;
  348. if ($sFormPath == $this->sId)
  349. {
  350. $ret = $this;
  351. }
  352. else
  353. {
  354. foreach ($this->aFields as $oField)
  355. {
  356. if ($oField instanceof SubFormField)
  357. {
  358. $ret = $oField->FindSubForm($sFormPath);
  359. if ($ret !== null) break;
  360. }
  361. }
  362. }
  363. return $ret;
  364. }
  365. /**
  366. * Finalizes each field, following the dependencies so that a field can compute its value or other properties,
  367. * depending on other fields
  368. */
  369. public function Finalize()
  370. {
  371. $aFieldList = array(); // Fields ordered by dependence
  372. // Clone the dependency data : $aDependencies will be truncated as the fields are added to the list
  373. $aDependencies = $this->aDependencies;
  374. $bMadeProgress = true; // Safety net in case of circular references
  375. foreach ($aDependencies as $sImpactedBy => $aSomeFields)
  376. {
  377. foreach ($aSomeFields as $i => $sSomeId)
  378. {
  379. if (!array_key_exists($sSomeId, $this->aFields))
  380. {
  381. throw new Exception('Unmet dependency : Field ' . $sImpactedBy . ' expecting field ' . $sSomeId . ' which is not in the Form');
  382. }
  383. }
  384. }
  385. while ($bMadeProgress && count($aFieldList) < count($this->aFields))
  386. {
  387. $bMadeProgress = false;
  388. foreach ($this->aFields as $sId => $oField)
  389. {
  390. if (array_key_exists($sId, $aFieldList))
  391. continue;
  392. if (isset($aDependencies[$sId]) && count($aDependencies[$sId]) > 0) continue;
  393. // Add the field at the end of the list
  394. $aFieldList[$sId] = $oField;
  395. $bMadeProgress = true;
  396. // Track that this dependency has been solved
  397. foreach ($aDependencies as $sImpactedBy => $aSomeFields)
  398. {
  399. foreach ($aSomeFields as $i => $sSomeId)
  400. {
  401. if ($sSomeId == $sId)
  402. {
  403. unset($aDependencies[$sImpactedBy][$i]);
  404. }
  405. }
  406. }
  407. }
  408. }
  409. if (!$bMadeProgress)
  410. {
  411. throw new Exception('Unmet dependencies (might be a circular reference) : ' . implode(', ', array_keys($aDependencies)));
  412. }
  413. foreach ($aFieldList as $sId => $oField)
  414. {
  415. $oField->OnFinalize();
  416. }
  417. }
  418. /**
  419. * Validate the form and return if it's valid or not
  420. *
  421. * @return boolean
  422. */
  423. public function Validate()
  424. {
  425. $this->SetValid(true);
  426. $this->EmptyErrorMessages();
  427. foreach ($this->aFields as $oField)
  428. {
  429. if (!$oField->Validate())
  430. {
  431. $this->SetValid(false);
  432. foreach ($oField->GetErrorMessages() as $sErrorMessage)
  433. {
  434. $this->AddErrorMessage(Dict::S($sErrorMessage), $oField->Getid());
  435. }
  436. }
  437. }
  438. return $this->GetValid();
  439. }
  440. }