forms.class.inc.php 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769
  1. <?php
  2. // Copyright (C) 2010-2012 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. /**
  19. * Helper class to build interactive forms to be used either in stand-alone
  20. * modal dialog or in "property-sheet" panes.
  21. *
  22. * @copyright Copyright (C) 2010-2012 Combodo SARL
  23. * @license http://opensource.org/licenses/AGPL-3.0
  24. */
  25. class DesignerForm
  26. {
  27. protected $aFieldSets;
  28. protected $sCurrentFieldSet;
  29. protected $sScript;
  30. protected $sReadyScript;
  31. protected $sFormId;
  32. protected $sFormPrefix;
  33. protected $sParamsContainer;
  34. protected $oParentForm;
  35. protected $aSubmitParams;
  36. protected $sSubmitTo;
  37. protected $bReadOnly;
  38. protected $sHierarchyPath; // Needed to manage the visibility of nested subform
  39. protected $sHierarchyParent; // Needed to manage the visibility of nested subform
  40. protected $sHierarchySelector; // Needed to manage the visibility of nested subform
  41. protected $bDisplayed;
  42. protected $aDefaultValues;
  43. protected $sFieldsSuffix;
  44. public function __construct()
  45. {
  46. $this->aFieldSets = array();
  47. $this->sCurrentFieldSet = '';
  48. $this->sScript = '';
  49. $this->sReadyScript = '';
  50. $this->sFormPrefix = '';
  51. $this->sFieldsSuffix = '';
  52. $this->sParamsContainer = '';
  53. $this->sFormId = 'form_'.rand();
  54. $this->oParentForm = null;
  55. $this->bReadOnly = false;
  56. $this->sHierarchyPath = '';
  57. $this->sHierarchyParent = '';
  58. $this->sHierarchySelector = '';
  59. $this->StartFieldSet($this->sCurrentFieldSet);
  60. $this->bDisplayed = true;
  61. $this->aDefaultvalues = array();
  62. }
  63. public function AddField(DesignerFormField $oField)
  64. {
  65. if (!is_array($this->aFieldSets[$this->sCurrentFieldSet]))
  66. {
  67. $this->aFieldSets[$this->sCurrentFieldSet] = array();
  68. }
  69. $this->aFieldSets[$this->sCurrentFieldSet][] = $oField;
  70. $oField->SetForm($this);
  71. }
  72. public function StartFieldSet($sLabel)
  73. {
  74. $this->sCurrentFieldSet = $sLabel;
  75. if (!array_key_exists($this->sCurrentFieldSet, $this->aFieldSets))
  76. {
  77. $this->aFieldSets[$this->sCurrentFieldSet] = array();
  78. }
  79. }
  80. public function Render($oP, $bReturnHTML = false)
  81. {
  82. $sFormId = $this->GetFormId();
  83. if ($this->oParentForm == null)
  84. {
  85. $sReturn = '<form id="'.$sFormId.'">';
  86. }
  87. else
  88. {
  89. $sReturn = '';
  90. }
  91. $sHiddenFields = '';
  92. foreach($this->aFieldSets as $sLabel => $aFields)
  93. {
  94. $aDetails = array();
  95. if ($sLabel != '')
  96. {
  97. $sReturn .= '<fieldset>';
  98. $sReturn .= '<legend>'.$sLabel.'</legend>';
  99. }
  100. foreach($aFields as $oField)
  101. {
  102. $aRow = $oField->Render($oP, $sFormId);
  103. if ($oField->IsVisible())
  104. {
  105. $sValidation = '&nbsp;<span class="prop_apply">'.$this->GetValidationArea($oField->GetFieldId()).'</span>';
  106. $sField = $aRow['value'].$sValidation;
  107. $aDetails[] = array('label' => $aRow['label'], 'value' => $sField);
  108. }
  109. else
  110. {
  111. $sHiddenFields .= $aRow['value'];
  112. }
  113. }
  114. $sReturn .= $oP->GetDetails($aDetails);
  115. if ($sLabel != '')
  116. {
  117. $sReturn .= '</fieldset>';
  118. }
  119. }
  120. $sReturn .= $sHiddenFields;
  121. if ($this->oParentForm == null)
  122. {
  123. $sReturn .= '</form>';
  124. }
  125. if($this->sScript != '')
  126. {
  127. $oP->add_script($this->sScript);
  128. }
  129. if($this->sReadyScript != '')
  130. {
  131. $oP->add_ready_script($this->sReadyScript);
  132. }
  133. if ($bReturnHTML)
  134. {
  135. return $sReturn;
  136. }
  137. else
  138. {
  139. $oP->add($sReturn);
  140. }
  141. }
  142. public function GetFieldSets()
  143. {
  144. return $this->aFieldSets;
  145. }
  146. public function SetSubmitParams($sSubmitToUrl, $aSubmitParams)
  147. {
  148. $this->sSubmitTo = $sSubmitToUrl;
  149. $this->aSubmitParams = $aSubmitParams;
  150. }
  151. public function CopySubmitParams($oParentForm)
  152. {
  153. $this->sSubmitTo = $oParentForm->sSubmitTo;
  154. $this->aSubmitParams = $oParentForm->aSubmitParams;
  155. }
  156. public function GetSubmitParams()
  157. {
  158. return array( 'url' => $this->sSubmitTo, 'params' => $this->aSubmitParams);
  159. }
  160. /**
  161. * Helper to handle subforms hide/show
  162. */
  163. public function SetHierarchyPath($sHierarchy)
  164. {
  165. $this->sHierarchyPath = $sHierarchy;
  166. }
  167. /**
  168. * Helper to handle subforms hide/show
  169. */
  170. public function GetHierarchyPath()
  171. {
  172. return $this->sHierarchyPath;
  173. }
  174. /**
  175. * Helper to handle subforms hide/show
  176. */
  177. public function SetHierarchyParent($sHierarchy)
  178. {
  179. $this->sHierarchyParent = $sHierarchy;
  180. }
  181. /**
  182. * Helper to handle subforms hide/show
  183. */
  184. public function GetHierarchyParent()
  185. {
  186. return $this->sHierarchyParent;
  187. }
  188. public function RenderAsPropertySheet($oP, $bReturnHTML = false, $sNotifyParentSelector = null)
  189. {
  190. $sReturn = '';
  191. $sActionUrl = addslashes($this->sSubmitTo);
  192. $sJSSubmitParams = json_encode($this->aSubmitParams);
  193. $sFormId = $this->GetFormId();
  194. if ($this->oParentForm == null)
  195. {
  196. $sReturn = '<form id="'.$sFormId.'" onsubmit="return false;">';
  197. $sReturn .= '<table class="prop_table">';
  198. $sReturn .= '<thead><tr><th class="prop_header">'.Dict::S('UI:Form:Property').'</th><th class="prop_header">'.Dict::S('UI:Form:Value').'</th><th colspan="2" class="prop_header">&nbsp;</th></tr></thead><tbody>';
  199. }
  200. $sHiddenFields = '';
  201. foreach($this->aFieldSets as $sLabel => $aFields)
  202. {
  203. $aDetails = array();
  204. if ($sLabel != '')
  205. {
  206. $sReturn .= $this->StartRow().'<th colspan="4">'.$sLabel.'</th>'.$this->EndRow();
  207. }
  208. foreach($aFields as $oField)
  209. {
  210. $aRow = $oField->Render($oP, $sFormId, 'property');
  211. if ($oField->IsVisible())
  212. {
  213. $sFieldId = $this->GetFieldId($oField->GetCode());
  214. $sValidation = $this->GetValidationArea($sFieldId, '<span title="Apply" class="ui-icon ui-icon-circle-check"/>');
  215. $sValidationFields = '</td><td class="prop_icon prop_apply">'.$sValidation.'</td><td class="prop_icon prop_cancel"><span title="Revert" class="ui-icon ui-icon-circle-close"/></td>'.$this->EndRow();
  216. $sPath = $this->GetHierarchyPath().'/'.$oField->GetCode();
  217. if (is_null($aRow['label']))
  218. {
  219. $sReturn .= $this->StartRow($sFieldId).'<td class="prop_value" colspan="2">'.$aRow['value'];
  220. }
  221. else
  222. {
  223. $sReturn .= $this->StartRow($sFieldId).'<td class="prop_label">'.$aRow['label'].'</td><td class="prop_value">'.$aRow['value'];
  224. }
  225. if (!($oField instanceof DesignerFormSelectorField) && !($oField instanceof DesignerMultipleSubFormField))
  226. {
  227. $sReturn .= $sValidationFields;
  228. }
  229. $sNotifyParentSelectorJS = is_null($sNotifyParentSelector) ? 'null' : "'".addslashes($sNotifyParentSelector)."'";
  230. $sAutoApply = $oField->IsAutoApply() ? 'true' : 'false';
  231. $sHandlerEquals = $oField->GetHandlerEquals();
  232. $sHandlerGetValue = $oField->GetHandlerGetValue();
  233. $sWidgetClass = $oField->GetWidgetClass();
  234. $sJSExtraParams = '';
  235. if (count($oField->GetWidgetExtraParams()) > 0)
  236. {
  237. $aExtraParams = array();
  238. foreach($oField->GetWidgetExtraParams() as $key=> $value)
  239. {
  240. $aExtraParams[] = "'$key': ".json_encode($value);
  241. }
  242. $sJSExtraParams = ', '.implode(', ', $aExtraParams);
  243. }
  244. $this->AddReadyScript(
  245. <<<EOF
  246. $('#row_$sFieldId').$sWidgetClass({parent_selector: $sNotifyParentSelectorJS, field_id: '$sFieldId', equals: $sHandlerEquals, get_field_value: $sHandlerGetValue, auto_apply: $sAutoApply, value: '', submit_to: '$sActionUrl', submit_parameters: $sJSSubmitParams $sJSExtraParams });
  247. EOF
  248. );
  249. }
  250. else
  251. {
  252. $sHiddenFields .= $aRow['value'];
  253. }
  254. }
  255. }
  256. if ($this->oParentForm == null)
  257. {
  258. $sFormId = $this->sFormId;
  259. $sReturn .= '</tbody>';
  260. $sReturn .= '</table>';
  261. $sReturn .= $sHiddenFields;
  262. $sReturn .= '</form>';
  263. $sReturn .= '<div id="prop_submit_result"></div>'; // for the return of the submit operation
  264. }
  265. else
  266. {
  267. $sReturn .= $sHiddenFields;
  268. }
  269. $this->AddReadyScript(
  270. <<<EOF
  271. $('.prop_table').tableHover();
  272. var idx = 0;
  273. $('.prop_table tbody tr').each(function() {
  274. if ((idx % 2) == 0)
  275. {
  276. $(this).addClass('even');
  277. }
  278. else
  279. {
  280. $(this).addClass('odd');
  281. }
  282. idx++;
  283. });
  284. EOF
  285. );
  286. if($this->sScript != '')
  287. {
  288. $oP->add_script($this->sScript);
  289. }
  290. if($this->sReadyScript != '')
  291. {
  292. $oP->add_ready_script($this->sReadyScript);
  293. }
  294. if ($bReturnHTML)
  295. {
  296. return $sReturn;
  297. }
  298. else
  299. {
  300. $oP->add($sReturn);
  301. }
  302. }
  303. public function StartRow($sFieldId = null)
  304. {
  305. if ($sFieldId != null)
  306. {
  307. return '<tr id="row_'.$sFieldId.'" data-path="'.$this->GetHierarchyPath().'" data-selector="'.$this->GetHierarchyParent().'">';
  308. }
  309. return '<tr data-path="'.$this->GetHierarchyPath().'" data-selector="'.$this->GetHierarchyParent().'">';
  310. }
  311. public function EndRow()
  312. {
  313. return '</tr>';
  314. }
  315. public function RenderAsDialog($oPage, $sDialogId, $sDialogTitle, $iDialogWidth, $sOkButtonLabel, $sIntroduction = null, $bAutoOpen = true)
  316. {
  317. $this->SetPrefix('dlg_'); // To make sure that the controls have different IDs that the property sheet which may be displayed at the same time
  318. $sDialogTitle = addslashes($sDialogTitle);
  319. $sOkButtonLabel = addslashes($sOkButtonLabel);
  320. $sCancelButtonLabel = Dict::S('UI:Button:Cancel');
  321. $oPage->add("<div id=\"$sDialogId\">");
  322. if ($sIntroduction != null)
  323. {
  324. $oPage->add('<div class="ui-dialog-header">'.$sIntroduction.'</div>');
  325. }
  326. $this->Render($oPage);
  327. $oPage->add('</div>');
  328. $sAutoOpen = $bAutoOpen ? 'true' : 'false';
  329. $oPage->add_ready_script(
  330. <<<EOF
  331. $('#$sDialogId').dialog({
  332. height: 'auto',
  333. maxHeight: $(window).height() - 8,
  334. width: $iDialogWidth,
  335. modal: true,
  336. autoOpen: $sAutoOpen,
  337. title: '$sDialogTitle',
  338. buttons: [
  339. { text: "$sOkButtonLabel", click: function() {
  340. var oForm = $(this).closest('.ui-dialog').find('form');
  341. oForm.submit();
  342. if (AnimateDlgButtons)
  343. {
  344. sFormId = oForm.attr('id');
  345. if (oFormValidation[sFormId].length == 0)
  346. {
  347. AnimateDlgButtons(this);
  348. }
  349. }
  350. } },
  351. { text: "$sCancelButtonLabel", click: function() { KillAllMenus(); $(this).dialog( "close" ); $(this).remove(); } },
  352. ],
  353. close: function() { KillAllMenus(); $(this).remove(); }
  354. });
  355. var oForm = $('#$sDialogId form');
  356. var sFormId = oForm.attr('id');
  357. ValidateForm(sFormId, true);
  358. EOF
  359. );
  360. }
  361. public function ReadParams(&$aValues = array())
  362. {
  363. foreach($this->aFieldSets as $sLabel => $aFields)
  364. {
  365. foreach($aFields as $oField)
  366. {
  367. $oField->ReadParam($aValues);
  368. }
  369. }
  370. return $aValues;
  371. }
  372. public function SetPrefix($sPrefix)
  373. {
  374. $this->sFormPrefix = $sPrefix;
  375. }
  376. public function GetPrefix()
  377. {
  378. $sPrefix = '';
  379. if ($this->oParentForm != null)
  380. {
  381. $sPrefix = $this->oParentForm->GetPrefix();
  382. }
  383. return $sPrefix.$this->sFormPrefix;
  384. }
  385. public function SetSuffix($sSuffix)
  386. {
  387. $this->sFieldsSuffix = $sSuffix;
  388. }
  389. public function GetSuffix()
  390. {
  391. $sSuffix = '';
  392. if ($this->oParentForm != null)
  393. {
  394. $sSuffix = $this->oParentForm->GetSuffix();
  395. }
  396. return $sSuffix.$this->sFieldsSuffix;
  397. }
  398. public function SetReadOnly($bReadOnly = true)
  399. {
  400. $this->bReadOnly = $bReadOnly;
  401. }
  402. public function IsReadOnly()
  403. {
  404. if ($this->oParentForm == null)
  405. {
  406. return $this->bReadOnly;
  407. }
  408. else
  409. {
  410. return $this->oParentForm->IsReadOnly();
  411. }
  412. }
  413. public function SetParamsContainer($sParamsContainer)
  414. {
  415. $this->sParamsContainer = $sParamsContainer;
  416. }
  417. public function GetParamsContainer()
  418. {
  419. if ($this->oParentForm == null)
  420. {
  421. return $this->sParamsContainer;
  422. }
  423. else
  424. {
  425. return $this->oParentForm->GetParamsContainer();
  426. }
  427. }
  428. public function SetParentForm($oParentForm)
  429. {
  430. $this->oParentForm = $oParentForm;
  431. }
  432. public function SetDefaultValues($aDefaultValues)
  433. {
  434. if (!is_array($aDefaultValues)) return;
  435. foreach($this->aFieldSets as $sLabel => $aFields)
  436. {
  437. foreach($aFields as $oField)
  438. {
  439. $oField->SetDefaultValueFrom($aDefaultValues);
  440. }
  441. }
  442. }
  443. public function GetDefaultValues()
  444. {
  445. return $this->aDefaultValues;
  446. }
  447. public function GetParentForm()
  448. {
  449. return $this->oParentForm;
  450. }
  451. public function GetFormId()
  452. {
  453. if ($this->oParentForm)
  454. {
  455. $this->oParentForm->GetFormId();
  456. }
  457. return $this->sFormId;
  458. }
  459. public function SetDisplayed($bDisplayed)
  460. {
  461. $this->bDisplayed = $bDisplayed;
  462. }
  463. public function IsDisplayed()
  464. {
  465. if ($this->oParentForm == null)
  466. {
  467. return $this->bDisplayed;
  468. }
  469. else
  470. {
  471. return ($this->bDisplayed && $this->oParentForm->IsDisplayed());
  472. }
  473. }
  474. public function AddScript($sScript)
  475. {
  476. $this->sScript .= $sScript;
  477. }
  478. public function AddReadyScript($sScript)
  479. {
  480. $this->sReadyScript .= $sScript;
  481. }
  482. public function GetFieldId($sCode)
  483. {
  484. return $this->GetPrefix().'attr_'.utils::GetSafeId($sCode.$this->GetSuffix());
  485. }
  486. public function GetFieldName($sCode)
  487. {
  488. return 'attr_'.$sCode.$this->GetSuffix();
  489. }
  490. public function GetParamName($sCode)
  491. {
  492. return 'attr_'.$sCode.$this->GetSuffix();
  493. }
  494. public function GetValidationArea($sId, $sContent = '')
  495. {
  496. return "<span style=\"display:inline-block;width:20px;\" id=\"v_{$sId}\"><span class=\"ui-icon ui-icon-alert\"></span>$sContent</span>";
  497. }
  498. public function GetAsyncActionClass()
  499. {
  500. return $this->sAsyncActionClass;
  501. }
  502. public function FindField($sFieldCode)
  503. {
  504. $oFoundField = false;
  505. foreach($this->aFieldSets as $sLabel => $aFields)
  506. {
  507. foreach($aFields as $oField)
  508. {
  509. $oFoundField = $oField->FindField($sFieldCode);
  510. if ($oFoundField !== false) break;
  511. }
  512. if ($oFoundField !== false) break;
  513. }
  514. return $oFoundField;
  515. }
  516. }
  517. class DesignerTabularForm extends DesignerForm
  518. {
  519. protected $aTable;
  520. public function __construct()
  521. {
  522. parent::__construct();
  523. $this->aTable = array();
  524. }
  525. public function AddRow($aRow)
  526. {
  527. $this->aTable[] = $aRow;
  528. }
  529. public function RenderAsPropertySheet($oP, $bReturnHTML = false, $sNotifyParentSelector = null)
  530. {
  531. return $this->Render($oP, $bReturnHTML);
  532. }
  533. public function Render($oP, $bReturnHTML = false)
  534. {
  535. $sReturn = '';
  536. if ($this->oParentForm == null)
  537. {
  538. $sFormId = $this->sFormId;
  539. $sReturn = '<form id="'.$sFormId.'">';
  540. }
  541. else
  542. {
  543. $sFormId = $this->oParentForm->sFormId;
  544. }
  545. $sHiddenFields = '';
  546. $sReturn .= '<table style="width:100%">';
  547. foreach($this->aTable as $aRow)
  548. {
  549. $sReturn .= '<tr>';
  550. foreach($aRow as $field)
  551. {
  552. if (!is_object($field))
  553. {
  554. // Shortcut: pass a string for a cell containing just a label
  555. $sReturn .= '<td>'.$field.'</td>';
  556. }
  557. else
  558. {
  559. $field->SetForm($this);
  560. $aFieldData = $field->Render($oP, $sFormId);
  561. if ($field->IsVisible())
  562. {
  563. // put the label and value separated by a non-breaking space if needed
  564. $aData = array();
  565. foreach(array('label', 'value') as $sCode )
  566. {
  567. if ($aFieldData[$sCode] != '')
  568. {
  569. $aData[] = $aFieldData[$sCode];
  570. }
  571. }
  572. $sReturn .= '<td>'.implode('&nbsp;', $aData).'</td>';
  573. }
  574. else
  575. {
  576. $sHiddenFields .= $aRow['value'];
  577. }
  578. }
  579. }
  580. $sReturn .= '</tr>';
  581. }
  582. $sReturn .= '</table>';
  583. $sReturn .= $sHiddenFields;
  584. if($this->sScript != '')
  585. {
  586. $oP->add_script($this->sScript);
  587. }
  588. if($this->sReadyScript != '')
  589. {
  590. $oP->add_ready_script($this->sReadyScript);
  591. }
  592. if ($bReturnHTML)
  593. {
  594. return $sReturn;
  595. }
  596. else
  597. {
  598. $oP->add($sReturn);
  599. }
  600. }
  601. public function ReadParams(&$aValues = array())
  602. {
  603. foreach($this->aTable as $aRow)
  604. {
  605. foreach($aRow as $field)
  606. {
  607. if (is_object($field))
  608. {
  609. $field->SetForm($this);
  610. $field->ReadParam($aValues);
  611. }
  612. }
  613. }
  614. return $aValues;
  615. }
  616. }
  617. class DesignerFormField
  618. {
  619. protected $sLabel;
  620. protected $sCode;
  621. protected $defaultValue;
  622. protected $oForm;
  623. protected $bMandatory;
  624. protected $bReadOnly;
  625. protected $bAutoApply;
  626. protected $aCSSClasses;
  627. protected $bDisplayed;
  628. protected $aWidgetExtraParams;
  629. public function __construct($sCode, $sLabel, $defaultValue)
  630. {
  631. $this->sLabel = $sLabel;
  632. $this->sCode = $sCode;
  633. $this->defaultValue = $defaultValue;
  634. $this->bMandatory = false;
  635. $this->bReadOnly = false;
  636. $this->bAutoApply = false;
  637. $this->aCSSClasses = array();
  638. $this->bDisplayed = true;
  639. $this->aWidgetExtraParams = array();
  640. }
  641. public function GetCode()
  642. {
  643. return $this->sCode;
  644. }
  645. public function SetForm($oForm)
  646. {
  647. $this->oForm = $oForm;
  648. }
  649. public function SetMandatory($bMandatory = true)
  650. {
  651. $this->bMandatory = $bMandatory;
  652. }
  653. public function SetReadOnly($bReadOnly = true)
  654. {
  655. $this->bReadOnly = $bReadOnly;
  656. }
  657. public function IsReadOnly()
  658. {
  659. return ($this->oForm->IsReadOnly() || $this->bReadOnly);
  660. }
  661. public function SetAutoApply($bAutoApply)
  662. {
  663. $this->bAutoApply = $bAutoApply;
  664. }
  665. public function IsAutoApply()
  666. {
  667. return $this->bAutoApply;
  668. }
  669. public function SetDisplayed($bDisplayed)
  670. {
  671. $this->bDisplayed = $bDisplayed;
  672. }
  673. public function IsDisplayed()
  674. {
  675. return $this->bDisplayed;
  676. }
  677. public function GetFieldId()
  678. {
  679. return $this->oForm->GetFieldId($this->sCode);
  680. }
  681. public function GetWidgetClass()
  682. {
  683. return 'property_field';
  684. }
  685. public function GetWidgetExtraParams()
  686. {
  687. return $this->aWidgetExtraParams;
  688. }
  689. public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
  690. {
  691. $sId = $this->oForm->GetFieldId($this->sCode);
  692. $sName = $this->oForm->GetFieldName($this->sCode);
  693. return array('label' => $this->sLabel, 'value' => "<input type=\"text\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\">");
  694. }
  695. public function ReadParam(&$aValues)
  696. {
  697. if ($this->IsReadOnly())
  698. {
  699. $aValues[$this->sCode] = $this->defaultValue;
  700. }
  701. else
  702. {
  703. if ($this->oForm->GetParamsContainer() != '')
  704. {
  705. $aParams = utils::ReadParam($this->oForm->GetParamsContainer(), array(), false, 'raw_data');
  706. if (array_key_exists($this->oForm->GetParamName($this->sCode), $aParams))
  707. {
  708. $aValues[$this->sCode] = $aParams[$this->oForm->GetParamName($this->sCode)];
  709. }
  710. else
  711. {
  712. $aValues[$this->sCode] = $this->defaultValue;
  713. }
  714. }
  715. else
  716. {
  717. $aValues[$this->sCode] = utils::ReadParam($this->oForm->GetParamName($this->sCode), $this->defaultValue, false, 'raw_data');
  718. }
  719. }
  720. }
  721. public function IsVisible()
  722. {
  723. return true;
  724. }
  725. public function AddCSSClass($sCSSClass)
  726. {
  727. $this->aCSSClasses[] = $sCSSClass;
  728. }
  729. /**
  730. * A way to set/change the default value after constructing the field
  731. */
  732. public function SetDefaultValueFrom($aAllDefaultValue)
  733. {
  734. if (array_key_exists($this->GetCode(), $aAllDefaultValue))
  735. {
  736. $this->defaultValue = $aAllDefaultValue[$this->GetCode()];
  737. }
  738. }
  739. public function FindField($sFieldCode)
  740. {
  741. if ($this->sCode == $sFieldCode)
  742. {
  743. return $this;
  744. }
  745. return false;
  746. }
  747. public function GetHandlerEquals()
  748. {
  749. return 'null';
  750. }
  751. public function GetHandlerGetValue()
  752. {
  753. return 'null';
  754. }
  755. }
  756. class DesignerLabelField extends DesignerFormField
  757. {
  758. protected $sDescription;
  759. public function __construct($sLabel, $sDescription)
  760. {
  761. parent::__construct('', $sLabel, '');
  762. $this->sDescription = $sDescription;
  763. }
  764. public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
  765. {
  766. $sId = $this->oForm->GetFieldId($this->sCode);
  767. $sName = $this->oForm->GetFieldName($this->sCode);
  768. return array('label' => $this->sLabel, 'value' => $this->sDescription);
  769. }
  770. public function ReadParam(&$aValues)
  771. {
  772. }
  773. public function IsVisible()
  774. {
  775. return true;
  776. }
  777. }
  778. class DesignerTextField extends DesignerFormField
  779. {
  780. protected $sValidationPattern;
  781. protected $aForbiddenValues;
  782. public function __construct($sCode, $sLabel = '', $defaultValue = '')
  783. {
  784. parent::__construct($sCode, $sLabel, $defaultValue);
  785. $this->sValidationPattern = '';
  786. $this->aForbiddenValues = array();
  787. }
  788. public function SetValidationPattern($sValidationPattern)
  789. {
  790. $this->sValidationPattern = $sValidationPattern;
  791. }
  792. public function SetForbiddenValues($aValues, $sExplain, $bCaseSensitive = true)
  793. {
  794. $aForbiddenValues = $aValues;
  795. $iDefaultKey = array_search($this->defaultValue, $aForbiddenValues);
  796. if ($iDefaultKey !== false)
  797. {
  798. // The default (current) value is always allowed...
  799. unset($aForbiddenValues[$iDefaultKey]);
  800. }
  801. $this->aForbiddenValues[] = array('values' => $aForbiddenValues, 'message' => $sExplain, 'case_sensitive' => $bCaseSensitive);
  802. }
  803. public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
  804. {
  805. $sId = $this->oForm->GetFieldId($this->sCode);
  806. $sName = $this->oForm->GetFieldName($this->sCode);
  807. if ($this->IsReadOnly())
  808. {
  809. $sHtmlValue = "<span>".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\"/></span>";
  810. }
  811. else
  812. {
  813. $sPattern = addslashes($this->sValidationPattern);
  814. if (is_array($this->aForbiddenValues))
  815. {
  816. $sForbiddenValues = json_encode($this->aForbiddenValues);
  817. }
  818. else
  819. {
  820. $sForbiddenValues = '[]'; //Empty JS array
  821. }
  822. $sMandatory = $this->bMandatory ? 'true' : 'false';
  823. $oP->add_ready_script(
  824. <<<EOF
  825. $('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', $(this).closest('form').attr('id'), $sForbiddenValues); } );
  826. {
  827. var myTimer = null;
  828. $('#$sId').bind('keyup', function() { clearTimeout(myTimer); myTimer = setTimeout(function() { $('#$sId').trigger('change', {} ); }, 100); });
  829. }
  830. EOF
  831. );
  832. $sCSSClasses = '';
  833. if (count($this->aCSSClasses) > 0)
  834. {
  835. $sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
  836. }
  837. $sHtmlValue = "<input type=\"text\" $sCSSClasses id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\">";
  838. }
  839. return array('label' => $this->sLabel, 'value' => $sHtmlValue);
  840. }
  841. public function ReadParam(&$aValues)
  842. {
  843. parent::ReadParam($aValues);
  844. $sPattern = '/'.str_replace('/', '\/', $this->sValidationPattern).'/'; // Escape the forward slashes since they are used as delimiters for preg_match
  845. if (($this->sValidationPattern != '') && (!preg_match($sPattern, $aValues[$this->sCode])) )
  846. {
  847. $aValues[$this->sCode] = $this->defaultValue;
  848. }
  849. else if(($this->aForbiddenValues != null) && in_array($aValues[$this->sCode], $this->aForbiddenValues))
  850. {
  851. // Reject the value...
  852. $aValues[$this->sCode] = $this->defaultValue;
  853. }
  854. }
  855. }
  856. class DesignerLongTextField extends DesignerTextField
  857. {
  858. public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
  859. {
  860. $sId = $this->oForm->GetFieldId($this->sCode);
  861. $sName = $this->oForm->GetFieldName($this->sCode);
  862. $sPattern = addslashes($this->sValidationPattern);
  863. if (is_array($this->aForbiddenValues))
  864. {
  865. $sForbiddenValues = json_encode($this->aForbiddenValues);
  866. }
  867. else
  868. {
  869. $sForbiddenValues = '[]'; //Empty JS array
  870. }
  871. $sMandatory = $this->bMandatory ? 'true' : 'false';
  872. $sCSSClasses = '';
  873. if (count($this->aCSSClasses) > 0)
  874. {
  875. $sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
  876. }
  877. if (!$this->IsReadOnly())
  878. {
  879. $oP->add_ready_script(
  880. <<<EOF
  881. $('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', $(this).closest('form').attr('id'), $sForbiddenValues); } );
  882. {
  883. var myTimer = null;
  884. $('#$sId').bind('keyup', function() { clearTimeout(myTimer); myTimer = setTimeout(function() { $('#$sId').trigger('change', {} ); }, 100); });
  885. }
  886. EOF
  887. );
  888. $sValue = "<textarea $sCSSClasses id=\"$sId\" name=\"$sName\">".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."</textarea>";
  889. }
  890. else
  891. {
  892. $sValue = "<div $sCSSClasses id=\"$sId\">".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."</div>";
  893. }
  894. return array('label' => $this->sLabel, 'value' => $sValue);
  895. }
  896. }
  897. class DesignerIntegerField extends DesignerFormField
  898. {
  899. protected $iMin; // Lower boundary, inclusive
  900. protected $iMax; // Higher boundary, inclusive
  901. public function __construct($sCode, $sLabel = '', $defaultValue = '')
  902. {
  903. parent::__construct($sCode, $sLabel, $defaultValue);
  904. $this->iMin = 0; // Positive integer is the default
  905. $this->iMax = null;
  906. }
  907. public function SetBoundaries($iMin = null, $iMax = null)
  908. {
  909. $this->iMin = $iMin;
  910. $this->iMax = $iMax;
  911. }
  912. public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
  913. {
  914. $sId = $this->oForm->GetFieldId($this->sCode);
  915. $sName = $this->oForm->GetFieldName($this->sCode);
  916. if ($this->IsReadOnly())
  917. {
  918. $sHtmlValue = "<span>".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\"/></span>";
  919. }
  920. else
  921. {
  922. $sMin = json_encode($this->iMin);
  923. $sMax = json_encode($this->iMax);
  924. $sMandatory = $this->bMandatory ? 'true' : 'false';
  925. $oP->add_ready_script(
  926. <<<EOF
  927. $('#$sId').bind('change keyup validate', function() { ValidateInteger('$sId', $sMandatory, $(this).closest('form').attr('id'), $sMin, $sMax); } );
  928. {
  929. var myTimer = null;
  930. $('#$sId').bind('keyup', function() { clearTimeout(myTimer); myTimer = setTimeout(function() { $('#$sId').trigger('change', {} ); }, 100); });
  931. }
  932. EOF
  933. );
  934. $sCSSClasses = '';
  935. if (count($this->aCSSClasses) > 0)
  936. {
  937. $sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
  938. }
  939. $sHtmlValue = "<input type=\"text\" $sCSSClasses id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\">";
  940. }
  941. return array('label' => $this->sLabel, 'value' => $sHtmlValue);
  942. }
  943. public function ReadParam(&$aValues)
  944. {
  945. parent::ReadParam($aValues);
  946. if (!is_null($this->iMin) && ($aValues[$this->sCode] < $this->iMin))
  947. {
  948. // Reject the value...
  949. $aValues[$this->sCode] = $this->defaultValue;
  950. }
  951. if (!is_null($this->iMax) && ($aValues[$this->sCode] > $this->iMax))
  952. {
  953. // Reject the value...
  954. $aValues[$this->sCode] = $this->defaultValue;
  955. }
  956. }
  957. }
  958. class DesignerComboField extends DesignerFormField
  959. {
  960. protected $aAllowedValues;
  961. protected $bMultipleSelection;
  962. protected $bOtherChoices;
  963. protected $sNullLabel;
  964. protected $bSorted;
  965. public function __construct($sCode, $sLabel = '', $defaultValue = '')
  966. {
  967. parent::__construct($sCode, $sLabel, $defaultValue);
  968. $this->aAllowedValues = array();
  969. $this->bMultipleSelection = false;
  970. $this->bOtherChoices = false;
  971. $this->sNullLabel = Dict::S('UI:SelectOne');
  972. $this->bAutoApply = true;
  973. $this->bSorted = true; // Sorted by default
  974. }
  975. public function SetAllowedValues($aAllowedValues)
  976. {
  977. $this->aAllowedValues = $aAllowedValues;
  978. }
  979. public function MultipleSelection($bMultipleSelection = true)
  980. {
  981. $this->bMultipleSelection = $bMultipleSelection;
  982. }
  983. public function OtherChoices($bOtherChoices = true)
  984. {
  985. $this->bOtherChoices = $bOtherChoices;
  986. }
  987. /**
  988. * An empty label will disable the default empty value
  989. */
  990. public function SetNullLabel($sLabel)
  991. {
  992. $this->sNullLabel = $sLabel;
  993. }
  994. public function IsSorted()
  995. {
  996. return $this->bSorted;
  997. }
  998. public function SetSorted($bSorted)
  999. {
  1000. $this->bSorted = $bSorted;
  1001. }
  1002. public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
  1003. {
  1004. $sId = $this->oForm->GetFieldId($this->sCode);
  1005. $sName = $this->oForm->GetFieldName($this->sCode);
  1006. $sChecked = $this->defaultValue ? 'checked' : '';
  1007. $sMandatory = $this->bMandatory ? 'true' : 'false';
  1008. $sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : '';
  1009. if ($this->IsSorted())
  1010. {
  1011. asort($this->aAllowedValues);
  1012. }
  1013. $sCSSClasses = '';
  1014. if (count($this->aCSSClasses) > 0)
  1015. {
  1016. $sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
  1017. }
  1018. if ($this->IsReadOnly())
  1019. {
  1020. $aSelected = array();
  1021. $aHiddenValues = array();
  1022. foreach($this->aAllowedValues as $sKey => $sDisplayValue)
  1023. {
  1024. if ($this->bMultipleSelection)
  1025. {
  1026. if(in_array($sKey, $this->defaultValue))
  1027. {
  1028. $aSelected[] = $sDisplayValue;
  1029. $aHiddenValues[] = "<input type=\"hidden\" name=\"{$sName}[]\" value=\"".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."\"/>";
  1030. }
  1031. }
  1032. else
  1033. {
  1034. if ($sKey == $this->defaultValue)
  1035. {
  1036. $aSelected[] = $sDisplayValue;
  1037. $aHiddenValues[] = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."\"/>";
  1038. }
  1039. }
  1040. }
  1041. $sHtml = "<span $sCSSClasses>".htmlentities(implode(', ', $aSelected), ENT_QUOTES, 'UTF-8').implode($aHiddenValues)."</span>";
  1042. }
  1043. else
  1044. {
  1045. if ($this->bMultipleSelection)
  1046. {
  1047. $sHtml = "<select $sCSSClasses multiple size=\"8\"id=\"$sId\" name=\"$sName\">";
  1048. }
  1049. else
  1050. {
  1051. $sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\">";
  1052. if ($this->sNullLabel != '')
  1053. {
  1054. $sHtml .= "<option value=\"\">".$this->sNullLabel."</option>";
  1055. }
  1056. }
  1057. foreach($this->aAllowedValues as $sKey => $sDisplayValue)
  1058. {
  1059. if ($this->bMultipleSelection)
  1060. {
  1061. $sSelected = in_array($sKey, $this->defaultValue) ? 'selected' : '';
  1062. }
  1063. else
  1064. {
  1065. $sSelected = ($sKey == $this->defaultValue) ? 'selected' : '';
  1066. }
  1067. // Quick and dirty: display the menu parents as a tree
  1068. $sHtmlValue = str_replace(' ', '&nbsp;', htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8'));
  1069. $sHtml .= "<option value=\"".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."\" $sSelected>$sHtmlValue</option>";
  1070. }
  1071. $sHtml .= "</select>";
  1072. if ($this->bOtherChoices)
  1073. {
  1074. $sHtml .= '<br/><input type="checkbox" id="other_chk_'.$sId.'"><label for="other_chk_'.$sId.'">&nbsp;Other:</label>&nbsp;<input type="text" id="other_'.$sId.'" name="other_'.$sName.'" size="30"/>';
  1075. }
  1076. $oP->add_ready_script(
  1077. <<<EOF
  1078. $('#$sId').bind('change validate', function() { ValidateWithPattern('$sId', $sMandatory, '', $(this).closest('form').attr('id'), null, null); } );
  1079. EOF
  1080. );
  1081. }
  1082. return array('label' => $this->sLabel, 'value' => $sHtml);
  1083. }
  1084. public function ReadParam(&$aValues)
  1085. {
  1086. parent::ReadParam($aValues);
  1087. if ($aValues[$this->sCode] == 'null')
  1088. {
  1089. $aValues[$this->sCode] = array();
  1090. }
  1091. }
  1092. }
  1093. class DesignerBooleanField extends DesignerFormField
  1094. {
  1095. public function __construct($sCode, $sLabel = '', $defaultValue = '')
  1096. {
  1097. parent::__construct($sCode, $sLabel, $defaultValue);
  1098. $this->bAutoApply = true;
  1099. }
  1100. public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
  1101. {
  1102. $sId = $this->oForm->GetFieldId($this->sCode);
  1103. $sName = $this->oForm->GetFieldName($this->sCode);
  1104. $sChecked = $this->defaultValue ? 'checked' : '';
  1105. if ($this->IsReadOnly())
  1106. {
  1107. $sLabel = $this->defaultValue ? Dict::S('UI:UserManagement:ActionAllowed:Yes') : Dict::S('UI:UserManagement:ActionAllowed:No'); //TODO use our own yes/no translations
  1108. $sHtmlValue = "<span>".htmlentities($sLabel)."<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\"/></span>";
  1109. }
  1110. else
  1111. {
  1112. $sCSSClasses = '';
  1113. if (count($this->aCSSClasses) > 0)
  1114. {
  1115. $sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
  1116. }
  1117. $sHtmlValue = "<input $sCSSClasses type=\"checkbox\" $sChecked id=\"$sId\" name=\"$sName\" value=\"true\">";
  1118. }
  1119. return array('label' => $this->sLabel, 'value' => $sHtmlValue);
  1120. }
  1121. public function ReadParam(&$aValues)
  1122. {
  1123. if ($this->IsReadOnly())
  1124. {
  1125. $aValues[$this->sCode] = $this->defaultValue;
  1126. }
  1127. else
  1128. {
  1129. $sParamsContainer = $this->oForm->GetParamsContainer();
  1130. if ($sParamsContainer != '')
  1131. {
  1132. $aParams = utils::ReadParam($sParamsContainer, array(), false, 'raw_data');
  1133. if (array_key_exists($this->oForm->GetParamName($this->sCode), $aParams))
  1134. {
  1135. $sValue = $aParams[$this->oForm->GetParamName($this->sCode)];
  1136. }
  1137. else
  1138. {
  1139. $sValue = 'false';
  1140. }
  1141. }
  1142. else
  1143. {
  1144. $sValue = utils::ReadParam($this->oForm->GetParamName($this->sCode), 'false', false, 'raw_data');
  1145. }
  1146. $aValues[$this->sCode] = ($sValue == 'true');
  1147. }
  1148. }
  1149. }
  1150. class DesignerHiddenField extends DesignerFormField
  1151. {
  1152. public function __construct($sCode, $sLabel = '', $defaultValue = '')
  1153. {
  1154. parent::__construct($sCode, $sLabel, $defaultValue);
  1155. }
  1156. public function IsVisible()
  1157. {
  1158. return false;
  1159. }
  1160. public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
  1161. {
  1162. $sId = $this->oForm->GetFieldId($this->sCode);
  1163. $sName = $this->oForm->GetFieldName($this->sCode);
  1164. $sChecked = $this->defaultValue ? 'checked' : '';
  1165. return array('label' =>'', 'value' => "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\">");
  1166. }
  1167. }
  1168. class DesignerIconSelectionField extends DesignerFormField
  1169. {
  1170. protected $sUploadUrl;
  1171. protected $aAllowedValues;
  1172. public function __construct($sCode, $sLabel = '', $defaultValue = '')
  1173. {
  1174. parent::__construct($sCode, $sLabel, $defaultValue);
  1175. $this->bAutoApply = true;
  1176. $this->sUploadUrl = null;
  1177. }
  1178. public function SetAllowedValues($aAllowedValues)
  1179. {
  1180. $this->aAllowedValues = $aAllowedValues;
  1181. }
  1182. public function EnableUpload($sIconUploadUrl)
  1183. {
  1184. $this->sUploadUrl = $sIconUploadUrl;
  1185. }
  1186. public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
  1187. {
  1188. $sId = $this->oForm->GetFieldId($this->sCode);
  1189. $sName = $this->oForm->GetFieldName($this->sCode);
  1190. $idx = 0;
  1191. foreach($this->aAllowedValues as $index => $aValue)
  1192. {
  1193. if ($aValue['value'] == $this->defaultValue)
  1194. {
  1195. $idx = $index;
  1196. break;
  1197. }
  1198. }
  1199. $sJSItems = json_encode($this->aAllowedValues);
  1200. $sPostUploadTo = ($this->sUploadUrl == null) ? 'null' : "'{$this->sUploadUrl}'";
  1201. if (!$this->IsReadOnly())
  1202. {
  1203. $sValue = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"{$this->defaultValue}\"/>";
  1204. $oP->add_ready_script(
  1205. <<<EOF
  1206. $('#$sId').icon_select({current_idx: $idx, items: $sJSItems, post_upload_to: $sPostUploadTo});
  1207. EOF
  1208. );
  1209. }
  1210. else
  1211. {
  1212. $sValue = '<span style="display:inline-block;line-height:48px;height:48px;"><span><img style="vertical-align:middle" src="'.$this->aAllowedValues[$idx]['icon'].'" />&nbsp;'.htmlentities($this->aAllowedValues[$idx]['label'], ENT_QUOTES, 'UTF-8').'</span></span>';
  1213. }
  1214. $sReadOnly = $this->IsReadOnly() ? 'disabled' : '';
  1215. return array('label' => $this->sLabel, 'value' => $sValue);
  1216. }
  1217. }
  1218. class RunTimeIconSelectionField extends DesignerIconSelectionField
  1219. {
  1220. static $aAllIcons = array();
  1221. public function __construct($sCode, $sLabel = '', $defaultValue = '')
  1222. {
  1223. parent::__construct($sCode, $sLabel, $defaultValue);
  1224. if (count(self::$aAllIcons) == 0)
  1225. {
  1226. self::$aAllIcons = self::FindIconsOnDisk(APPROOT.'env-'.utils::GetCurrentEnvironment());
  1227. ksort(self::$aAllIcons);
  1228. }
  1229. $aValues = array();
  1230. foreach(self::$aAllIcons as $sFilePath)
  1231. {
  1232. $aValues[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => utils::GetAbsoluteUrlModulesRoot().$sFilePath);
  1233. }
  1234. $this->SetAllowedValues($aValues);
  1235. }
  1236. static protected function FindIconsOnDisk($sBaseDir, $sDir = '')
  1237. {
  1238. $aFiles = null;
  1239. $sKey = $sBaseDir.'/'.$sDir;
  1240. $sShortKey = abs(crc32($sKey));
  1241. $sCacheFile = utils::GetCachePath().'available-icons-'.$sShortKey.'.php';
  1242. $sCacheClass = 'AvailableIcons_'.$sShortKey;
  1243. if (file_exists($sCacheFile))
  1244. {
  1245. require_once($sCacheFile);
  1246. if ($sCacheClass::$sKey === $sKey) // crc32 collision detection
  1247. {
  1248. $aFiles = $sCacheClass::$aIconFiles;
  1249. }
  1250. }
  1251. if ($aFiles === null)
  1252. {
  1253. $aFiles = self::_FindIconsOnDisk($sBaseDir, $sDir);
  1254. $sAvailableIcons = '<?php'.PHP_EOL;
  1255. $sAvailableIcons .= '// Generated and used by '.__METHOD__.PHP_EOL;
  1256. $sAvailableIcons .= 'class '.$sCacheClass.PHP_EOL;
  1257. $sAvailableIcons .= '{'.PHP_EOL;
  1258. $sAvailableIcons .= ' static $sKey = '.var_export($sKey, true).';'.PHP_EOL;
  1259. $sAvailableIcons .= ' static $aIconFiles = '.var_export($aFiles, true).';'.PHP_EOL;
  1260. $sAvailableIcons .= '}'.PHP_EOL;
  1261. file_put_contents($sCacheFile, $sAvailableIcons, LOCK_EX);
  1262. }
  1263. return $aFiles;
  1264. }
  1265. static protected function _FindIconsOnDisk($sBaseDir, $sDir = '')
  1266. {
  1267. $aResult = array();
  1268. // Populate automatically the list of icon files
  1269. if ($hDir = @opendir($sBaseDir.'/'.$sDir))
  1270. {
  1271. while (($sFile = readdir($hDir)) !== false)
  1272. {
  1273. $aMatches = array();
  1274. if (($sFile != '.') && ($sFile != '..') && ($sFile != 'lifecycle') && is_dir($sBaseDir.'/'.$sDir.'/'.$sFile))
  1275. {
  1276. $sDirSubPath = ($sDir == '') ? $sFile : $sDir.'/'.$sFile;
  1277. $aResult = array_merge($aResult, self::_FindIconsOnDisk($sBaseDir, $sDirSubPath));
  1278. }
  1279. if (preg_match("/\.(png|jpg|jpeg|gif)$/i", $sFile, $aMatches)) // png, jp(e)g and gif are considered valid
  1280. {
  1281. $aResult[$sFile.'_'.$sDir] = $sDir.'/'.$sFile;
  1282. }
  1283. }
  1284. closedir($hDir);
  1285. }
  1286. return $aResult;
  1287. }
  1288. public function ValueFromDOMNode($oDOMNode)
  1289. {
  1290. return $oDOMNode->textContent;
  1291. }
  1292. public function ValueToDOMNode($oDOMNode, $value)
  1293. {
  1294. $oTextNode = $oDOMNode->ownerDocument->createTextNode($value);
  1295. $oDOMNode->appendChild($oTextNode);
  1296. }
  1297. public function MakeFileUrl($value)
  1298. {
  1299. return utils::GetAbsoluteUrlModulesRoot().$value;
  1300. }
  1301. public function GetDefaultValue($sClass = 'Contact')
  1302. {
  1303. $sIcon = '';
  1304. if (MetaModel::IsValidClass($sClass))
  1305. {
  1306. $sIconPath = MetaModel::GetClassIcon($sClass, false);
  1307. $sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIconPath);
  1308. }
  1309. return $sIcon;
  1310. }
  1311. }
  1312. class DesignerSortableField extends DesignerFormField
  1313. {
  1314. protected $aAllowedValues;
  1315. public function __construct($sCode, $sLabel = '', $defaultValue = '')
  1316. {
  1317. parent::__construct($sCode, $sLabel, $defaultValue);
  1318. $this->aAllowedValues = array();
  1319. }
  1320. public function SetAllowedValues($aAllowedValues)
  1321. {
  1322. $this->aAllowedValues = $aAllowedValues;
  1323. }
  1324. public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
  1325. {
  1326. $bOpen = false;
  1327. $sId = $this->oForm->GetFieldId($this->sCode);
  1328. $sName = $this->oForm->GetFieldName($this->sCode);
  1329. $sReadOnly = $this->IsReadOnly() ? 'readonly="readonly"' : '';
  1330. $aResult = array('label' => $this->sLabel, 'value' => "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" $sReadOnly value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\">");
  1331. $sJSFields = json_encode(array_keys($this->aAllowedValues));
  1332. $oP->add_ready_script(
  1333. "$('#$sId').sortable_field({aAvailableFields: $sJSFields});"
  1334. );
  1335. return $aResult;
  1336. }
  1337. }
  1338. class DesignerFormSelectorField extends DesignerFormField
  1339. {
  1340. protected $aSubForms;
  1341. protected $defaultRealValue; // What's stored as default value is actually the index
  1342. protected $bSorted;
  1343. public function __construct($sCode, $sLabel = '', $defaultValue = '')
  1344. {
  1345. parent::__construct($sCode, $sLabel, 0);
  1346. $this->defaultRealValue = $defaultValue;
  1347. $this->aSubForms = array();
  1348. $this->bSorted = true;
  1349. }
  1350. public function IsSorted()
  1351. {
  1352. return $this->bSorted;
  1353. }
  1354. public function SetSorted($bSorted)
  1355. {
  1356. $this->bSorted = $bSorted;
  1357. }
  1358. /**
  1359. * Callback for sorting an array of $aFormData based ont he labels of the subforms
  1360. * @param unknown $aItem1
  1361. * @param unknown $aItem2
  1362. * @return number
  1363. */
  1364. static function SortOnFormLabel($aItem1, $aItem2)
  1365. {
  1366. return strcasecmp($aItem1['label'], $aItem2['label']);
  1367. }
  1368. public function GetWidgetClass()
  1369. {
  1370. return 'selector_property_field';
  1371. }
  1372. public function AddSubForm($oSubForm, $sLabel, $sValue)
  1373. {
  1374. $idx = count($this->aSubForms);
  1375. $this->aSubForms[] = array('form' => $oSubForm, 'label' => $sLabel, 'value' => $sValue);
  1376. if ($sValue == $this->defaultRealValue)
  1377. {
  1378. // Store the index of the selected/default form
  1379. $this->defaultValue = count($this->aSubForms) - 1;
  1380. }
  1381. }
  1382. public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
  1383. {
  1384. $sId = $this->oForm->GetFieldId($this->sCode);
  1385. $sName = $this->oForm->GetFieldName($this->sCode);
  1386. $sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : '';
  1387. $this->aCSSClasses[] = 'formSelector';
  1388. $sCSSClasses = '';
  1389. if (count($this->aCSSClasses) > 0)
  1390. {
  1391. $sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
  1392. }
  1393. if ($this->IsSorted())
  1394. {
  1395. uasort($this->aSubForms, array(get_class($this), 'SortOnFormLabel'));
  1396. }
  1397. if ($this->IsReadOnly())
  1398. {
  1399. $aSelected = array();
  1400. $aHiddenValues = array();
  1401. $sDisplayValue = '';
  1402. $sHiddenValue = '';
  1403. foreach($this->aSubForms as $iKey => $aFormData)
  1404. {
  1405. if ($iKey == $this->defaultValue) // Default value is actually the index
  1406. {
  1407. $sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');
  1408. $sHiddenValue = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($iKey, ENT_QUOTES, 'UTF-8')."\"/>";
  1409. break;
  1410. }
  1411. }
  1412. $sHtml = "<span $sCSSClasses>".$sDisplayValue.$sHiddenValue."</span>";
  1413. }
  1414. else
  1415. {
  1416. $sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\" $sReadOnly>";
  1417. foreach($this->aSubForms as $iKey => $aFormData)
  1418. {
  1419. $sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');
  1420. $sValue = htmlentities($aFormData['value'], ENT_QUOTES, 'UTF-8');
  1421. $sSelected = ($iKey == $this->defaultValue) ? 'selected' : '';
  1422. $sHtml .= "<option data-value=\"$sValue\" value=\"$iKey\" $sSelected>".$sDisplayValue."</option>";
  1423. }
  1424. $sHtml .= "</select>";
  1425. }
  1426. if ($sRenderMode == 'property')
  1427. {
  1428. $sHtml .= '</td><td class="prop_icon prop_apply"><span title="Apply" class="ui-icon ui-icon-circle-check"/></td><td class="prop_icon prop_cancel"><span title="Revert" class="ui-icon ui-icon-circle-close"/></td></tr>';
  1429. }
  1430. foreach($this->aSubForms as $sKey => $aFormData)
  1431. {
  1432. $sId = $this->oForm->GetFieldId($this->sCode);
  1433. $sStyle = (($sKey == $this->defaultValue) && $this->oForm->IsDisplayed()) ? '' : 'style="display:none"';
  1434. $oSubForm = $aFormData['form'];
  1435. $oSubForm->SetParentForm($this->oForm);
  1436. $oSubForm->CopySubmitParams($this->oForm);
  1437. $oSubForm->SetPrefix($this->oForm->GetPrefix().$sKey.'_');
  1438. if ($sRenderMode == 'property')
  1439. {
  1440. // Note: Managing the visibility of nested subforms had several implications
  1441. // 1) Attributes are displayed in a table and we have to group them in as many tbodys as necessary to hide/show the various options depending on the current selection
  1442. // 2) It is not possible to nest tbody tags. Therefore, it is not possible to manage the visibility the same way as it is done for the dialog mode (using nested divs).
  1443. // The div hierarchy has been emulated by adding attributes to the tbody tags:
  1444. // - data-selector : uniquely identifies the DesignerFormSelectorField that has an impact on the visibility of the node
  1445. // - data-path : uniquely identifies the combination of users choices that must be made to show the node
  1446. // - data-state : records the state, depending on the user choice on the FormSelectorField just above the node, but indepentantly from the visibility in the page (can be visible in the form itself being in a hidden form)
  1447. // Then a series of actions are performed to hide and show the relevant nodes, depending on the user choice
  1448. $sSelector = $this->oForm->GetHierarchyPath().'/'.$this->sCode.$this->oForm->GetSuffix();
  1449. $oSubForm->SetHierarchyParent($sSelector);
  1450. $sPath = $this->oForm->GetHierarchyPath().'/'.$this->sCode.$this->oForm->GetSuffix().'-'.$sKey;
  1451. $oSubForm->SetHierarchyPath($sPath);
  1452. $oSubForm->SetDisplayed($sKey == $this->defaultValue);
  1453. $sState = ($sKey == $this->defaultValue) ? 'visible' : 'hidden';
  1454. //$sHtml .= "</tbody><tbody data-selector=\"$sSelector\" data-path=\"$sPath\" data-state=\"$sState\" $sStyle>";
  1455. $sHtml .= $oSubForm->RenderAsPropertySheet($oP, true);
  1456. $sState = $this->oForm->IsDisplayed() ? 'visible' : 'hidden';
  1457. $sParentStyle = '';
  1458. if ($oParent = $this->oForm->GetParentForm())
  1459. {
  1460. $sParentStyle = ($oParent->IsDisplayed()) ? '' : 'style="display:none"';
  1461. $sParentSelector = $oParent->GetHierarchyParent();
  1462. $sParentPath = $oParent->GetHierarchyPath();
  1463. }
  1464. else
  1465. {
  1466. $sParentSelector = '';
  1467. $sParentPath = '';
  1468. }
  1469. //$sHtml .= "</tbody><tbody data-selector=\"$sParentSelector\" data-path=\"$sParentPath\" data-state=\"$sState\" $sParentStyle>";
  1470. }
  1471. else
  1472. {
  1473. $sHtml .= "<div class=\"subform_{$sId} {$sId}_{$sKey}\" $sStyle>";
  1474. $sHtml .= $oSubForm->Render($oP, true);
  1475. $sHtml .= "</div>";
  1476. }
  1477. }
  1478. if ($sRenderMode == 'property')
  1479. {
  1480. $sSelector = $this->oForm->GetHierarchyPath().'/'.$this->sCode.$this->oForm->GetSuffix();
  1481. $this->aWidgetExtraParams['data_selector'] = $sSelector;
  1482. }
  1483. else
  1484. {
  1485. $oP->add_ready_script(
  1486. <<<EOF
  1487. $('#$sId').bind('change reverted', function() { $('.subform_{$sId}').hide(); $('.{$sId}_'+this.value).show(); } );
  1488. EOF
  1489. );
  1490. }
  1491. return array('label' => $this->sLabel, 'value' => $sHtml);
  1492. }
  1493. public function ReadParam(&$aValues)
  1494. {
  1495. parent::ReadParam($aValues);
  1496. $sKey = $aValues[$this->sCode];
  1497. $aValues[$this->sCode] = $this->aSubForms[$sKey]['value'];
  1498. $this->aSubForms[$sKey]['form']->SetPrefix($this->oForm->GetPrefix().$sKey.'_');
  1499. $this->aSubForms[$sKey]['form']->SetParentForm($this->oForm);
  1500. $this->aSubForms[$sKey]['form']->ReadParams($aValues);
  1501. }
  1502. public function SetDefaultValueFrom($aAllDefaultValues)
  1503. {
  1504. if (array_key_exists($this->GetCode(), $aAllDefaultValues))
  1505. {
  1506. $selectedValue = $aAllDefaultValues[$this->GetCode()];
  1507. foreach($this->aSubForms as $iKey => $aFormData)
  1508. {
  1509. $sId = $this->oForm->GetFieldId($this->sCode);
  1510. if ($selectedValue == $aFormData['value'])
  1511. {
  1512. $this->defaultValue =$iKey;
  1513. $aDefaultValues = $this->oForm->GetDefaultValues();
  1514. $oSubForm = $aFormData['form'];
  1515. $oSubForm->SetDefaultValues($aAllDefaultValues);
  1516. }
  1517. }
  1518. }
  1519. }
  1520. public function FindField($sFieldCode)
  1521. {
  1522. $oField = parent::FindField($sFieldCode);
  1523. if ($oField === false)
  1524. {
  1525. // Look in the subforms
  1526. foreach($this->aSubForms as $sKey => $aFormData)
  1527. {
  1528. $oSubForm = $aFormData['form'];
  1529. $oField = $oSubForm->FindField($sFieldCode);
  1530. if ($oField !== false)
  1531. {
  1532. break;
  1533. }
  1534. }
  1535. }
  1536. return $oField;
  1537. }
  1538. }
  1539. class DesignerSubFormField extends DesignerFormField
  1540. {
  1541. protected $oSubForm;
  1542. public function __construct($sLabel, $oSubForm)
  1543. {
  1544. parent::__construct('', $sLabel, '');
  1545. $this->oSubForm = $oSubForm;
  1546. }
  1547. public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
  1548. {
  1549. $this->oSubForm->SetParentForm($this->oForm);
  1550. $this->oSubForm->CopySubmitParams($this->oForm);
  1551. if ($sRenderMode == 'property')
  1552. {
  1553. $sHtml = $this->oSubForm->RenderAsPropertySheet($oP, true);
  1554. }
  1555. else
  1556. {
  1557. $sHtml = $this->oSubForm->Render($oP, true);
  1558. }
  1559. return array('label' => $this->sLabel, 'value' => $sHtml);
  1560. }
  1561. public function ReadParam(&$aValues)
  1562. {
  1563. $this->oSubForm->SetParentForm($this->oForm);
  1564. $this->oSubForm->ReadParams($aValues);
  1565. }
  1566. public function FindField($sFieldCode)
  1567. {
  1568. $oField = parent::FindField($sFieldCode);
  1569. if ($oField === false)
  1570. {
  1571. // Look in the subform
  1572. $oField = $this->oSubForm->FindField($sFieldCode);
  1573. }
  1574. return $oField;
  1575. }
  1576. }
  1577. class DesignerStaticTextField extends DesignerFormField
  1578. {
  1579. public function __construct($sCode, $sLabel = '', $defaultValue = '')
  1580. {
  1581. parent::__construct($sCode, $sLabel, $defaultValue);
  1582. }
  1583. public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
  1584. {
  1585. return array('label' => $this->sLabel, 'value' => $this->defaultValue);
  1586. }
  1587. }