form.class.inc.php 12 KB

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