mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 15:34:12 +01:00
# Conflicts: # composer.lock # datamodels/2.x/itop-oauth-client/datamodel.itop-oauth-client.xml # lib/composer/autoload_files.php # lib/composer/autoload_real.php # lib/composer/autoload_static.php # lib/composer/installed.json # lib/composer/installed.php
1156 lines
45 KiB
PHP
1156 lines
45 KiB
PHP
<?php
|
|
/*
|
|
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
|
* @license http://opensource.org/licenses/AGPL-3.0
|
|
*/
|
|
|
|
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
|
|
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
|
|
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
|
|
|
require_once(APPROOT.'/application/displayblock.class.inc.php');
|
|
|
|
/**
|
|
* Class UIExtKeyWidget
|
|
* UI widget for displaying and editing external keys when
|
|
* A simple drop-down list is not enough...
|
|
*
|
|
* The layout is the following
|
|
*
|
|
* +-- #label_<id> (input)-------+ +-----------+
|
|
* | | | Browse... |
|
|
* +-----------------------------+ +-----------+
|
|
*
|
|
* And the popup dialog has the following layout:
|
|
*
|
|
* +------------------- ac_dlg_<id> (div)-----------+
|
|
* + +--- ds_<id> (div)---------------------------+ |
|
|
* | | +------------- fs_<id> (form)------------+ | |
|
|
* | | | +--------+---+ | | |
|
|
* | | | | Class | V | | | |
|
|
* | | | +--------+---+ | | |
|
|
* | | | | | |
|
|
* | | | S e a r c h F o r m | | |
|
|
* | | | +--------+ | | |
|
|
* | | | | Search | | | |
|
|
* | | | +--------+ | | |
|
|
* | | +----------------------------------------+ | |
|
|
* | +--------------+-dh_<id>-+--------------------+ |
|
|
* | \ Search / |
|
|
* | +------+ |
|
|
* | +--- fr_<id> (form)--------------------------+ |
|
|
* | | +------------ dr_<id> (div)--------------+ | |
|
|
* | | | | | |
|
|
* | | | S e a r c h R e s u l t s | | |
|
|
* | | | | | |
|
|
* | | +----------------------------------------+ | |
|
|
* | | +--------+ +-----+ | |
|
|
* | | | Cancel | | Add | | |
|
|
* | | +--------+ +-----+ | |
|
|
* | +--------------------------------------------+ |
|
|
* +------------------------------------------------+
|
|
*/
|
|
class UIExtKeyWidget
|
|
{
|
|
const ENUM_OUTPUT_FORMAT_CSV = 'csv';
|
|
const ENUM_OUTPUT_FORMAT_JSON = 'json';
|
|
|
|
protected $iId;
|
|
protected $sTargetClass;
|
|
protected $sFilter;
|
|
protected $sAttCode;
|
|
protected $bSearchMode;
|
|
|
|
//public function __construct($sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '')
|
|
|
|
/**
|
|
* @param \WebPage $oPage
|
|
* @param string $sAttCode
|
|
* @param string $sClass
|
|
* @param string $sTitle
|
|
* @param object $oAllowedValues
|
|
* @param mixed $value
|
|
* @param int $iInputId
|
|
* @param boolean $bMandatory
|
|
* @param string $sFieldName
|
|
* @param string $sFormPrefix
|
|
* @param array $aArgs
|
|
* @param boolean $bSearchMode
|
|
* @param string $sInputType type of field rendering, contains one of the \cmdbAbstractObject::ENUM_INPUT_TYPE_* constants
|
|
*
|
|
* @return string
|
|
* @throws \Exception
|
|
*
|
|
* @since 3.0.0 N°3750 new $sInputType parameter
|
|
* @since 2.7.7 3.0.1 3.1.0 N°3129 Add default value for $aArgs for PHP 8.0 compat
|
|
*/
|
|
public static function DisplayFromAttCode(
|
|
$oPage, $sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName = '', $sFormPrefix = '',
|
|
$aArgs = [], $bSearchMode = false, &$sInputType = ''
|
|
)
|
|
{
|
|
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
|
$sTargetClass = $oAttDef->GetTargetClass();
|
|
$iMaxComboLength = $oAttDef->GetMaximumComboLength();
|
|
$bAllowTargetCreation = $oAttDef->AllowTargetCreation();
|
|
if (!$bSearchMode) {
|
|
$sDisplayStyle = $oAttDef->GetDisplayStyle();
|
|
} else {
|
|
$sDisplayStyle = 'select'; // In search mode, always use a drop-down list
|
|
}
|
|
$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode);
|
|
if (!$bSearchMode) {
|
|
switch ($sDisplayStyle)
|
|
{
|
|
case 'radio':
|
|
case 'radio_horizontal':
|
|
case 'radio_vertical':
|
|
$sInputType = CmdbAbstractObject::ENUM_INPUT_TYPE_RADIO;
|
|
|
|
return $oWidget->DisplayRadio($oPage, $iMaxComboLength, $bAllowTargetCreation, $oAllowedValues, $value, $sFieldName, $sDisplayStyle);
|
|
|
|
case 'select':
|
|
case 'list':
|
|
default:
|
|
return $oWidget->DisplaySelect($oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value,
|
|
$bMandatory, $sFieldName, $sFormPrefix, $aArgs, $sInputType);
|
|
}
|
|
} else {
|
|
return $oWidget->Display($oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId,
|
|
$bMandatory, $sFieldName, $sFormPrefix, $aArgs, null, $sDisplayStyle, true, $sInputType);
|
|
}
|
|
}
|
|
|
|
public function __construct($sTargetClass, $iInputId, $sAttCode = '', $bSearchMode = false, $sFilter = null)
|
|
{
|
|
$this->sTargetClass = $sTargetClass;
|
|
$this->sFilter = $sFilter;
|
|
$this->iId = $iInputId;
|
|
$this->sAttCode = $sAttCode;
|
|
$this->bSearchMode = $bSearchMode;
|
|
}
|
|
|
|
/**
|
|
* @param \WebPage $oPage
|
|
* @param int $iMaxComboLength
|
|
* @param bool $bAllowTargetCreation
|
|
* @param string $sTitle
|
|
* @param \DBObjectset $oAllowedValues
|
|
* @param mixed $value
|
|
* @param bool $bMandatory
|
|
* @param string $sFieldName
|
|
* @param string $sFormPrefix
|
|
* @param array $aArgs Extra context arguments
|
|
* @param string $sInputType type of field rendering, contains one of the \cmdbAbstractObject::ENUM_INPUT_TYPE_* constants
|
|
*
|
|
* @return string the HTML fragment corresponding to the ext key editing widget
|
|
* @throws \ArchivedObjectException
|
|
* @throws \ConfigException
|
|
* @throws \CoreException
|
|
* @throws \CoreUnexpectedValue
|
|
* @throws \DictExceptionMissingString
|
|
* @throws \MissingQueryArgument
|
|
* @throws \MySQLException
|
|
* @throws \MySQLHasGoneAwayException
|
|
*
|
|
* @since 3.0.0 N°2508 - Include Obsolescence icon within list and autocomplete
|
|
* @since 3.0.0 N°3750 new $sInputType parameter
|
|
*/
|
|
public function DisplaySelect(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, DBObjectset $oAllowedValues, $value, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), &$sInputType = '')
|
|
{
|
|
$sTitle = addslashes($sTitle);
|
|
$oPage->add_linked_script('../js/extkeywidget.js');
|
|
$oPage->add_linked_script('../js/forms-json-utils.js');
|
|
|
|
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
|
|
$bExtensions = true;
|
|
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
|
|
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
|
|
|
|
|
|
|
|
$sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL());
|
|
if ($this->bSearchMode) {
|
|
$sWizHelper = 'null';
|
|
$sWizHelperJSON = "''";
|
|
$sJSSearchMode = 'true';
|
|
} else {
|
|
if (isset($aArgs['wizHelper'])) {
|
|
$sWizHelper = $aArgs['wizHelper'];
|
|
} else {
|
|
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
|
|
}
|
|
$sWizHelperJSON = $sWizHelper.'.UpdateWizardToJSON()';
|
|
$sJSSearchMode = 'false';
|
|
}
|
|
if (is_null($oAllowedValues)) {
|
|
throw new Exception('Implementation: null value for allowed values definition');
|
|
}
|
|
$oAllowedValues->SetShowObsoleteData(utils::ShowObsoleteData());
|
|
// Don't automatically launch the search if the table is huge
|
|
$bDoSearch = !utils::IsHighCardinality($this->sTargetClass);
|
|
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
|
|
|
|
$bIsAutocomplete = $oAllowedValues->CountExceeds($iMaxComboLength);
|
|
$sWrapperCssClass = $bIsAutocomplete ? 'ibo-input-select-autocomplete-wrapper' : 'ibo-input-select-wrapper';
|
|
$sHTMLValue = "<div class=\"field_input_zone field_input_extkey ibo-input-wrapper ibo-input-select-wrapper--with-buttons $sWrapperCssClass\" data-attcode=\"".$this->sAttCode."\" data-validation=\"untouched\" data-accessibility-selectize-label=\"$sTitle\">";
|
|
|
|
// We just need to compare the number of entries with MaxComboLength, so no need to get the real count.
|
|
if (!$bIsAutocomplete) {
|
|
// Discrete list of values, use a SELECT or RADIO buttons depending on the config
|
|
$sHelpText = ''; //$this->oAttDef->GetHelpOnEdition();
|
|
//$sHTMLValue .= "<div class=\"field_select_wrapper\">\n";
|
|
$aOptions = [];
|
|
|
|
$aOption = [];
|
|
$aOption['value'] = "";
|
|
$aOption['label'] = Dict::S('UI:SelectOne');
|
|
array_push($aOptions, $aOption);
|
|
|
|
$oAllowedValues->Rewind();
|
|
$sClassAllowed = $oAllowedValues->GetClass();
|
|
$bAddingValue = false;
|
|
|
|
$aComplementAttributeSpec = MetaModel::GetNameSpec($oAllowedValues->GetClass(), FriendlyNameType::COMPLEMENTARY);
|
|
$sFormatAdditionalField = $aComplementAttributeSpec[0];
|
|
$aAdditionalField = $aComplementAttributeSpec[1];
|
|
|
|
if (count($aAdditionalField) > 0) {
|
|
$bAddingValue = true;
|
|
}
|
|
$sObjectImageAttCode = MetaModel::GetImageAttributeCode($sClassAllowed);
|
|
$bInitValue = false;
|
|
while ($oObj = $oAllowedValues->Fetch()) {
|
|
$aOption = [];
|
|
$aOption['value'] = $oObj->GetKey();
|
|
$aOption['label'] = $oObj->GetName();
|
|
$aOption['search_label'] = utils::HtmlEntityDecode($oObj->GetName());
|
|
|
|
if (($oAllowedValues->Count() == 1) && ($bMandatory == 'true')) {
|
|
// When there is only once choice, select it by default
|
|
if ($value != $oObj->GetKey()) {
|
|
$value = $oObj->GetKey();
|
|
$bInitValue = true;
|
|
}
|
|
}
|
|
if ($oObj->IsObsolete()) {
|
|
$aOption['obsolescence_flag'] = "1";
|
|
}
|
|
if ($bAddingValue) {
|
|
$aArguments = [];
|
|
foreach ($aAdditionalField as $sAdditionalField) {
|
|
array_push($aArguments, $oObj->Get($sAdditionalField));
|
|
}
|
|
$aOption['additional_field'] = utils::HtmlEntities(vsprintf($sFormatAdditionalField, $aArguments));
|
|
}
|
|
if (!empty($sObjectImageAttCode)) {
|
|
// Try to retrieve image for contact
|
|
/** @var \ormDocument $oImage */
|
|
$oImage = $oObj->Get($sObjectImageAttCode);
|
|
if (!$oImage->IsEmpty()) {
|
|
$aOption['picture_url'] = $oImage->GetDisplayURL($sClassAllowed, $oObj->GetKey(), $sObjectImageAttCode);
|
|
$aOption['initials'] = '';
|
|
} else {
|
|
$aOption['initials'] = utils::FormatInitialsForMedallion(utils::ToAcronym($oObj->Get('friendlyname')));
|
|
}
|
|
}
|
|
array_push($aOptions, $aOption);
|
|
}
|
|
$sInputType = CmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_DECORATED;
|
|
$sHTMLValue .= "<select class=\"ibo-input-select-placeholder\" title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\" tabindex=\"0\"></select>";
|
|
$sJsonOptions = str_replace("'", "\'", str_replace('\\', '\\\\', json_encode($aOptions)));
|
|
$oPage->add_ready_script(
|
|
<<<EOF
|
|
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
|
|
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
|
|
oACWidget_{$this->iId}.AddSelectize('$sJsonOptions','$value');
|
|
$('#$this->iId').on('update', function() { oACWidget_{$this->iId}.Update(); } );
|
|
$('#$this->iId').on('change', function() { $(this).trigger('extkeychange'); } );
|
|
EOF
|
|
);
|
|
if ($bInitValue) {
|
|
$oPage->add_ready_script("$('#$this->iId').one('validate', function() { $(this).trigger('change'); } );");
|
|
}
|
|
$sHTMLValue .= "<div class=\"ibo-input-select--action-buttons\">";
|
|
}
|
|
else
|
|
{
|
|
// Too many choices, use an autocomplete
|
|
// Check that the given value is allowed
|
|
$oSearch = $oAllowedValues->GetFilter();
|
|
$oSearch->AddCondition('id', $value);
|
|
$oSet = new DBObjectSet($oSearch);
|
|
if ($oSet->Count() == 0)
|
|
{
|
|
$value = null;
|
|
}
|
|
|
|
if (is_null($value) || ($value == 0)) // Null values are displayed as ''
|
|
{
|
|
$sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : '';
|
|
} else {
|
|
$sDisplayValue = $this->GetObjectName($value);
|
|
}
|
|
$iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 2; //@@@ $this->oAttDef->GetMinAutoCompleteChars();
|
|
|
|
// the input for the auto-complete
|
|
$sInputType = CmdbAbstractObject::ENUM_INPUT_TYPE_AUTOCOMPLETE;
|
|
$sHTMLValue .= "<input class=\"field_autocomplete ibo-input ibo-input-select ibo-input-select-autocomplete\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\" placeholder='...'/>";
|
|
|
|
// another hidden input to store & pass the object's Id
|
|
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n";
|
|
|
|
$JSSearchMode = $this->bSearchMode ? 'true' : 'false';
|
|
// Scripts to start the autocomplete and bind some events to it
|
|
$oPage->add_ready_script(
|
|
<<<EOF
|
|
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
|
|
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
|
|
oACWidget_{$this->iId}.AddAutocomplete($iMinChars, $sWizHelperJSON);
|
|
if ($('#ac_dlg_{$this->iId}').length == 0)
|
|
{
|
|
$('body').append('<div id="ac_dlg_{$this->iId}"></div>');
|
|
}
|
|
EOF
|
|
);
|
|
$sHTMLValue .= "<div class=\"ibo-input-select--action-buttons\">";
|
|
$sHTMLValue .= " <div class=\"ibo-input-select--action-button ibo-input-select--action-button--clear ibo-is-hidden\" id=\"mini_clear_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Clear();\" data-tooltip-content='".Dict::S('UI:Button:Clear')."'><i class=\"fas fa-times\"></i></div>";
|
|
}
|
|
if ($bCreate && $bExtensions) {
|
|
$sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject';
|
|
|
|
$sHTMLValue .= "<div class=\"ibo-input-select--action-button ibo-input-select--action-button--create\" id=\"mini_add_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\" data-tooltip-content='".Dict::S('UI:Button:Create')."'><i class=\"fas fa-plus\"></i></div>";
|
|
$oPage->add_ready_script(
|
|
<<<JS
|
|
if ($('#ajax_{$this->iId}').length == 0)
|
|
{
|
|
$('body').append('<div id="ajax_{$this->iId}"></div>');
|
|
}
|
|
JS
|
|
);
|
|
}
|
|
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false) {
|
|
$sHTMLValue .= "<div class=\"ibo-input-select--action-button ibo-input-select--action-button--hierarchy\" id=\"mini_tree_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\" data-tooltip-content='".Dict::S('UI:Button:SearchInHierarchy')."'><i class=\"fas fa-sitemap\"></i></div>";
|
|
$oPage->add_ready_script(
|
|
<<<JS
|
|
if ($('#ac_tree_{$this->iId}').length == 0)
|
|
{
|
|
$('body').append('<div id="ac_tree_{$this->iId}"></div>');
|
|
}
|
|
JS
|
|
);
|
|
}
|
|
if ($oAllowedValues->CountExceeds($iMaxComboLength)) {
|
|
$sHTMLValue .= " <div class=\"ibo-input-select--action-button ibo-input-select--action-button--search\" id=\"mini_search_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Search();\" data-tooltip-content='".Dict::S('UI:Button:Search')."'><i class=\"fas fa-search\"></i></div>";
|
|
}
|
|
$sHTMLValue .= "</div>";
|
|
$sHTMLValue .= "</div>";
|
|
$sHTMLValue .= "<span class=\"form_validation ibo-field-validation\" id=\"v_{$this->iId}\"></span><span class=\"field_status\" id=\"fstatus_{$this->iId}\"></span>";
|
|
|
|
return $sHTMLValue;
|
|
}
|
|
|
|
/**
|
|
* @since 3.0.0 N°2508 - Include Obsolescence icon within list and autocomplete
|
|
* Get the HTML fragment corresponding to the ext key editing widget
|
|
* @param WebPage $oP The web page used for all the output
|
|
* @param array $aArgs Extra context arguments
|
|
* @return string The HTML fragment to be inserted into the page
|
|
*/
|
|
public function DisplayRadio(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, DBObjectset $oAllowedValues, $value, $sFieldName, $sDisplayStyle)
|
|
{
|
|
$oPage->add_linked_script('../js/forms-json-utils.js');
|
|
|
|
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
|
|
$bExtensions = true;
|
|
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
|
|
|
|
$sHTMLValue = "<div class=\"field_input_zone field_input_extkey\">";
|
|
|
|
if (is_null($oAllowedValues))
|
|
{
|
|
throw new Exception('Implementation: null value for allowed values definition');
|
|
}
|
|
$oAllowedValues->SetShowObsoleteData(utils::ShowObsoleteData());
|
|
|
|
// We just need to compare the number of entries with MaxComboLength, so no need to get the real count.
|
|
if (!$oAllowedValues->CountExceeds($iMaxComboLength))
|
|
{
|
|
// Discrete list of values, use a SELECT or RADIO buttons depending on the config
|
|
$sValidationField = null;
|
|
|
|
$bVertical = ($sDisplayStyle != 'radio_horizontal');
|
|
$bExtensions = false;
|
|
$oAllowedValues->Rewind();
|
|
$aAllowedValues = array();
|
|
while($oObj = $oAllowedValues->Fetch())
|
|
{
|
|
$aAllowedValues[$oObj->GetKey()] = $oObj->GetName();
|
|
}
|
|
$sHTMLValue .= $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", false /* $bMandatory will be placed manually */, $bVertical, $sValidationField);
|
|
$aEventsList[] ='change';
|
|
}
|
|
else
|
|
{
|
|
$sHTMLValue .= "unable to display. Too much values";
|
|
}
|
|
$sHTMLValue .= '<div class="ibo-input-select--action-buttons">';
|
|
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false)
|
|
{
|
|
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_tree_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"><i class=\"fas fa-sitemap\"></i></div></span>";
|
|
$oPage->add_ready_script(
|
|
<<<JS
|
|
if ($('#ac_tree_{$this->iId}').length == 0)
|
|
{
|
|
$('body').append('<div id="ac_tree_{$this->iId}"></div>');
|
|
}
|
|
JS
|
|
);
|
|
}
|
|
if ($bCreate && $bExtensions)
|
|
{
|
|
$sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject';
|
|
|
|
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_add_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"><i class=\"fas fa-plus\"></i></div></span>";
|
|
$oPage->add_ready_script(
|
|
<<<JS
|
|
if ($('#ajax_{$this->iId}').length == 0)
|
|
{
|
|
$('body').append('<div id="ajax_{$this->iId}"></div>');
|
|
}
|
|
JS
|
|
);
|
|
}
|
|
$sHTMLValue .= "</div>";
|
|
$sHTMLValue .= "</div>";
|
|
|
|
// Note: This test is no longer necessary as we changed the markup to extract validation decoration in the standard .field_input_xxx container
|
|
//if (($sDisplayStyle == 'select') || ($sDisplayStyle == 'list'))
|
|
//{
|
|
$sHTMLValue .= "<span class=\"form_validation ibo-field-validation\" id=\"v_{$this->iId}\"></span><span class=\"field_status\" id=\"fstatus_{$this->iId}\"></span>";
|
|
//}
|
|
|
|
return $sHTMLValue;
|
|
}
|
|
|
|
/**
|
|
* Get the HTML fragment corresponding to the ext key editing widget
|
|
*
|
|
* @param \WebPage $oPage
|
|
* @param int $iMaxComboLength
|
|
* @param boolean $bAllowTargetCreation
|
|
* @param string $sTitle
|
|
* @param \DBObjectset $oAllowedValues
|
|
* @param mixed $value
|
|
* @param int $iInputId
|
|
* @param boolean $bMandatory
|
|
* @param strin $sFieldName
|
|
* @param string $sFormPrefix
|
|
* @param array $aArgs Extra context arguments
|
|
* @param null $bSearchMode
|
|
* @param string $sDisplayStyle
|
|
* @param boolean $bSearchMultiple
|
|
* @param string $sInputType type of field rendering, contains one of the \cmdbAbstractObject::ENUM_INPUT_TYPE_* constants
|
|
*
|
|
* @return string The HTML fragment to be inserted into the page
|
|
* @throws \ConfigException
|
|
* @throws \CoreException
|
|
* @throws \CoreUnexpectedValue
|
|
* @throws \MissingQueryArgument
|
|
* @throws \MySQLException
|
|
* @throws \MySQLHasGoneAwayException
|
|
*
|
|
* @since 3.0.0 N°3750 new $sInputType parameter
|
|
*/
|
|
public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, DBObjectset $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select', $bSearchMultiple = true, &$sInputType = '')
|
|
{
|
|
if (!is_null($bSearchMode)) {
|
|
$this->bSearchMode = $bSearchMode;
|
|
}
|
|
$sTitle = addslashes($sTitle);
|
|
$oPage->add_linked_script('../js/extkeywidget.js');
|
|
$oPage->add_linked_script('../js/forms-json-utils.js');
|
|
|
|
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
|
|
$bExtensions = true;
|
|
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
|
|
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
|
|
|
|
$sHTMLValue = "<div class=\"field_input_zone field_input_extkey\">";
|
|
$sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL());
|
|
if ($this->bSearchMode) {
|
|
$sWizHelper = 'null';
|
|
$sWizHelperJSON = "''";
|
|
$sJSSearchMode = 'true';
|
|
} else {
|
|
if (isset($aArgs['wizHelper'])) {
|
|
$sWizHelper = $aArgs['wizHelper'];
|
|
} else {
|
|
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
|
|
}
|
|
$sWizHelperJSON = $sWizHelper.'.UpdateWizardToJSON()';
|
|
$sJSSearchMode = 'false';
|
|
}
|
|
if (is_null($oAllowedValues)) {
|
|
throw new Exception('Implementation: null value for allowed values definition');
|
|
}
|
|
$oAllowedValues->SetShowObsoleteData(utils::ShowObsoleteData());
|
|
// Don't automatically launch the search if the table is huge
|
|
$bDoSearch = !utils::IsHighCardinality($this->sTargetClass);
|
|
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
|
|
|
|
// We just need to compare the number of entries with MaxComboLength, so no need to get the real count.
|
|
if (!$oAllowedValues->CountExceeds($iMaxComboLength)) {
|
|
// Discrete list of values, use a SELECT or RADIO buttons depending on the config
|
|
switch ($sDisplayStyle) {
|
|
case 'radio':
|
|
case 'radio_horizontal':
|
|
case 'radio_vertical':
|
|
$sInputType = CmdbAbstractObject::ENUM_INPUT_TYPE_RADIO;
|
|
$sValidationField = null;
|
|
|
|
$bVertical = ($sDisplayStyle != 'radio_horizontal');
|
|
$bExtensions = false;
|
|
$oAllowedValues->Rewind();
|
|
$aAllowedValues = array();
|
|
while ($oObj = $oAllowedValues->Fetch()) {
|
|
$aAllowedValues[$oObj->GetKey()] = $oObj->GetName();
|
|
}
|
|
$sHTMLValue .= $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", false /* $bMandatory will be placed manually */, $bVertical, $sValidationField);
|
|
$aEventsList[] = 'change';
|
|
break;
|
|
|
|
case 'select':
|
|
case 'list':
|
|
default:
|
|
$sHelpText = '';
|
|
$sHTMLValue .= "<div class=\"field_select_wrapper\">\n";
|
|
|
|
if ($this->bSearchMode) {
|
|
if ($bSearchMultiple) {
|
|
$sHTMLValue .= "<select class=\"multiselect\" multiple title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}[]\" id=\"$this->iId\">\n";
|
|
} else {
|
|
$sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
|
|
$sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : Dict::S('UI:SearchValue:Any');
|
|
$sHTMLValue .= "<option value=\"\">$sDisplayValue</option>\n";
|
|
}
|
|
} else {
|
|
$sHTMLValue .= "<select class=\"ibo-input-select-placeholder\" title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
|
|
$sHTMLValue .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>\n";
|
|
}
|
|
|
|
$oAllowedValues->Rewind();
|
|
while ($oObj = $oAllowedValues->Fetch()) {
|
|
$key = $oObj->GetKey();
|
|
$display_value = $oObj->GetName();
|
|
|
|
if (($oAllowedValues->Count() == 1) && ($bMandatory == 'true')) {
|
|
// When there is only once choice, select it by default
|
|
$sSelected = 'selected';
|
|
if ($value != $key) {
|
|
$oPage->add_ready_script(
|
|
<<<EOF
|
|
$('#$this->iId').attr('data-validate','dependencies');
|
|
EOF
|
|
);
|
|
}
|
|
} else {
|
|
$sSelected = (is_array($value) && in_array($key, $value)) || ($value == $key) ? 'selected' : '';
|
|
}
|
|
$sHTMLValue .= "<option value=\"$key\" $sSelected>$display_value</option>\n";
|
|
}
|
|
$sHTMLValue .= "</select>\n";
|
|
$sHTMLValue .= "</div>\n";
|
|
|
|
$sInputType = CmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_RAW;
|
|
if (($this->bSearchMode) && $bSearchMultiple) {
|
|
$sInputType = CmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_MULTIPLE_CHOICES;
|
|
$aOptions = array(
|
|
'header' => true,
|
|
'checkAllText' => Dict::S('UI:SearchValue:CheckAll'),
|
|
'uncheckAllText' => Dict::S('UI:SearchValue:UncheckAll'),
|
|
'noneSelectedText' => Dict::S('UI:SearchValue:Any'),
|
|
'selectedText' => Dict::S('UI:SearchValue:NbSelected'),
|
|
'selectedList' => 1,
|
|
);
|
|
$sJSOptions = json_encode($aOptions);
|
|
$oPage->add_ready_script("$('.multiselect').multiselect($sJSOptions);");
|
|
}
|
|
$oPage->add_ready_script(
|
|
<<<EOF
|
|
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
|
|
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
|
|
$('#$this->iId').on('update', function() { oACWidget_{$this->iId}.Update(); } );
|
|
$('#$this->iId').on('change', function() { $(this).trigger('extkeychange') } );
|
|
|
|
EOF
|
|
);
|
|
}
|
|
} else {
|
|
// Too many choices, use an autocomplete
|
|
$sInputType = CmdbAbstractObject::ENUM_INPUT_TYPE_AUTOCOMPLETE;
|
|
// Check that the given value is allowed
|
|
$oSearch = $oAllowedValues->GetFilter();
|
|
$oSearch->AddCondition('id', $value);
|
|
$oSet = new DBObjectSet($oSearch);
|
|
if ($oSet->Count() == 0) {
|
|
$value = null;
|
|
}
|
|
|
|
if (is_null($value) || ($value == 0)) // Null values are displayed as ''
|
|
{
|
|
$sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : '';
|
|
} else {
|
|
$sDisplayValue = $this->GetObjectName($value);
|
|
}
|
|
$iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 2; //@@@ $this->oAttDef->GetMinAutoCompleteChars();
|
|
|
|
// the input for the auto-complete
|
|
$sHTMLValue .= "<input class=\"field_autocomplete ibo-input-select\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
|
|
$sHTMLValue .= "<div class=\"ibo-input-select--action-buttons\"><span class=\"field_input_btn\"><div class=\"mini_button ibo-input-select--action-button\" id=\"mini_search_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Search();\"><i class=\"fas fa-search\"></i></div></span></div>";
|
|
|
|
// another hidden input to store & pass the object's Id
|
|
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n";
|
|
|
|
$JSSearchMode = $this->bSearchMode ? 'true' : 'false';
|
|
// Scripts to start the autocomplete and bind some events to it
|
|
$oPage->add_ready_script(
|
|
<<<EOF
|
|
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
|
|
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
|
|
oACWidget_{$this->iId}.AddAutocomplete($iMinChars, $sWizHelperJSON);
|
|
if ($('#ac_dlg_{$this->iId}').length == 0)
|
|
{
|
|
$('body').append('<div id="ac_dlg_{$this->iId}"></div>');
|
|
}
|
|
EOF
|
|
);
|
|
}
|
|
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false) {
|
|
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"ibo-input-select--action-button\" id=\"mini_tree_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"><i class=\"fas fa-sitemap\"></i></div></span>";
|
|
$oPage->add_ready_script(
|
|
<<<JS
|
|
if ($('#ac_tree_{$this->iId}').length == 0)
|
|
{
|
|
$('body').append('<div id="ac_tree_{$this->iId}"></div>');
|
|
}
|
|
JS
|
|
);
|
|
}
|
|
if ($bCreate && $bExtensions) {
|
|
$sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject';
|
|
|
|
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"ibo-input-select--action-button\" id=\"mini_add_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"><i class=\"fas fa-plus\"></i></div></span>";
|
|
$oPage->add_ready_script(
|
|
<<<JS
|
|
if ($('#ajax_{$this->iId}').length == 0)
|
|
{
|
|
$('body').append('<div id="ajax_{$this->iId}"></div>');
|
|
}
|
|
JS
|
|
);
|
|
}
|
|
$sHTMLValue .= "</div>";
|
|
$sHTMLValue .= "<span class=\"form_validation ibo-field-validation\" id=\"v_{$this->iId}\"></span><span class=\"field_status\" id=\"fstatus_{$this->iId}\"></span>";
|
|
|
|
return $sHTMLValue;
|
|
}
|
|
|
|
public function GetSearchDialog(WebPage $oPage, $sTitle, $oCurrObject = null)
|
|
{
|
|
$oPage->add('<div class="wizContainer" style="vertical-align:top;"><div id="dc_'.$this->iId.'">');
|
|
|
|
if (($oCurrObject != null) && ($this->sAttCode != '')) {
|
|
$oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode);
|
|
/** @var \DBObject $oCurrObject */
|
|
$aArgs = $oCurrObject->ToArgsForQuery();
|
|
$aParams = array('query_params' => $aArgs);
|
|
$oSet = $oAttDef->GetAllowedValuesAsObjectSet($aArgs);
|
|
$oFilter = $oSet->GetFilter();
|
|
} else if (!empty($this->sFilter)) {
|
|
$aParams = array();
|
|
$oFilter = DBObjectSearch::FromOQL($this->sFilter);
|
|
} else {
|
|
$aParams = array();
|
|
$oFilter = new DBObjectSearch($this->sTargetClass);
|
|
}
|
|
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
|
$oBlock = new DisplayBlock($oFilter, 'search', false, $aParams);
|
|
$oPage->AddUiBlock($oBlock->GetDisplay($oPage, $this->iId,
|
|
array(
|
|
'menu' => false,
|
|
'currentId' => $this->iId,
|
|
'table_id' => "dr_{$this->iId}",
|
|
'table_inner_id' => "{$this->iId}_results",
|
|
'selection_mode' => true,
|
|
'selection_type' => 'single',
|
|
'cssCount' => '#count_'.$this->iId.'_results',
|
|
)
|
|
));
|
|
$sCancel = Dict::S('UI:Button:Cancel');
|
|
$sOK = Dict::S('UI:Button:Ok');
|
|
$sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm');
|
|
$oPage->add(<<<HTML
|
|
<form id="fr_{$this->iId}" OnSubmit="return oACWidget_{$this->iId}.DoOk();">
|
|
<div id="dr_{$this->iId}">
|
|
<div><p>{$sEmptyList}</p></div>
|
|
</div>
|
|
<input type="hidden" id="count_{$this->iId}_results" value="0">
|
|
</form>
|
|
</div></div>
|
|
HTML
|
|
);
|
|
|
|
$sDialogTitleSanitized = addslashes(utils::HtmlToText($sTitle));
|
|
$oPage->add_ready_script(<<<JS
|
|
$('#ac_dlg_{$this->iId}').dialog({
|
|
width: $(window).width()*0.8,
|
|
height: $(window).height()*0.8,
|
|
autoOpen: false,
|
|
modal: true,
|
|
title: '$sDialogTitleSanitized',
|
|
resizeStop: oACWidget_{$this->iId}.UpdateSizes,
|
|
close: oACWidget_{$this->iId}.OnClose,
|
|
buttons: [
|
|
{ text: "$sCancel",
|
|
class: "ibo-is-alternative ibo-is-neutral",
|
|
click: function() {
|
|
$(this).dialog('close');
|
|
} },
|
|
{ text: "$sOK",
|
|
class: "ibo-is-regular ibo-is-primary",
|
|
click: function() {
|
|
oACWidget_{$this->iId}.DoOk();
|
|
} },
|
|
],
|
|
});
|
|
$('#fs_{$this->iId}').on('submit.uiAutocomplete', oACWidget_{$this->iId}.DoSearchObjects);
|
|
$('#dc_{$this->iId}').resize(oACWidget_{$this->iId}.UpdateSizes);
|
|
JS
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Search for objects to be selected
|
|
*
|
|
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
|
|
* @param $sFilter
|
|
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of m_sRemoteClass
|
|
* @param null $oObj
|
|
*
|
|
* @throws \OQLException
|
|
*/
|
|
public function SearchObjectsToSelect(WebPage $oP, $sFilter, $sRemoteClass = '', $oObj = null)
|
|
{
|
|
if (is_null($sFilter))
|
|
{
|
|
throw new Exception('Implementation: null value for allowed values definition');
|
|
}
|
|
|
|
$oFilter = DBObjectSearch::FromOQL($sFilter);
|
|
if (strlen($sRemoteClass) > 0)
|
|
{
|
|
$oFilter->ChangeClass($sRemoteClass);
|
|
}
|
|
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
|
|
|
// Current extkey value, so we can display event if it is not available anymore (eg. archived).
|
|
$iCurrentExtKeyId = (is_null($oObj)) ? 0 : $oObj->Get($this->sAttCode);
|
|
|
|
$oBlock = new DisplayBlock($oFilter, 'list_search', false, array('query_params' => array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId)));
|
|
$oBlock->Display($oP, $this->iId.'_results', array('this' => $oObj, 'cssCount'=> '#count_'.$this->iId.'_results', 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'table_id' => 'select_'.$this->sAttCode)); // Don't display the 'Actions' menu on the results
|
|
}
|
|
|
|
/**
|
|
* Search for objects to be selected
|
|
*
|
|
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
|
|
* @param string $sFilter The OQL expression used to define/limit limit the scope of possible values
|
|
* @param DBObject $oObj The current object for the OQL context
|
|
* @param string $sContains The text of the autocomplete to filter the results
|
|
* @param string $sOutputFormat
|
|
* @param null $sOperation for the values @see ValueSetObjects->LoadValues()
|
|
*
|
|
* @throws CoreException
|
|
* @throws OQLException
|
|
*
|
|
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $oObj for PHP 8.0 compatibility
|
|
*/
|
|
public function AutoComplete(
|
|
WebPage $oP, $sFilter, $oObj, $sContains, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV, $sOperation = null
|
|
)
|
|
{
|
|
if (is_null($sFilter)) {
|
|
throw new Exception('Implementation: null value for allowed values definition');
|
|
}
|
|
|
|
// Current extkey value, so we can display event if it is not available anymore (eg. archived).
|
|
$iCurrentExtKeyId = (is_null($oObj) || $this->sAttCode === '') ? 0 : $oObj->Get($this->sAttCode);
|
|
$oValuesSet = new ValueSetObjects($sFilter, 'friendlyname'); // Bypass GetName() to avoid the encoding by htmlentities
|
|
$iMax = MetaModel::GetConfig()->Get('max_autocomplete_results');
|
|
$oValuesSet->SetLimit($iMax);
|
|
$oValuesSet->SetSort(false);
|
|
$oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
|
$oValuesSet->SetLimit($iMax);
|
|
$aValuesContains = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'start_with');
|
|
asort($aValuesContains);
|
|
$aValues = $aValuesContains;
|
|
if (sizeof($aValues) < $iMax) {
|
|
$aValuesContains = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'contains');
|
|
asort($aValuesContains);
|
|
$iSize = sizeof($aValuesContains);
|
|
foreach ($aValuesContains as $sKey => $sFriendlyName)
|
|
{
|
|
if (!isset($aValues[$sKey]))
|
|
{
|
|
$aValues[$sKey] = $sFriendlyName;
|
|
if (++$iSize >= $iMax)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
elseif (!in_array($sContains, $aValues))
|
|
{
|
|
$aValuesEquals = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'equals');
|
|
$aValues = array_merge($aValuesEquals, $aValues);
|
|
}
|
|
|
|
switch($sOutputFormat)
|
|
{
|
|
case static::ENUM_OUTPUT_FORMAT_JSON:
|
|
|
|
$aJsonMap = array();
|
|
foreach ($aValues as $sKey => $aValue) {
|
|
$aElt = ['value' => $sKey, 'label' => utils::EscapeHtml($aValue['label']), 'obsolescence_flag' => $aValue['obsolescence_flag']];
|
|
if ($aValue['additional_field'] != '') {
|
|
$aElt['additional_field'] = utils::EscapeHtml($aValue['additional_field']);
|
|
}
|
|
|
|
if (array_key_exists('initials', $aValue)) {
|
|
$aElt['initials'] = utils::FormatInitialsForMedallion($aValue['initials']);
|
|
if (array_key_exists('picture_url', $aValue)) {
|
|
$aElt['picture_url'] = $aValue['picture_url'];
|
|
}
|
|
}
|
|
$aJsonMap[] = $aElt;
|
|
}
|
|
|
|
$oP->SetContentType('application/json');
|
|
$oP->add(json_encode($aJsonMap));
|
|
break;
|
|
|
|
case static::ENUM_OUTPUT_FORMAT_CSV:
|
|
foreach($aValues as $sKey => $aValue)
|
|
{
|
|
$oP->add(trim($aValue['label'])."\t".$sKey."\n");
|
|
}
|
|
break;
|
|
default:
|
|
throw new Exception('Invalid output format, "'.$sOutputFormat.'" given.');
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param int $iObjId object ID
|
|
* @param string $sFormAttCode attcode if we need a value that isn't the display name
|
|
*
|
|
* @return string Either the display name of the selected object or the specified attribute value
|
|
*
|
|
* @uses \DBObject::GetName() to get display name
|
|
*
|
|
* @since 3.0.0 N°3227 add the $sAttCode parameter and method PHPDoc
|
|
*/
|
|
public function GetObjectName($iObjId, $sFormAttCode = null)
|
|
{
|
|
$aModifierProps = array();
|
|
$aModifierProps['UserRightsGetSelectFilter']['bSearchMode'] = $this->bSearchMode;
|
|
|
|
$oObj = MetaModel::GetObject($this->sTargetClass, $iObjId, false, false, $aModifierProps);
|
|
if ($oObj) {
|
|
if (is_null($sFormAttCode)) {
|
|
return $oObj->GetName();
|
|
} else {
|
|
return $oObj->Get($sFormAttCode);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the form to select a leaf class from the $this->sTargetClass (that should be abstract)
|
|
* Note: Inspired from UILinksWidgetDirect::GetObjectCreationDialog()
|
|
*
|
|
* @param WebPage $oPage
|
|
*
|
|
* @throws \CoreException
|
|
* @throws \DictExceptionMissingString
|
|
*/
|
|
public function GetClassSelectionForm(WebPage $oPage)
|
|
{
|
|
// For security reasons: check that the "proposed" class is actually a subclass of the linked class
|
|
// and that the current user is allowed to create objects of this class
|
|
$aSubClasses = MetaModel::EnumChildClasses($this->sTargetClass);
|
|
$aPossibleClasses = array();
|
|
foreach($aSubClasses as $sCandidateClass)
|
|
{
|
|
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
|
|
{
|
|
$aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass);
|
|
}
|
|
}
|
|
|
|
$sClassLabel = MetaModel::GetName($this->sTargetClass);
|
|
$sDialogTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel);;
|
|
$oBlock = UIContentBlockUIBlockFactory::MakeStandard('ac_create_'.$this->iId,['ibo-is-visible']);
|
|
$oPage->AddSubBlock($oBlock);
|
|
$oClassForm = FormUIBlockFactory::MakeStandard();
|
|
$oBlock->AddSubBlock($oClassForm);
|
|
$oClassForm->AddSubBlock(cmdbAbstractObject::DisplayBlockSelectClassToCreate( $sClassLabel, $this->sTargetClass, $aPossibleClasses));
|
|
$sDialogTitleEscaped = addslashes($sDialogTitle);
|
|
$oPage->add_ready_script("$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitleEscaped'});\n");
|
|
$oPage->add_ready_script("$('#ac_create_{$this->iId} form').removeAttr('onsubmit');");
|
|
$oPage->add_ready_script("$('#ac_create_{$this->iId} form').on('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);");
|
|
}
|
|
|
|
/**
|
|
* Get the form to create a new object of the 'target' class
|
|
*/
|
|
public function GetObjectCreationForm(WebPage $oPage, $oCurrObject, $aPrefillFormParam)
|
|
{
|
|
// Set all the default values in an object and clone this "default" object
|
|
$oNewObj = MetaModel::NewObject($this->sTargetClass);
|
|
|
|
// 1st - set context values
|
|
$oAppContext = new ApplicationContext();
|
|
$oAppContext->InitObjectFromContext($oNewObj);
|
|
$oNewObj->PrefillForm('creation_from_extkey', $aPrefillFormParam);
|
|
// 2nd set the default values from the constraint on the external key... if any
|
|
if ( ($oCurrObject != null) && ($this->sAttCode != ''))
|
|
{
|
|
$oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode);
|
|
$aParams = array('this' => $oCurrObject);
|
|
$oSet = $oAttDef->GetAllowedValuesAsObjectSet($aParams);
|
|
$aConsts = $oSet->ListConstantFields();
|
|
$sClassAlias = $oSet->GetFilter()->GetClassAlias();
|
|
if (isset($aConsts[$sClassAlias]))
|
|
{
|
|
foreach($aConsts[$sClassAlias] as $sAttCode => $value) {
|
|
$oNewObj->Set($sAttCode, $value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3rd - set values from the page argument 'default'
|
|
$oNewObj->UpdateObjectFromArg('default');
|
|
|
|
$sClassLabel = MetaModel::GetName($this->sTargetClass);
|
|
$sHeaderTitleEscaped = utils::EscapeHtml(Dict::Format('UI:CreationTitle_Class', $sClassLabel));
|
|
|
|
$oPage->add(<<<HTML
|
|
<div id="ac_create_{$this->iId}" title="{$sHeaderTitleEscaped}">
|
|
<div id="dcr_{$this->iId}">
|
|
HTML
|
|
);
|
|
$aFieldsFlags = array();
|
|
$aFieldsComments = array();
|
|
foreach (MetaModel::ListAttributeDefs($this->sTargetClass) as $sAttCode => $oAttDef) {
|
|
if (($oAttDef instanceof AttributeBlob) || (false)) {
|
|
$aFieldsFlags[$sAttCode] = OPT_ATT_READONLY;
|
|
$aFieldsComments[$sAttCode] = ' <img src="../images/transp-lock.png" style="vertical-align:middle" title="'.htmlentities(Dict::S('UI:UploadNotSupportedInThisMode')).'"/>';
|
|
}
|
|
}
|
|
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true, 'fieldsFlags' => $aFieldsFlags, 'fieldsComments' => $aFieldsComments));
|
|
$oPage->add(<<<HTML
|
|
</div>
|
|
</div>
|
|
HTML
|
|
);
|
|
|
|
$oPage->add_ready_script(<<<JS
|
|
$('#ac_create_{$this->iId}').dialog({ width: $(window).width() * 0.6, height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true});
|
|
$('#dcr_{$this->iId} form').removeAttr('onsubmit');
|
|
$('#dcr_{$this->iId} form').find('button[type="submit"]').on('click', oACWidget_{$this->iId}.DoCreateObject);
|
|
JS
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Display the hierarchy of the 'target' class
|
|
*/
|
|
public function DisplayHierarchy(WebPage $oPage, $sFilter, $currValue, $oObj)
|
|
{
|
|
$sDialogTitle = addslashes(Dict::Format('UI:HierarchyOf_Class', MetaModel::GetName($this->sTargetClass)));
|
|
$oPage->add('<div id="dlg_tree_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div style="margin-bottom:5px;" id="tree_'.$this->iId.'">');
|
|
$oPage->add('<table style="width:100%"><tr><td>');
|
|
if (is_null($sFilter))
|
|
{
|
|
throw new Exception('Implementation: null value for allowed values definition');
|
|
}
|
|
|
|
$oFilter = DBObjectSearch::FromOQL($sFilter);
|
|
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
|
$oSet = new DBObjectSet($oFilter, array(), array('this' => $oObj, 'current_extkey_id' => $currValue));
|
|
|
|
$oSet->SetShowObsoleteData(utils::ShowObsoleteData());
|
|
|
|
$sHKAttCode = MetaModel::IsHierarchicalClass($this->sTargetClass);
|
|
$bHasChildLeafs = $this->DumpTree($oPage, $oSet, $sHKAttCode, $currValue);
|
|
|
|
$oPage->add('</td></tr></table>');
|
|
$oPage->add('</div>');
|
|
|
|
if ($bHasChildLeafs)
|
|
{
|
|
$oPage->add('<span class="treecontrol ibo-button-group" id="treecontrolid"><a class="ibo-button ibo-is-regular ibo-is-neutral" href="?#">'.Dict::S("UI:Treeview:CollapseAll").'</a><a class="ibo-button ibo-is-regular ibo-is-neutral" href="?#">'.Dict::S("UI:Treeview:ExpandAll").'</a></span>');
|
|
}
|
|
|
|
$oPage->add('</div></div>');
|
|
|
|
$sOkButtonLabel = Dict::S('UI:Button:Ok');
|
|
$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
|
|
$oPage->add_ready_script("\$('#tree_$this->iId ul').treeview({ control: '#treecontrolid', persist: 'false'});\n");
|
|
$oPage->add_ready_script(<<<JS
|
|
$('#dlg_tree_$this->iId').dialog({
|
|
width: 'auto',
|
|
height: 'auto',
|
|
autoOpen: true,
|
|
modal: true,
|
|
title: '$sDialogTitle',
|
|
open: function() {
|
|
$(this).css("max-height", parseInt($(window).height()*0.7)+'px');
|
|
},
|
|
buttons: [
|
|
{
|
|
text: "$sCancelButtonLabel",
|
|
click: function() { $(this).dialog( "close" ); }
|
|
},
|
|
{
|
|
text: "$sOkButtonLabel",
|
|
class: "ibo-is-primary",
|
|
click: function() {
|
|
oACWidget_{$this->iId}.DoHKOk();
|
|
},
|
|
},
|
|
],
|
|
|
|
resizeStop: oACWidget_{$this->iId}.OnHKResize,
|
|
close: oACWidget_{$this->iId}.OnHKClose
|
|
});
|
|
|
|
$('#dlg_tree_$this->iId + .ui-dialog-buttonpane .ui-dialog-buttonset').prepend($('#treecontrolid'));
|
|
|
|
JS
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get the form to create a new object of the 'target' class
|
|
*/
|
|
public function DoCreateObject($oPage)
|
|
{
|
|
try
|
|
{
|
|
$oObj = MetaModel::NewObject($this->sTargetClass);
|
|
$aErrors = $oObj->UpdateObjectFromPostedForm($this->iId);
|
|
if (count($aErrors) == 0)
|
|
{
|
|
$oObj->DBInsert();
|
|
return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
|
|
}
|
|
else
|
|
{
|
|
return array('error' => implode(' ', $aErrors), 'id' => 0);
|
|
}
|
|
}
|
|
catch(Exception $e)
|
|
{
|
|
return array('error' => $e->getMessage(), 'id' => 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param WebPage $oP
|
|
* @param \DBObjectSet $oSet
|
|
* @param string $sParentAttCode
|
|
* @param string $currValue
|
|
*
|
|
* @return bool true if there are at least one child leaf, false if only roots nodes are present
|
|
* @throws \ArchivedObjectException
|
|
* @throws \CoreException
|
|
* @throws \CoreUnexpectedValue
|
|
* @throws \MySQLException
|
|
*/
|
|
function DumpTree($oP, $oSet, $sParentAttCode, $currValue)
|
|
{
|
|
$aTree = array();
|
|
$aNodes = array();
|
|
while($oObj = $oSet->Fetch())
|
|
{
|
|
$iParentId = $oObj->Get($sParentAttCode);
|
|
if (!isset($aTree[$iParentId]))
|
|
{
|
|
$aTree[$iParentId] = array();
|
|
}
|
|
$aTree[$iParentId][$oObj->GetKey()] = $oObj->GetName();
|
|
$aNodes[$oObj->GetKey()] = $oObj;
|
|
}
|
|
|
|
$aParents = array_keys($aTree);
|
|
$aRoots = array();
|
|
foreach($aParents as $id)
|
|
{
|
|
if (!array_key_exists($id, $aNodes))
|
|
{
|
|
$aRoots[] = $id;
|
|
}
|
|
}
|
|
foreach($aRoots as $iRootId)
|
|
{
|
|
$this->DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue);
|
|
}
|
|
|
|
$bHasOnlyRootNodes = (count($aTree) === 1);
|
|
return !$bHasOnlyRootNodes;
|
|
}
|
|
|
|
function DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue)
|
|
{
|
|
$bSelect = true;
|
|
$bMultiple = false;
|
|
$sSelect = '';
|
|
if (array_key_exists($iRootId, $aTree))
|
|
{
|
|
$aSortedRoots = $aTree[$iRootId];
|
|
asort($aSortedRoots);
|
|
$oP->add("<ul>\n");
|
|
$fUniqueId = microtime(true);
|
|
foreach($aSortedRoots as $id => $sName)
|
|
{
|
|
if ($bSelect)
|
|
{
|
|
$sChecked = ($aNodes[$id]->GetKey() == $currValue) ? 'checked' : '';
|
|
if ($bMultiple)
|
|
{
|
|
$sSelect = '<input id="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'" type="checkbox" value="'.$aNodes[$id]->GetKey().'" name="selectObject[]" '.$sChecked.'> ';
|
|
}
|
|
else
|
|
{
|
|
$sSelect = '<input id="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'" type="radio" value="'.$aNodes[$id]->GetKey().'" name="selectObject" '.$sChecked.'> ';
|
|
}
|
|
}
|
|
$oP->add('<li class="closed">'.$sSelect.'<label for="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'">'.$aNodes[$id]->GetName().'</label>');
|
|
$this->DumpNodes($oP, $id, $aTree, $aNodes, $currValue);
|
|
$oP->add("</li>\n");
|
|
}
|
|
$oP->add("</ul>\n");
|
|
}
|
|
}
|
|
|
|
}
|