ui.extkeywidget.class.inc.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. <?php
  2. // Copyright (C) 2010-2017 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. * Class UIExtKeyWidget
  20. * UI wdiget for displaying and editing external keys when
  21. * A simple drop-down list is not enough...
  22. *
  23. * The layout is the following
  24. *
  25. * +-- #label_<id> (input)-------+ +-----------+
  26. * | | | Browse... |
  27. * +-----------------------------+ +-----------+
  28. *
  29. * And the popup dialog has the following layout:
  30. *
  31. * +------------------- ac_dlg_<id> (div)-----------+
  32. * + +--- ds_<id> (div)---------------------------+ |
  33. * | | +------------- fs_<id> (form)------------+ | |
  34. * | | | +--------+---+ | | |
  35. * | | | | Class | V | | | |
  36. * | | | +--------+---+ | | |
  37. * | | | | | |
  38. * | | | S e a r c h F o r m | | |
  39. * | | | +--------+ | | |
  40. * | | | | Search | | | |
  41. * | | | +--------+ | | |
  42. * | | +----------------------------------------+ | |
  43. * | +--------------+-dh_<id>-+--------------------+ |
  44. * | \ Search / |
  45. * | +------+ |
  46. * | +--- fr_<id> (form)--------------------------+ |
  47. * | | +------------ dr_<id> (div)--------------+ | |
  48. * | | | | | |
  49. * | | | S e a r c h R e s u l t s | | |
  50. * | | | | | |
  51. * | | +----------------------------------------+ | |
  52. * | | +--------+ +-----+ | |
  53. * | | | Cancel | | Add | | |
  54. * | | +--------+ +-----+ | |
  55. * | +--------------------------------------------+ |
  56. * +------------------------------------------------+
  57. * @copyright Copyright (C) 2010-2017 Combodo SARL
  58. * @license http://opensource.org/licenses/AGPL-3.0
  59. */
  60. require_once(APPROOT.'/application/webpage.class.inc.php');
  61. require_once(APPROOT.'/application/displayblock.class.inc.php');
  62. class UIExtKeyWidget
  63. {
  64. protected $iId;
  65. protected $sTargetClass;
  66. protected $sAttCode;
  67. protected $bSearchMode;
  68. //public function __construct($sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '')
  69. static public function DisplayFromAttCode($oPage, $sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName = '', $sFormPrefix = '', $aArgs, $bSearchMode = false)
  70. {
  71. $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
  72. $sTargetClass = $oAttDef->GetTargetClass();
  73. $iMaxComboLength = $oAttDef->GetMaximumComboLength();
  74. $bAllowTargetCreation = $oAttDef->AllowTargetCreation();
  75. if (!$bSearchMode)
  76. {
  77. $sDisplayStyle = $oAttDef->GetDisplayStyle();
  78. }
  79. else
  80. {
  81. $sDisplayStyle = 'select'; // In search mode, always use a drop-down list
  82. }
  83. $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode);
  84. return $oWidget->Display($oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix, $aArgs, null, $sDisplayStyle);
  85. }
  86. public function __construct($sTargetClass, $iInputId, $sAttCode = '', $bSearchMode = false)
  87. {
  88. $this->sTargetClass = $sTargetClass;
  89. $this->iId = $iInputId;
  90. $this->sAttCode = $sAttCode;
  91. $this->bSearchMode = $bSearchMode;
  92. }
  93. /**
  94. * Get the HTML fragment corresponding to the ext key editing widget
  95. * @param WebPage $oP The web page used for all the output
  96. * @param Hash $aArgs Extra context arguments
  97. * @return string The HTML fragment to be inserted into the page
  98. */
  99. public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, DBObjectset $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select', $bSearchMultiple = true)
  100. {
  101. if (!is_null($bSearchMode))
  102. {
  103. $this->bSearchMode = $bSearchMode;
  104. }
  105. $sTitle = addslashes($sTitle);
  106. $oPage->add_linked_script('../js/extkeywidget.js');
  107. $oPage->add_linked_script('../js/forms-json-utils.js');
  108. $bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
  109. $bExtensions = true;
  110. $sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
  111. $sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
  112. $sHTMLValue = "<div class=\"field_input_zone field_input_extkey\">";
  113. $sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL());
  114. if($this->bSearchMode)
  115. {
  116. $sWizHelper = 'null';
  117. $sWizHelperJSON = "''";
  118. $sJSSearchMode = 'true';
  119. }
  120. else
  121. {
  122. if (isset($aArgs['wizHelper']))
  123. {
  124. $sWizHelper = $aArgs['wizHelper'];
  125. }
  126. else
  127. {
  128. $sWizHelper = 'oWizardHelper'.$sFormPrefix;
  129. }
  130. $sWizHelperJSON = $sWizHelper.'.UpdateWizardToJSON()';
  131. $sJSSearchMode = 'false';
  132. }
  133. if (is_null($oAllowedValues))
  134. {
  135. throw new Exception('Implementation: null value for allowed values definition');
  136. }
  137. $oAllowedValues->SetShowObsoleteData(utils::ShowObsoleteData());
  138. if ($oAllowedValues->Count() < $iMaxComboLength)
  139. {
  140. // Discrete list of values, use a SELECT or RADIO buttons depending on the config
  141. switch($sDisplayStyle)
  142. {
  143. case 'radio':
  144. case 'radio_horizontal':
  145. case 'radio_vertical':
  146. $sValidationField = null;
  147. $bVertical = ($sDisplayStyle != 'radio_horizontal');
  148. $bExtensions = false;
  149. $oAllowedValues->Rewind();
  150. $aAllowedValues = array();
  151. while($oObj = $oAllowedValues->Fetch())
  152. {
  153. $aAllowedValues[$oObj->GetKey()] = $oObj->GetName();
  154. }
  155. $sHTMLValue .= $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", false /* $bMandatory will be placed manually */, $bVertical, $sValidationField);
  156. $aEventsList[] ='change';
  157. break;
  158. case 'select':
  159. case 'list':
  160. default:
  161. $sSelectMode = 'true';
  162. $sHelpText = ''; //$this->oAttDef->GetHelpOnEdition();
  163. $sHTMLValue .= "<div class=\"field_select_wrapper\">\n";
  164. if ($this->bSearchMode)
  165. {
  166. if ($bSearchMultiple)
  167. {
  168. $sHTMLValue .= "<select class=\"multiselect\" multiple title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}[]\" id=\"$this->iId\">\n";
  169. }
  170. else
  171. {
  172. $sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
  173. $sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : Dict::S('UI:SearchValue:Any');
  174. $sHTMLValue .= "<option value=\"\">$sDisplayValue</option>\n";
  175. }
  176. }
  177. else
  178. {
  179. $sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
  180. $sHTMLValue .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>\n";
  181. }
  182. $oAllowedValues->Rewind();
  183. while($oObj = $oAllowedValues->Fetch())
  184. {
  185. $key = $oObj->GetKey();
  186. $display_value = $oObj->GetName();
  187. if (($oAllowedValues->Count() == 1) && ($bMandatory == 'true') )
  188. {
  189. // When there is only once choice, select it by default
  190. $sSelected = ' selected';
  191. }
  192. else
  193. {
  194. $sSelected = (is_array($value) && in_array($key, $value)) || ($value == $key) ? ' selected' : '';
  195. }
  196. $sHTMLValue .= "<option value=\"$key\"$sSelected>$display_value</option>\n";
  197. }
  198. $sHTMLValue .= "</select>\n";
  199. $sHTMLValue .= "</div>\n";
  200. if (($this->bSearchMode) && $bSearchMultiple)
  201. {
  202. $aOptions = array(
  203. 'header' => true,
  204. 'checkAllText' => Dict::S('UI:SearchValue:CheckAll'),
  205. 'uncheckAllText' => Dict::S('UI:SearchValue:UncheckAll'),
  206. 'noneSelectedText' => Dict::S('UI:SearchValue:Any'),
  207. 'selectedText' => Dict::S('UI:SearchValue:NbSelected'),
  208. 'selectedList' => 1,
  209. );
  210. $sJSOptions = json_encode($aOptions);
  211. $oPage->add_ready_script("$('.multiselect').multiselect($sJSOptions);");
  212. }
  213. $oPage->add_ready_script(
  214. <<<EOF
  215. oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode);
  216. oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
  217. $('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
  218. $('#$this->iId').bind('change', function() { $(this).trigger('extkeychange') } );
  219. EOF
  220. );
  221. } // Switch
  222. }
  223. else
  224. {
  225. // Too many choices, use an autocomplete
  226. $sSelectMode = 'false';
  227. // Check that the given value is allowed
  228. $oSearch = $oAllowedValues->GetFilter();
  229. $oSearch->AddCondition('id', $value);
  230. $oSet = new DBObjectSet($oSearch);
  231. if ($oSet->Count() == 0)
  232. {
  233. $value = null;
  234. }
  235. if (is_null($value) || ($value == 0)) // Null values are displayed as ''
  236. {
  237. $sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : '';
  238. }
  239. else
  240. {
  241. $sDisplayValue = $this->GetObjectName($value);
  242. }
  243. $iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 3; //@@@ $this->oAttDef->GetMinAutoCompleteChars();
  244. $iFieldSize = isset($aArgs['iFieldSize']) ? $aArgs['iFieldSize'] : 20; //@@@ $this->oAttDef->GetMaxSize();
  245. // the input for the auto-complete
  246. $sHTMLValue .= "<input class=\"field_autocomplete\" count=\"".$oAllowedValues->Count()."\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
  247. $sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_search.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.Search();\"/></span>";
  248. // another hidden input to store & pass the object's Id
  249. $sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n";
  250. $JSSearchMode = $this->bSearchMode ? 'true' : 'false';
  251. // Scripts to start the autocomplete and bind some events to it
  252. $oPage->add_ready_script(
  253. <<<EOF
  254. oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode);
  255. oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
  256. $('#label_$this->iId').autocomplete(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { scroll:true, minChars:{$iMinChars}, autoFill:false, matchContains:true, mustMatch: true, keyHolder:'#{$this->iId}', extraParams:{operation:'ac_extkey', sTargetClass:'{$this->sTargetClass}',sFilter:'$sFilter',bSearchMode:$JSSearchMode, json: function() { return $sWizHelperJSON; } }});
  257. $('#label_$this->iId').keyup(function() { if ($(this).val() == '') { $('#$this->iId').val(''); } } ); // Useful for search forms: empty value in the "label", means no value, immediatly !
  258. $('#label_$this->iId').result( function(event, data, formatted) { OnAutoComplete('{$this->iId}', event, data, formatted); } );
  259. $('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
  260. if ($('#ac_dlg_{$this->iId}').length == 0)
  261. {
  262. $('body').append('<div id="ac_dlg_{$this->iId}"></div>');
  263. }
  264. EOF
  265. );
  266. }
  267. if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false)
  268. {
  269. $sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_tree_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_tree.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"/></span>";
  270. $oPage->add_ready_script(
  271. <<<EOF
  272. if ($('#ac_tree_{$this->iId}').length == 0)
  273. {
  274. $('body').append('<div id="ac_tree_{$this->iId}"></div>');
  275. }
  276. EOF
  277. );
  278. }
  279. if ($bCreate && $bExtensions)
  280. {
  281. $sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject';
  282. $sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_add_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_add.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"/></span>";
  283. $oPage->add_ready_script(
  284. <<<EOF
  285. if ($('#ajax_{$this->iId}').length == 0)
  286. {
  287. $('body').append('<div id="ajax_{$this->iId}"></div>');
  288. }
  289. EOF
  290. );
  291. }
  292. $sHTMLValue .= "</div>";
  293. // Note: This test is no longer necessary as we changed the markup to extract validation decoration in the standard .field_input_xxx container
  294. //if (($sDisplayStyle == 'select') || ($sDisplayStyle == 'list'))
  295. //{
  296. $sHTMLValue .= "<span class=\"form_validation\" id=\"v_{$this->iId}\"></span><span class=\"field_status\" id=\"fstatus_{$this->iId}\"></span>";
  297. //}
  298. return $sHTMLValue;
  299. }
  300. public function GetSearchDialog(WebPage $oPage, $sTitle, $oCurrObject = null)
  301. {
  302. $sHTML = '<div class="wizContainer" style="vertical-align:top;"><div id="dc_'.$this->iId.'">';
  303. if ( ($oCurrObject != null) && ($this->sAttCode != ''))
  304. {
  305. $oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode);
  306. $aArgs = array('this' => $oCurrObject);
  307. $aParams = array('query_params' => $aArgs);
  308. $oSet = $oAttDef->GetAllowedValuesAsObjectSet($aArgs);
  309. $oFilter = $oSet->GetFilter();
  310. }
  311. else
  312. {
  313. $aParams = array();
  314. $oFilter = new DBObjectSearch($this->sTargetClass);
  315. }
  316. $bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
  317. $oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
  318. $oBlock = new DisplayBlock($oFilter, 'search', false, $aParams);
  319. $sHTML .= $oBlock->GetDisplay($oPage, $this->iId, array('open' => $bOpen, 'currentId' => $this->iId));
  320. $sHTML .= "<form id=\"fr_{$this->iId}\" OnSubmit=\"return oACWidget_{$this->iId}.DoOk();\">\n";
  321. $sHTML .= "<div id=\"dr_{$this->iId}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
  322. $sHTML .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
  323. $sHTML .= "</div>\n";
  324. $sHTML .= "<input type=\"button\" id=\"btn_cancel_{$this->iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#ac_dlg_{$this->iId}').dialog('close');\">&nbsp;&nbsp;";
  325. $sHTML .= "<input type=\"button\" id=\"btn_ok_{$this->iId}\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"oACWidget_{$this->iId}.DoOk();\">";
  326. $sHTML .= "<input type=\"hidden\" id=\"count_{$this->iId}\" value=\"0\">";
  327. $sHTML .= "</form>\n";
  328. $sHTML .= '</div></div>';
  329. $sDialogTitle = addslashes($sTitle);
  330. $oPage->add_ready_script(
  331. <<<EOF
  332. $('#ac_dlg_{$this->iId}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, title: '$sDialogTitle', resizeStop: oACWidget_{$this->iId}.UpdateSizes, close: oACWidget_{$this->iId}.OnClose });
  333. $('#fs_{$this->iId}').bind('submit.uiAutocomplete', oACWidget_{$this->iId}.DoSearchObjects);
  334. $('#dc_{$this->iId}').resize(oACWidget_{$this->iId}.UpdateSizes);
  335. EOF
  336. );
  337. $oPage->add($sHTML);
  338. }
  339. /**
  340. * Search for objects to be selected
  341. * @param WebPage $oP The page used for the output (usually an AjaxWebPage)
  342. * @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of m_sRemoteClass
  343. * @param Array $aAlreadyLinkedIds List of IDs of objects of "remote" class already linked, to be filtered out of the search
  344. */
  345. public function SearchObjectsToSelect(WebPage $oP, $sFilter, $sRemoteClass = '', $oObj = null)
  346. {
  347. if (is_null($sFilter))
  348. {
  349. throw new Exception('Implementation: null value for allowed values definition');
  350. }
  351. $oFilter = DBObjectSearch::FromOQL($sFilter);
  352. if (strlen($sRemoteClass) > 0)
  353. {
  354. $oFilter->ChangeClass($sRemoteClass);
  355. }
  356. $oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
  357. // Current extkey value, so we can display event if it is not available anymore (eg. archived).
  358. $iCurrentExtKeyId = (is_null($oObj)) ? 0 : $oObj->Get($this->sAttCode);
  359. $oBlock = new DisplayBlock($oFilter, 'list', false, array('query_params' => array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId)));
  360. $oBlock->Display($oP, $this->iId.'_results', array('this' => $oObj, 'cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'table_id' => 'select_'.$this->sAttCode)); // Don't display the 'Actions' menu on the results
  361. }
  362. /**
  363. * Search for objects to be selected
  364. * @param WebPage $oP The page used for the output (usually an AjaxWebPage)
  365. * @param string $sFilter The OQL expression used to define/limit limit the scope of possible values
  366. * @param DBObject $oObj The current object for the OQL context
  367. * @param string $sContains The text of the autocomplete to filter the results
  368. */
  369. public function AutoComplete(WebPage $oP, $sFilter, $oObj = null, $sContains)
  370. {
  371. if (is_null($sFilter))
  372. {
  373. throw new Exception('Implementation: null value for allowed values definition');
  374. }
  375. // Current extkey value, so we can display event if it is not available anymore (eg. archived).
  376. $iCurrentExtKeyId = (is_null($oObj) || $this->sAttCode === '') ? 0 : $oObj->Get($this->sAttCode);
  377. $oValuesSet = new ValueSetObjects($sFilter, 'friendlyname'); // Bypass GetName() to avoid the encoding by htmlentities
  378. $oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
  379. $aValues = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains);
  380. foreach($aValues as $sKey => $sFriendlyName)
  381. {
  382. $oP->add(trim($sFriendlyName)."\t".$sKey."\n");
  383. }
  384. }
  385. /**
  386. * Get the display name of the selected object, to fill back the autocomplete
  387. */
  388. public function GetObjectName($iObjId)
  389. {
  390. $aModifierProps = array();
  391. $aModifierProps['UserRightsGetSelectFilter']['bSearchMode'] = $this->bSearchMode;
  392. $oObj = MetaModel::GetObject($this->sTargetClass, $iObjId, false, false, $aModifierProps);
  393. if ($oObj)
  394. {
  395. return $oObj->GetName();
  396. }
  397. else
  398. {
  399. return '';
  400. }
  401. }
  402. /**
  403. * Get the form to select a leaf class from the $this->sTargetClass (that should be abstract)
  404. * Note: Inspired from UILinksWidgetDirect::GetObjectCreationDialog()
  405. *
  406. * @param WebPage $oPage
  407. */
  408. public function GetClassSelectionForm(WebPage $oPage)
  409. {
  410. // For security reasons: check that the "proposed" class is actually a subclass of the linked class
  411. // and that the current user is allowed to create objects of this class
  412. $aSubClasses = MetaModel::EnumChildClasses($this->sTargetClass);
  413. $aPossibleClasses = array();
  414. foreach($aSubClasses as $sCandidateClass)
  415. {
  416. if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
  417. {
  418. $aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass);
  419. }
  420. }
  421. $sDialogTitle = '';
  422. $oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">');
  423. $oPage->add('<form>');
  424. $sClassLabel = MetaModel::GetName($this->sTargetClass);
  425. $oPage->add('<p>'.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel));
  426. $oPage->add('<nobr><select name="class">');
  427. asort($aPossibleClasses);
  428. foreach($aPossibleClasses as $sClassName => $sClassLabel)
  429. {
  430. $oPage->add("<option value=\"$sClassName\">$sClassLabel</option>");
  431. }
  432. $oPage->add('</select>');
  433. $oPage->add('&nbsp; <button type="submit" class="action" style="margin-top:15px;"><span>' . Dict::S('UI:Button:Ok') . '</span></button></nobr></p>');
  434. $oPage->add('</form>');
  435. $oPage->add('</div></div></div>');
  436. $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
  437. $oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');");
  438. $oPage->add_ready_script("$('#dcr_{$this->iId} form').bind('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);");
  439. }
  440. /**
  441. * Get the form to create a new object of the 'target' class
  442. */
  443. public function GetObjectCreationForm(WebPage $oPage, $oCurrObject)
  444. {
  445. // Set all the default values in an object and clone this "default" object
  446. $oNewObj = MetaModel::NewObject($this->sTargetClass);
  447. // 1st - set context values
  448. $oAppContext = new ApplicationContext();
  449. $oAppContext->InitObjectFromContext($oNewObj);
  450. // 2nd set the default values from the constraint on the external key... if any
  451. if ( ($oCurrObject != null) && ($this->sAttCode != ''))
  452. {
  453. $oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode);
  454. $aParams = array('this' => $oCurrObject);
  455. $oSet = $oAttDef->GetAllowedValuesAsObjectSet($aParams);
  456. $aConsts = $oSet->ListConstantFields();
  457. $sClassAlias = $oSet->GetFilter()->GetClassAlias();
  458. if (isset($aConsts[$sClassAlias]))
  459. {
  460. foreach($aConsts[$sClassAlias] as $sAttCode => $value)
  461. {
  462. $oNewObj->Set($sAttCode, $value);
  463. }
  464. }
  465. }
  466. // 3rd - set values from the page argument 'default'
  467. $oNewObj->UpdateObjectFromArg('default');
  468. $sDialogTitle = '';
  469. $oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">');
  470. $oPage->add("<h1>".MetaModel::GetClassIcon($this->sTargetClass)."&nbsp;".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sTargetClass))."</h1>\n");
  471. $aFieldsFlags = array();
  472. $aFieldsComments = array();
  473. foreach(MetaModel::ListAttributeDefs($this->sTargetClass) as $sAttCode => $oAttDef)
  474. {
  475. if (($oAttDef instanceof AttributeBlob) || (false))
  476. {
  477. $aFieldsFlags[$sAttCode] = OPT_ATT_READONLY;
  478. $aFieldsComments[$sAttCode] = '&nbsp;<img src="../images/transp-lock.png" style="vertical-align:middle" title="'.htmlentities(Dict::S('UI:UploadNotSupportedInThisMode')).'"/>';
  479. }
  480. }
  481. cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true, 'fieldsFlags' => $aFieldsFlags, 'fieldsComments' => $aFieldsComments));
  482. $oPage->add('</div></div></div>');
  483. // $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: $(window).width()*0.8, height: 'auto', autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
  484. $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
  485. $oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');");
  486. $oPage->add_ready_script("$('#dcr_{$this->iId} form').bind('submit.uilinksWizard', oACWidget_{$this->iId}.DoCreateObject);");
  487. }
  488. /**
  489. * Display the hierarchy of the 'target' class
  490. */
  491. public function DisplayHierarchy(WebPage $oPage, $sFilter, $currValue, $oObj)
  492. {
  493. $sDialogTitle = addslashes(Dict::Format('UI:HierarchyOf_Class', MetaModel::GetName($this->sTargetClass)));
  494. $oPage->add('<div id="dlg_tree_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div style="overflow:auto;background:#fff;margin-bottom:5px;" id="tree_'.$this->iId.'">');
  495. $oPage->add('<table style="width:100%"><tr><td>');
  496. if (is_null($sFilter))
  497. {
  498. throw new Exception('Implementation: null value for allowed values definition');
  499. }
  500. try
  501. {
  502. $oFilter = DBObjectSearch::FromOQL($sFilter);
  503. $oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
  504. $oSet = new DBObjectSet($oFilter, array(), array('this' => $oObj, 'current_extkey_id' => $currValue));
  505. }
  506. catch(MissingQueryArgument $e)
  507. {
  508. // When used in a search form the $this parameter may be missing, in this case return all possible values...
  509. // TODO check if we can improve this behavior...
  510. $sOQL = 'SELECT '.$this->m_sTargetClass;
  511. $oFilter = DBObjectSearch::FromOQL($sOQL);
  512. $oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
  513. $oSet = new DBObjectSet($oFilter);
  514. }
  515. $oSet->SetShowObsoleteData(utils::ShowObsoleteData());
  516. $sHKAttCode = MetaModel::IsHierarchicalClass($this->sTargetClass);
  517. $this->DumpTree($oPage, $oSet, $sHKAttCode, $currValue);
  518. $oPage->add('</td></tr></table>');
  519. $oPage->add('</div>');
  520. $oPage->add("<input type=\"button\" id=\"btn_cancel_{$this->iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_tree_{$this->iId}').dialog('close');\">&nbsp;&nbsp;");
  521. $oPage->add("<input type=\"button\" id=\"btn_ok_{$this->iId}\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"oACWidget_{$this->iId}.DoHKOk();\">");
  522. $oPage->add('</div></div>');
  523. $oPage->add_ready_script("\$('#tree_$this->iId ul').treeview();\n");
  524. $oPage->add_ready_script("\$('#dlg_tree_$this->iId').dialog({ width: 'auto', height: 'auto', autoOpen: true, modal: true, title: '$sDialogTitle', resizeStop: oACWidget_{$this->iId}.OnHKResize, close: oACWidget_{$this->iId}.OnHKClose });\n");
  525. }
  526. /**
  527. * Get the form to create a new object of the 'target' class
  528. */
  529. public function DoCreateObject($oPage)
  530. {
  531. try
  532. {
  533. $oObj = MetaModel::NewObject($this->sTargetClass);
  534. $aErrors = $oObj->UpdateObjectFromPostedForm($this->iId);
  535. if (count($aErrors) == 0)
  536. {
  537. $oObj->DBInsert();
  538. return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
  539. }
  540. else
  541. {
  542. return array('error' => implode(' ', $aErrors), 'id' => 0);
  543. }
  544. }
  545. catch(Exception $e)
  546. {
  547. return array('error' => $e->getMessage(), 'id' => 0);
  548. }
  549. }
  550. function DumpTree($oP, $oSet, $sParentAttCode, $currValue)
  551. {
  552. $aTree = array();
  553. $aNodes = array();
  554. while($oObj = $oSet->Fetch())
  555. {
  556. $iParentId = $oObj->Get($sParentAttCode);
  557. if (!isset($aTree[$iParentId]))
  558. {
  559. $aTree[$iParentId] = array();
  560. }
  561. $aTree[$iParentId][$oObj->GetKey()] = $oObj->GetName();
  562. $aNodes[$oObj->GetKey()] = $oObj;
  563. }
  564. $aParents = array_keys($aTree);
  565. $aRoots = array();
  566. foreach($aParents as $id)
  567. {
  568. if (!array_key_exists($id, $aNodes))
  569. {
  570. $aRoots[] = $id;
  571. }
  572. }
  573. foreach($aRoots as $iRootId)
  574. {
  575. $this->DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue);
  576. }
  577. }
  578. function DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue)
  579. {
  580. $bSelect = true;
  581. $bMultiple = false;
  582. $sSelect = '';
  583. if (array_key_exists($iRootId, $aTree))
  584. {
  585. $aSortedRoots = $aTree[$iRootId];
  586. asort($aSortedRoots);
  587. $oP->add("<ul>\n");
  588. $fUniqueId = microtime(true);
  589. foreach($aSortedRoots as $id => $sName)
  590. {
  591. if ($bSelect)
  592. {
  593. $sChecked = ($aNodes[$id]->GetKey() == $currValue) ? 'checked' : '';
  594. if ($bMultiple)
  595. {
  596. $sSelect = '<input id="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'" type="checkbox" value="'.$aNodes[$id]->GetKey().'" name="selectObject[]" '.$sChecked.'>&nbsp;';
  597. }
  598. else
  599. {
  600. $sSelect = '<input id="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'" type="radio" value="'.$aNodes[$id]->GetKey().'" name="selectObject" '.$sChecked.'>&nbsp;';
  601. }
  602. }
  603. $oP->add('<li>'.$sSelect.'<label for="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'">'.$aNodes[$id]->GetName().'</label>');
  604. $this->DumpNodes($oP, $id, $aTree, $aNodes, $currValue);
  605. $oP->add("</li>\n");
  606. }
  607. $oP->add("</ul>\n");
  608. }
  609. }
  610. }