form.class.inc.php 11 KB

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