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 61require_once(APPROOT.'/application/webpage.class.inc.php'); 62require_once(APPROOT.'/application/displayblock.class.inc.php'); 63 64class UIExtKeyWidget 65{ 66 const ENUM_OUTPUT_FORMAT_CSV = 'csv'; 67 const ENUM_OUTPUT_FORMAT_JSON = 'json'; 68 69 protected $iId; 70 protected $sTargetClass; 71 protected $sAttCode; 72 protected $bSearchMode; 73 74 //public function __construct($sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '') 75 static public function DisplayFromAttCode($oPage, $sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName = '', $sFormPrefix = '', $aArgs, $bSearchMode = false) 76 { 77 $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); 78 $sTargetClass = $oAttDef->GetTargetClass(); 79 $iMaxComboLength = $oAttDef->GetMaximumComboLength(); 80 $bAllowTargetCreation = $oAttDef->AllowTargetCreation(); 81 if (!$bSearchMode) 82 { 83 $sDisplayStyle = $oAttDef->GetDisplayStyle(); 84 } 85 else 86 { 87 $sDisplayStyle = 'select'; // In search mode, always use a drop-down list 88 } 89 $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode); 90 return $oWidget->Display($oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix, $aArgs, null, $sDisplayStyle); 91 } 92 93 public function __construct($sTargetClass, $iInputId, $sAttCode = '', $bSearchMode = false) 94 { 95 $this->sTargetClass = $sTargetClass; 96 $this->iId = $iInputId; 97 $this->sAttCode = $sAttCode; 98 $this->bSearchMode = $bSearchMode; 99 } 100 101 /** 102 * Get the HTML fragment corresponding to the ext key editing widget 103 * @param WebPage $oP The web page used for all the output 104 * @param array $aArgs Extra context arguments 105 * @return string The HTML fragment to be inserted into the page 106 */ 107 public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, DBObjectset $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select', $bSearchMultiple = true) 108 { 109 if (!is_null($bSearchMode)) 110 { 111 $this->bSearchMode = $bSearchMode; 112 } 113 $sTitle = addslashes($sTitle); 114 $oPage->add_linked_script('../js/extkeywidget.js'); 115 $oPage->add_linked_script('../js/forms-json-utils.js'); 116 117 $bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation); 118 $bExtensions = true; 119 $sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm'); 120 $sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_'; 121 122 $sHTMLValue = "<div class=\"field_input_zone field_input_extkey\">"; 123 $sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL()); 124 if($this->bSearchMode) 125 { 126 $sWizHelper = 'null'; 127 $sWizHelperJSON = "''"; 128 $sJSSearchMode = 'true'; 129 } 130 else 131 { 132 if (isset($aArgs['wizHelper'])) 133 { 134 $sWizHelper = $aArgs['wizHelper']; 135 } 136 else 137 { 138 $sWizHelper = 'oWizardHelper'.$sFormPrefix; 139 } 140 $sWizHelperJSON = $sWizHelper.'.UpdateWizardToJSON()'; 141 $sJSSearchMode = 'false'; 142 } 143 if (is_null($oAllowedValues)) 144 { 145 throw new Exception('Implementation: null value for allowed values definition'); 146 } 147 $oAllowedValues->SetShowObsoleteData(utils::ShowObsoleteData()); 148 // Don't automatically launch the search if the table is huge 149 $bDoSearch = !utils::IsHighCardinality($this->sTargetClass); 150 $sJSDoSearch = $bDoSearch ? 'true' : 'false'; 151 152 // We just need to compare the number of entries with MaxComboLength, so no need to get the real count. 153 if (!$oAllowedValues->CountExceeds($iMaxComboLength)) 154 { 155 // Discrete list of values, use a SELECT or RADIO buttons depending on the config 156 switch($sDisplayStyle) 157 { 158 case 'radio': 159 case 'radio_horizontal': 160 case 'radio_vertical': 161 $sValidationField = null; 162 163 $bVertical = ($sDisplayStyle != 'radio_horizontal'); 164 $bExtensions = false; 165 $oAllowedValues->Rewind(); 166 $aAllowedValues = array(); 167 while($oObj = $oAllowedValues->Fetch()) 168 { 169 $aAllowedValues[$oObj->GetKey()] = $oObj->GetName(); 170 } 171 $sHTMLValue .= $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", false /* $bMandatory will be placed manually */, $bVertical, $sValidationField); 172 $aEventsList[] ='change'; 173 break; 174 175 case 'select': 176 case 'list': 177 default: 178 179 $sHelpText = ''; //$this->oAttDef->GetHelpOnEdition(); 180 $sHTMLValue .= "<div class=\"field_select_wrapper\">\n"; 181 182 if ($this->bSearchMode) 183 { 184 if ($bSearchMultiple) 185 { 186 $sHTMLValue .= "<select class=\"multiselect\" multiple title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}[]\" id=\"$this->iId\">\n"; 187 } 188 else 189 { 190 $sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n"; 191 $sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : Dict::S('UI:SearchValue:Any'); 192 $sHTMLValue .= "<option value=\"\">$sDisplayValue</option>\n"; 193 } 194 } 195 else 196 { 197 $sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n"; 198 $sHTMLValue .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>\n"; 199 } 200 201 $oAllowedValues->Rewind(); 202 while($oObj = $oAllowedValues->Fetch()) 203 { 204 $key = $oObj->GetKey(); 205 $display_value = $oObj->GetName(); 206 207 if (($oAllowedValues->Count() == 1) && ($bMandatory == 'true') ) 208 { 209 // When there is only once choice, select it by default 210 $sSelected = 'selected'; 211 } 212 else 213 { 214 $sSelected = (is_array($value) && in_array($key, $value)) || ($value == $key) ? 'selected' : ''; 215 } 216 $sHTMLValue .= "<option value=\"$key\" $sSelected>$display_value</option>\n"; 217 } 218 $sHTMLValue .= "</select>\n"; 219 $sHTMLValue .= "</div>\n"; 220 221 if (($this->bSearchMode) && $bSearchMultiple) 222 { 223 $aOptions = array( 224 'header' => true, 225 'checkAllText' => Dict::S('UI:SearchValue:CheckAll'), 226 'uncheckAllText' => Dict::S('UI:SearchValue:UncheckAll'), 227 'noneSelectedText' => Dict::S('UI:SearchValue:Any'), 228 'selectedText' => Dict::S('UI:SearchValue:NbSelected'), 229 'selectedList' => 1, 230 ); 231 $sJSOptions = json_encode($aOptions); 232 $oPage->add_ready_script("$('.multiselect').multiselect($sJSOptions);"); 233 } 234 $oPage->add_ready_script( 235<<<EOF 236 oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch); 237 oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>"; 238 $('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } ); 239 $('#$this->iId').bind('change', function() { $(this).trigger('extkeychange') } ); 240 241EOF 242 ); 243 } // Switch 244 } 245 else 246 { 247 // Too many choices, use an autocomplete 248 // Check that the given value is allowed 249 $oSearch = $oAllowedValues->GetFilter(); 250 $oSearch->AddCondition('id', $value); 251 $oSet = new DBObjectSet($oSearch); 252 if ($oSet->Count() == 0) 253 { 254 $value = null; 255 } 256 257 if (is_null($value) || ($value == 0)) // Null values are displayed as '' 258 { 259 $sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : ''; 260 } 261 else 262 { 263 $sDisplayValue = $this->GetObjectName($value); 264 } 265 $iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 3; //@@@ $this->oAttDef->GetMinAutoCompleteChars(); 266 267 // the input for the auto-complete 268 $sHTMLValue .= "<input class=\"field_autocomplete\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>"; 269 $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?t=".utils::GetCacheBusterTimestamp()."\" onClick=\"oACWidget_{$this->iId}.Search();\"/></span>"; 270 271 // another hidden input to store & pass the object's Id 272 $sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n"; 273 274 $JSSearchMode = $this->bSearchMode ? 'true' : 'false'; 275 // Scripts to start the autocomplete and bind some events to it 276 $oPage->add_ready_script( 277<<<EOF 278 oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch); 279 oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>"; 280 $('#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; } }}); 281 $('#label_$this->iId').keyup(function() { if ($(this).val() == '') { $('#$this->iId').val(''); } } ); // Useful for search forms: empty value in the "label", means no value, immediatly ! 282 $('#label_$this->iId').result( function(event, data, formatted) { OnAutoComplete('{$this->iId}', event, data, formatted); } ); 283 $('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } ); 284 if ($('#ac_dlg_{$this->iId}').length == 0) 285 { 286 $('body').append('<div id="ac_dlg_{$this->iId}"></div>'); 287 } 288EOF 289); 290 } 291 if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false) 292 { 293 $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?t=".utils::GetCacheBusterTimestamp()."\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"/></span>"; 294 $oPage->add_ready_script( 295<<<EOF 296 if ($('#ac_tree_{$this->iId}').length == 0) 297 { 298 $('body').append('<div id="ac_tree_{$this->iId}"></div>'); 299 } 300EOF 301); 302 } 303 if ($bCreate && $bExtensions) 304 { 305 $sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject'; 306 307 $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?t=".utils::GetCacheBusterTimestamp()."\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"/></span>"; 308 $oPage->add_ready_script( 309<<<EOF 310 if ($('#ajax_{$this->iId}').length == 0) 311 { 312 $('body').append('<div id="ajax_{$this->iId}"></div>'); 313 } 314EOF 315); 316 } 317 $sHTMLValue .= "</div>"; 318 319 // Note: This test is no longer necessary as we changed the markup to extract validation decoration in the standard .field_input_xxx container 320 //if (($sDisplayStyle == 'select') || ($sDisplayStyle == 'list')) 321 //{ 322 $sHTMLValue .= "<span class=\"form_validation\" id=\"v_{$this->iId}\"></span><span class=\"field_status\" id=\"fstatus_{$this->iId}\"></span>"; 323 //} 324 325 return $sHTMLValue; 326 } 327 328 public function GetSearchDialog(WebPage $oPage, $sTitle, $oCurrObject = null) 329 { 330 $sHTML = '<div class="wizContainer" style="vertical-align:top;"><div id="dc_'.$this->iId.'">'; 331 332 if ( ($oCurrObject != null) && ($this->sAttCode != '')) 333 { 334 $oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode); 335 /** @var \DBObject $oCurrObject */ 336 $aArgs = $oCurrObject->ToArgsForQuery(); 337 $aParams = array('query_params' => $aArgs); 338 $oSet = $oAttDef->GetAllowedValuesAsObjectSet($aArgs); 339 $oFilter = $oSet->GetFilter(); 340 } 341 else 342 { 343 $aParams = array(); 344 $oFilter = new DBObjectSearch($this->sTargetClass); 345 } 346 $oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode); 347 $oBlock = new DisplayBlock($oFilter, 'search', false, $aParams); 348 $sHTML .= $oBlock->GetDisplay($oPage, $this->iId, 349 array( 350 'menu' => false, 351 'currentId' => $this->iId, 352 'table_id' => "dr_{$this->iId}", 353 'table_inner_id' => "{$this->iId}_results", 354 'selection_mode' => true, 355 'selection_type' => 'single', 356 'cssCount' => '#count_'.$this->iId) 357 ); 358 $sHTML .= "<form id=\"fr_{$this->iId}\" OnSubmit=\"return oACWidget_{$this->iId}.DoOk();\">\n"; 359 $sHTML .= "<div id=\"dr_{$this->iId}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n"; 360 $sHTML .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n"; 361 $sHTML .= "</div>\n"; 362 $sHTML .= "<input type=\"button\" id=\"btn_cancel_{$this->iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#ac_dlg_{$this->iId}').dialog('close');\"> "; 363 $sHTML .= "<input type=\"button\" id=\"btn_ok_{$this->iId}\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"oACWidget_{$this->iId}.DoOk();\">"; 364 $sHTML .= "<input type=\"hidden\" id=\"count_{$this->iId}\" value=\"0\">"; 365 $sHTML .= "</form>\n"; 366 $sHTML .= '</div></div>'; 367 368 $sDialogTitle = addslashes($sTitle); 369 $oPage->add_ready_script( 370<<<EOF 371 $('#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 }); 372 $('#fs_{$this->iId}').bind('submit.uiAutocomplete', oACWidget_{$this->iId}.DoSearchObjects); 373 $('#dc_{$this->iId}').resize(oACWidget_{$this->iId}.UpdateSizes); 374EOF 375); 376 $oPage->add($sHTML); 377 } 378 379 /** 380 * Search for objects to be selected 381 * 382 * @param WebPage $oP The page used for the output (usually an AjaxWebPage) 383 * @param $sFilter 384 * @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of m_sRemoteClass 385 * @param null $oObj 386 * 387 * @throws \OQLException 388 */ 389 public function SearchObjectsToSelect(WebPage $oP, $sFilter, $sRemoteClass = '', $oObj = null) 390 { 391 if (is_null($sFilter)) 392 { 393 throw new Exception('Implementation: null value for allowed values definition'); 394 } 395 396 $oFilter = DBObjectSearch::FromOQL($sFilter); 397 if (strlen($sRemoteClass) > 0) 398 { 399 $oFilter->ChangeClass($sRemoteClass); 400 } 401 $oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode); 402 403 // Current extkey value, so we can display event if it is not available anymore (eg. archived). 404 $iCurrentExtKeyId = (is_null($oObj)) ? 0 : $oObj->Get($this->sAttCode); 405 406 $oBlock = new DisplayBlock($oFilter, 'list_search', false, array('query_params' => array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId))); 407 $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 408 } 409 410 /** 411 * Search for objects to be selected 412 * 413 * @param WebPage $oP The page used for the output (usually an AjaxWebPage) 414 * @param string $sFilter The OQL expression used to define/limit limit the scope of possible values 415 * @param DBObject $oObj The current object for the OQL context 416 * @param string $sContains The text of the autocomplete to filter the results 417 * @param string $sOutputFormat 418 * @param null $sOperation for the values @see ValueSetObjects->LoadValues() 419 * 420 * @throws CoreException 421 * @throws OQLException 422 */ 423 public function AutoComplete(WebPage $oP, $sFilter, $oObj = null, $sContains, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV, $sOperation = null) 424 { 425 if (is_null($sFilter)) 426 { 427 throw new Exception('Implementation: null value for allowed values definition'); 428 } 429 430 // Current extkey value, so we can display event if it is not available anymore (eg. archived). 431 $iCurrentExtKeyId = (is_null($oObj) || $this->sAttCode === '') ? 0 : $oObj->Get($this->sAttCode); 432 433 $oValuesSet = new ValueSetObjects($sFilter, 'friendlyname'); // Bypass GetName() to avoid the encoding by htmlentities 434 $iMax = 150; 435 $oValuesSet->SetLimit($iMax); 436 $oValuesSet->SetSort(false); 437 $oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode); 438 $oValuesSet->SetLimit($iMax); 439 $aValuesContains = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'contains'); 440 asort($aValuesContains); 441 $aValues = array(); 442 foreach($aValuesContains as $sKey => $sFriendlyName) 443 { 444 if (!isset($aValues[$sKey])) 445 { 446 $aValues[$sKey] = $sFriendlyName; 447 } 448 } 449 450 switch($sOutputFormat) 451 { 452 case static::ENUM_OUTPUT_FORMAT_JSON: 453 454 $aJsonMap = array(); 455 foreach ($aValues as $sKey => $sLabel) 456 { 457 $aJsonMap[] = array('value' => $sKey, 'label' => $sLabel); 458 } 459 460 $oP->SetContentType('application/json'); 461 $oP->add(json_encode($aJsonMap)); 462 break; 463 464 case static::ENUM_OUTPUT_FORMAT_CSV: 465 foreach($aValues as $sKey => $sFriendlyName) 466 { 467 $oP->add(trim($sFriendlyName)."\t".$sKey."\n"); 468 } 469 break; 470 default: 471 throw new Exception('Invalid output format, "'.$sOutputFormat.'" given.'); 472 break; 473 } 474 } 475 476 /** 477 * Get the display name of the selected object, to fill back the autocomplete 478 */ 479 public function GetObjectName($iObjId) 480 { 481 $aModifierProps = array(); 482 $aModifierProps['UserRightsGetSelectFilter']['bSearchMode'] = $this->bSearchMode; 483 484 $oObj = MetaModel::GetObject($this->sTargetClass, $iObjId, false, false, $aModifierProps); 485 if ($oObj) 486 { 487 return $oObj->GetName(); 488 } 489 else 490 { 491 return ''; 492 } 493 } 494 495 /** 496 * Get the form to select a leaf class from the $this->sTargetClass (that should be abstract) 497 * Note: Inspired from UILinksWidgetDirect::GetObjectCreationDialog() 498 * 499 * @param WebPage $oPage 500 * 501 * @throws \CoreException 502 * @throws \DictExceptionMissingString 503 */ 504 public function GetClassSelectionForm(WebPage $oPage) 505 { 506 // For security reasons: check that the "proposed" class is actually a subclass of the linked class 507 // and that the current user is allowed to create objects of this class 508 $aSubClasses = MetaModel::EnumChildClasses($this->sTargetClass); 509 $aPossibleClasses = array(); 510 foreach($aSubClasses as $sCandidateClass) 511 { 512 if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) 513 { 514 $aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass); 515 } 516 } 517 518 $sDialogTitle = ''; 519 $oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">'); 520 $oPage->add('<form>'); 521 522 $sClassLabel = MetaModel::GetName($this->sTargetClass); 523 $oPage->add('<p>'.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel)); 524 $oPage->add('<nobr><select name="class">'); 525 asort($aPossibleClasses); 526 foreach($aPossibleClasses as $sClassName => $sClassLabel) 527 { 528 $oPage->add("<option value=\"$sClassName\">$sClassLabel</option>"); 529 } 530 $oPage->add('</select>'); 531 $oPage->add(' <button type="submit" class="action" style="margin-top:15px;"><span>' . Dict::S('UI:Button:Ok') . '</span></button></nobr></p>'); 532 533 $oPage->add('</form>'); 534 $oPage->add('</div></div></div>'); 535 $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n"); 536 $oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');"); 537 $oPage->add_ready_script("$('#dcr_{$this->iId} form').bind('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);"); 538 } 539 540 /** 541 * Get the form to create a new object of the 'target' class 542 */ 543 public function GetObjectCreationForm(WebPage $oPage, $oCurrObject, $aPrefillFormParam) 544 { 545 // Set all the default values in an object and clone this "default" object 546 $oNewObj = MetaModel::NewObject($this->sTargetClass); 547 548 // 1st - set context values 549 $oAppContext = new ApplicationContext(); 550 $oAppContext->InitObjectFromContext($oNewObj); 551 $oNewObj->PrefillForm('creation_from_extkey', $aPrefillFormParam); 552 // 2nd set the default values from the constraint on the external key... if any 553 if ( ($oCurrObject != null) && ($this->sAttCode != '')) 554 { 555 $oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode); 556 $aParams = array('this' => $oCurrObject); 557 $oSet = $oAttDef->GetAllowedValuesAsObjectSet($aParams); 558 $aConsts = $oSet->ListConstantFields(); 559 $sClassAlias = $oSet->GetFilter()->GetClassAlias(); 560 if (isset($aConsts[$sClassAlias])) 561 { 562 foreach($aConsts[$sClassAlias] as $sAttCode => $value) 563 { 564 $oNewObj->Set($sAttCode, $value); 565 } 566 } 567 } 568 569 // 3rd - set values from the page argument 'default' 570 $oNewObj->UpdateObjectFromArg('default'); 571 572 $sDialogTitle = ''; 573 $oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">'); 574 $oPage->add("<h1>".MetaModel::GetClassIcon($this->sTargetClass)." ".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sTargetClass))."</h1>\n"); 575 $aFieldsFlags = array(); 576 $aFieldsComments = array(); 577 foreach(MetaModel::ListAttributeDefs($this->sTargetClass) as $sAttCode => $oAttDef) 578 { 579 if (($oAttDef instanceof AttributeBlob) || (false)) 580 { 581 $aFieldsFlags[$sAttCode] = OPT_ATT_READONLY; 582 $aFieldsComments[$sAttCode] = ' <img src="../images/transp-lock.png" style="vertical-align:middle" title="'.htmlentities(Dict::S('UI:UploadNotSupportedInThisMode')).'"/>'; 583 } 584 } 585 cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true, 'fieldsFlags' => $aFieldsFlags, 'fieldsComments' => $aFieldsComments)); 586 $oPage->add('</div></div></div>'); 587// $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: $(window).width()*0.8, height: 'auto', autoOpen: false, modal: true, title: '$sDialogTitle'});\n"); 588 $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n"); 589 $oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');"); 590 $oPage->add_ready_script("$('#dcr_{$this->iId} form').bind('submit.uilinksWizard', oACWidget_{$this->iId}.DoCreateObject);"); 591 } 592 593 /** 594 * Display the hierarchy of the 'target' class 595 */ 596 public function DisplayHierarchy(WebPage $oPage, $sFilter, $currValue, $oObj) 597 { 598 $sDialogTitle = addslashes(Dict::Format('UI:HierarchyOf_Class', MetaModel::GetName($this->sTargetClass))); 599 $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.'">'); 600 $oPage->add('<table style="width:100%"><tr><td>'); 601 if (is_null($sFilter)) 602 { 603 throw new Exception('Implementation: null value for allowed values definition'); 604 } 605 606 $oFilter = DBObjectSearch::FromOQL($sFilter); 607 $oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode); 608 $oSet = new DBObjectSet($oFilter, array(), array('this' => $oObj, 'current_extkey_id' => $currValue)); 609 610 $oSet->SetShowObsoleteData(utils::ShowObsoleteData()); 611 612 $sHKAttCode = MetaModel::IsHierarchicalClass($this->sTargetClass); 613 $this->DumpTree($oPage, $oSet, $sHKAttCode, $currValue); 614 615 $oPage->add('</td></tr></table>'); 616 $oPage->add('</div>'); 617 $oPage->add("<input type=\"button\" id=\"btn_cancel_{$this->iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_tree_{$this->iId}').dialog('close');\"> "); 618 $oPage->add("<input type=\"button\" id=\"btn_ok_{$this->iId}\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"oACWidget_{$this->iId}.DoHKOk();\">"); 619 620 $oPage->add('</div></div>'); 621 $oPage->add_ready_script("\$('#tree_$this->iId ul').treeview();\n"); 622 $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"); 623 } 624 625 /** 626 * Get the form to create a new object of the 'target' class 627 */ 628 public function DoCreateObject($oPage) 629 { 630 try 631 { 632 $oObj = MetaModel::NewObject($this->sTargetClass); 633 $aErrors = $oObj->UpdateObjectFromPostedForm($this->iId); 634 if (count($aErrors) == 0) 635 { 636 $oObj->DBInsert(); 637 return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey()); 638 } 639 else 640 { 641 return array('error' => implode(' ', $aErrors), 'id' => 0); 642 } 643 } 644 catch(Exception $e) 645 { 646 return array('error' => $e->getMessage(), 'id' => 0); 647 } 648 } 649 650 function DumpTree($oP, $oSet, $sParentAttCode, $currValue) 651 { 652 $aTree = array(); 653 $aNodes = array(); 654 while($oObj = $oSet->Fetch()) 655 { 656 $iParentId = $oObj->Get($sParentAttCode); 657 if (!isset($aTree[$iParentId])) 658 { 659 $aTree[$iParentId] = array(); 660 } 661 $aTree[$iParentId][$oObj->GetKey()] = $oObj->GetName(); 662 $aNodes[$oObj->GetKey()] = $oObj; 663 } 664 665 $aParents = array_keys($aTree); 666 $aRoots = array(); 667 foreach($aParents as $id) 668 { 669 if (!array_key_exists($id, $aNodes)) 670 { 671 $aRoots[] = $id; 672 } 673 } 674 foreach($aRoots as $iRootId) 675 { 676 $this->DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue); 677 } 678 } 679 680 function DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue) 681 { 682 $bSelect = true; 683 $bMultiple = false; 684 $sSelect = ''; 685 if (array_key_exists($iRootId, $aTree)) 686 { 687 $aSortedRoots = $aTree[$iRootId]; 688 asort($aSortedRoots); 689 $oP->add("<ul>\n"); 690 $fUniqueId = microtime(true); 691 foreach($aSortedRoots as $id => $sName) 692 { 693 if ($bSelect) 694 { 695 $sChecked = ($aNodes[$id]->GetKey() == $currValue) ? 'checked' : ''; 696 if ($bMultiple) 697 { 698 $sSelect = '<input id="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'" type="checkbox" value="'.$aNodes[$id]->GetKey().'" name="selectObject[]" '.$sChecked.'> '; 699 } 700 else 701 { 702 $sSelect = '<input id="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'" type="radio" value="'.$aNodes[$id]->GetKey().'" name="selectObject" '.$sChecked.'> '; 703 } 704 } 705 $oP->add('<li>'.$sSelect.'<label for="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'">'.$aNodes[$id]->GetName().'</label>'); 706 $this->DumpNodes($oP, $id, $aTree, $aNodes, $currValue); 707 $oP->add("</li>\n"); 708 } 709 $oP->add("</ul>\n"); 710 } 711 } 712 713} 714