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');\">&nbsp;&nbsp;";
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('&nbsp; <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)."&nbsp;".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] = '&nbsp;<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');\">&nbsp;&nbsp;");
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.'>&nbsp;';
699					}
700					else
701					{
702						$sSelect = '<input id="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'" type="radio" value="'.$aNodes[$id]->GetKey().'" name="selectObject" '.$sChecked.'>&nbsp;';
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