m_sStyle}', $sFilter, $sExtraParams);
}, '$iReloadInterval');
JS
);
}
if (!empty($oBlock)) {
return $oBlock;
}
return new Html($sHtml);
}
/**
* Add a condition (restriction) to the current DBSearch on which the display block is based
* taking into account the hierarchical keys for which the condition is based on the 'below' operator
*
* @param string $sFilterCode
* @param array $condition
* @param string $sOpCode
* @param bool $bParseSearchString
*
* @throws \CoreException
* @throws \CoreWarning
* @throws \Exception
*/
protected function AddCondition($sFilterCode, $condition, $sOpCode = null, $bParseSearchString = false)
{
// Workaround to an issue revealed whenever a condition on org_id is applied twice (with a hierarchy of organizations)
// Moreover, it keeps the query as simple as possible
if (isset($this->m_aConditions[$sFilterCode]) && $condition == $this->m_aConditions[$sFilterCode])
{
// Skip
return;
}
$this->m_aConditions[$sFilterCode] = $condition;
$sClass = $this->m_oFilter->GetClass();
$bConditionAdded = false;
// If the condition is an external key with a class having a hierarchy, use a "below" criteria
if (MetaModel::IsValidAttCode($sClass, $sFilterCode))
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sFilterCode);
if ($oAttDef->IsExternalKey())
{
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($oAttDef->GetTargetClass());
if ($sHierarchicalKeyCode !== false)
{
$oFilter = new DBObjectSearch($oAttDef->GetTargetClass());
if (($sOpCode == 'IN') && is_array($condition))
{
$oFilter->AddConditionExpression(self::GetConditionIN($oFilter, 'id', $condition));
}
else
{
$oFilter->AddCondition('id', $condition);
}
$oHKFilter = new DBObjectSearch($oAttDef->GetTargetClass());
$oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW); // Use the 'below' operator by default
$this->m_oFilter->AddCondition_PointingTo($oHKFilter, $sFilterCode);
$bConditionAdded = true;
}
else if (($sOpCode == 'IN') && is_array($condition))
{
$this->m_oFilter->AddConditionExpression(self::GetConditionIN($this->m_oFilter, $sFilterCode, $condition));
$bConditionAdded = true;
}
}
else if (($sOpCode == 'IN') && is_array($condition))
{
$this->m_oFilter->AddConditionExpression(self::GetConditionIN($this->m_oFilter, $sFilterCode, $condition));
$bConditionAdded = true;
}
}
// In all other cases, just add the condition directly
if (!$bConditionAdded)
{
$this->m_oFilter->AddCondition($sFilterCode, $condition, null); // Use the default 'loose' operator
}
}
static protected function GetConditionIN($oFilter, $sFilterCode, $condition)
{
$oField = new FieldExpression($sFilterCode, $oFilter->GetClassAlias());
$sListExpr = '('.implode(', ', CMDBSource::Quote($condition)).')';
$sOQLCondition = $oField->RenderExpression()." IN $sListExpr";
$oNewCondition = Expression::FromOQL($sOQLCondition);
return $oNewCondition;
}
/**
* For the result to be meaningful, this function must be called AFTER GetRenderContent() (or Display())
* @return int
*/
public function GetDisplayedCount()
{
return $this->m_oSet->Count();
}
/**
* @param $aExtraParams
* @param $oGroupByExp
* @param $sGroupByLabel
* @param $aGroupBy
* @param $sAggregationFunction
* @param $sFctVar
* @param $sAggregationAttr
* @param $sSql
*
* @throws \Exception
*/
protected function MakeGroupByQuery(&$aExtraParams, &$oGroupByExp, &$sGroupByLabel, &$aGroupBy, &$sAggregationFunction, &$sFctVar, &$sAggregationAttr, &$sSql)
{
$sAlias = $this->m_oFilter->GetClassAlias();
if (isset($aExtraParams['group_by_label']))
{
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$sGroupByLabel = $aExtraParams['group_by_label'];
}
else
{
// Backward compatibility: group_by is simply a field id
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
}
// Security filtering
$aFields = $oGroupByExp->ListRequiredFields();
foreach($aFields as $sFieldAlias)
{
$aMatches = array();
if (preg_match('/^([^.]+)\\.([^.]+)$/', $sFieldAlias, $aMatches))
{
$sFieldClass = $this->m_oFilter->GetClassName($aMatches[1]);
$oAttDef = MetaModel::GetAttributeDef($sFieldClass, $aMatches[2]);
if ($oAttDef instanceof AttributeOneWayPassword)
{
throw new Exception('Grouping on password fields is not supported.');
}
}
}
$aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp;
$aQueryParams = array();
if (isset($aExtraParams['query_params']))
{
$aQueryParams = $aExtraParams['query_params'];
}
$aFunctions = array();
$sAggregationFunction = 'count';
$sFctVar = '_itop_count_';
$sAggregationAttr = '';
if (isset($aExtraParams['aggregation_function']) && !empty($aExtraParams['aggregation_attribute']))
{
$sAggregationFunction = $aExtraParams['aggregation_function'];
$sAggregationAttr = $aExtraParams['aggregation_attribute'];
$oAttrExpr = Expression::FromOQL('`'.$sAlias.'`.`'.$sAggregationAttr.'`');
$oFctExpr = new FunctionExpression(strtoupper($sAggregationFunction), array($oAttrExpr));
$sFctVar = '_itop_'.$sAggregationFunction.'_';
$aFunctions = array($sFctVar => $oFctExpr);
}
if (!empty($sAggregationAttr))
{
$sClass = $this->m_oFilter->GetClass();
$sAggregationAttr = MetaModel::GetLabel($sClass, $sAggregationAttr);
}
$iLimit = 0;
if (isset($aExtraParams['limit']))
{
$iLimit = intval($aExtraParams['limit']);
}
$aOrderBy = array();
if (isset($aExtraParams['order_direction']) && isset($aExtraParams['order_by']))
{
switch ($aExtraParams['order_by'])
{
case 'attribute':
$aOrderBy = array('grouped_by_1' => ($aExtraParams['order_direction'] === 'asc'));
break;
case 'function':
$aOrderBy = array($sFctVar => ($aExtraParams['order_direction'] === 'asc'));
break;
}
}
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true, $aFunctions, $aOrderBy, $iLimit);
}
/**
* @param array $aExtraParams
*
* @return iUIBlock
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreWarning
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
protected function RenderSummary(array $aExtraParams): iUIBlock
{
$sClass = $this->m_oFilter->GetClass();
$oAppContext = new ApplicationContext();
$sStateAttrCode = isset($aExtraParams['status[block]']) ? $aExtraParams['status[block]'] : 'status';
$sStatesList = isset($aExtraParams['status_codes[block]']) ? $aExtraParams['status_codes[block]'] : '';
$bContextFilter = isset($aExtraParams['context_filter']) ? isset($aExtraParams['context_filter']) != 0 : false;
if ($bContextFilter) {
foreach ($oAppContext->GetNames() as $sFilterCode) {
$sContextParamValue = $oAppContext->GetCurrentValue($sFilterCode, null);
if (!is_null($sContextParamValue) && !empty($sContextParamValue) && MetaModel::IsValidFilterCode($sClass, $sFilterCode)) {
$this->AddCondition($sFilterCode, $sContextParamValue);
}
}
$aQueryParams = array();
if (isset($aExtraParams['query_params'])) {
$aQueryParams = $aExtraParams['query_params'];
}
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, array(), $aQueryParams);
$this->m_oSet->SetShowObsoleteData($this->m_bShowObsoleteData);
}
// Summary details
$aCounts = array();
$aStateLabels = array();
if (!empty($sStateAttrCode) && !empty($sStatesList)) {
$aStates = explode(',', $sStatesList);
// Generate one count + group by query [#1330]
$sClassAlias = $this->m_oFilter->GetClassAlias();
$oGroupByExpr = Expression::FromOQL($sClassAlias.'.'.$sStateAttrCode);
$aGroupBy = array('group1' => $oGroupByExpr);
$oGroupBySearch = $this->m_oFilter->DeepClone();
if (isset($this->m_bShowObsoleteData)) {
$oGroupBySearch->SetShowObsoleteData($this->m_bShowObsoleteData);
}
$sCountGroupByQuery = $oGroupBySearch->MakeGroupByQuery($aQueryParams, $aGroupBy, false);
$aCountGroupByResults = CMDBSource::QueryToArray($sCountGroupByQuery);
$aCountsQueryResults = array();
foreach ($aCountGroupByResults as $aCountGroupBySingleResult) {
$aCountsQueryResults[$aCountGroupBySingleResult[0]] = $aCountGroupBySingleResult[1];
}
$oAttDef = MetaModel::GetAttributeDef($sClass, $sStateAttrCode);
$aValues = $oAttDef->GetAllowedValues();
foreach ($aStates as $sStateValue) {
$aStateLabels[$sStateValue] = $aValues[$sStateValue] ?? '';
$aCounts[$sStateValue] = (array_key_exists($sStateValue, $aCountsQueryResults))
? $aCountsQueryResults[$sStateValue]
: 0;
if ($aCounts[$sStateValue] == 0) {
$aCounts[$sStateValue] = ['link' => '-', 'label' => $aCounts[$sStateValue]];;
} else {
$oSingleGroupByValueFilter = $this->m_oFilter->DeepClone();
$oSingleGroupByValueFilter->AddCondition($sStateAttrCode, $sStateValue, '=');
if (isset($this->m_bShowObsoleteData)) {
$oSingleGroupByValueFilter->SetShowObsoleteData($this->m_bShowObsoleteData);
}
$sHyperlink = utils::GetAbsoluteUrlAppRoot()
.'pages/UI.php?operation=search'.$oAppContext->GetForLink(true)
.'&filter='.rawurlencode($oSingleGroupByValueFilter->serialize());
$aCounts[$sStateValue] = ['link' => $sHyperlink, 'label' => $aCounts[$sStateValue]];
}
}
}
$oBlock = new UIContentBlockWithJSRefreshCallback(null, ["ibo-dashlet-header-dynamic--container"]);
// N°6394 Make sure to sort elements as defined in the datamodel
if (utils::IsNotNullOrEmptyString($sStateAttrCode)) {
$oAttDef = MetaModel::GetAttributeDef($sClass, $sStateAttrCode);
$aAllowedValues = $oAttDef->GetAllowedValues() ?? [];
$AllowedValuesKeys = array_keys($aAllowedValues);
uksort($aStateLabels, function ($sKey1, $sKey2) use ($AllowedValuesKeys) {
return array_search($sKey1, $AllowedValuesKeys) > array_search($sKey2, $AllowedValuesKeys) ? 1 : -1;
});
}
foreach ($aStateLabels as $sStateValue => $sStateLabel) {
$aCount = $aCounts[$sStateValue];
$sHyperlink = $aCount['link'];
$sCountLabel = $aCount['label'];
$oPill = PillFactory::MakeForState($sClass, $sStateValue);
// N°5849 - Unencode label for ExternalKey attribute because friendlyname is already html encoded thanks to DBObject::GetName() in AttributeExternalKey::GetAllowedValues(). (A fix in this function may have too much impact).
if ($oAttDef instanceof AttributeExternalKey) {
$sPillTooltip = utils::EscapedHtmlDecode($sStateLabel);
$sPillLabel = $sStateLabel;
} else {
$sPillTooltip = $sStateLabel;
$sPillLabel = utils::HtmlEntities($sStateLabel);
}
$oPill->SetTooltip($sPillTooltip)
->AddHtml("");
if ($sHyperlink != '-') {
$oPill->SetUrl($sHyperlink);
}
$oBlock->AddSubBlock($oPill);
}
$aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams();
if(isset($aExtraParams['query_params']['this->object()'])){
$aExtraParams['query_params']['this->class'] = get_class($aExtraParams['query_params']['this->object()']);
$aExtraParams['query_params']['this->id'] = $aExtraParams['query_params']['this->object()']->GetKey();
unset($aExtraParams['query_params']['this->object()']);
}
$aRefreshParams = ['filter' => $this->m_oFilter->ToOQL(), "extra_params" => json_encode($aExtraParams)];
$oBlock->SetJSRefresh(
"$('#".$oBlock->GetId()."').block();
$.post('ajax.render.php?operation=refreshDashletSummary',
".json_encode($aRefreshParams).",
function(data){
$('#".$oBlock->GetId()."').html(data);
$('#".$oBlock->GetId()."').unblock();
});
$('#".$oBlock->GetId()."').unblock();");
return $oBlock;
}
/**
* @param array $aExtraParams
*
* @return string[]
*/
protected function GetAllowedActionsParams(array $aExtraParams)
{
return [
'context_filter', /** int if != 0 filter with user context */
'query_params', /** array query parameters */
];
}
/**
* @param array $aExtraParams
*
* @return iUIBlock
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreWarning
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
protected function RenderActions(array $aExtraParams): iUIBlock
{
$sClass = $this->m_oFilter->GetClass();
$oAppContext = new ApplicationContext();
$bContextFilter = isset($aExtraParams['context_filter']) ? isset($aExtraParams['context_filter']) != 0 : false;
if ($bContextFilter && is_null($this->m_oSet)) {
foreach ($oAppContext->GetNames() as $sFilterCode) {
$sContextParamValue = $oAppContext->GetCurrentValue($sFilterCode, null);
if (!is_null($sContextParamValue) && !empty($sContextParamValue) && MetaModel::IsValidFilterCode($sClass, $sFilterCode)) {
$this->AddCondition($sFilterCode, $sContextParamValue);
}
}
$aQueryParams = array();
if (isset($aExtraParams['query_params'])) {
$aQueryParams = $aExtraParams['query_params'];
}
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, array(), $aQueryParams);
$this->m_oSet->SetShowObsoleteData($this->m_bShowObsoleteData);
}
$iCount = $this->m_oSet->Count();
$sClassLabel = MetaModel::GetName($sClass);
$sClassIconUrl = MetaModel::GetClassIcon($sClass, false);
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search'.$oAppContext->GetForLink(true).'&filter='.rawurlencode($this->m_oFilter->serialize());
$aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams();
$aRefreshParams = [
"filter" => $this->m_oFilter->ToOQL(),
"extra_params" => $aExtraParams,
];
if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY)) {
$sCreateActionUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=new&class='.$sClass.$oAppContext->GetForLink(true);
$sCreateActionLabel = Dict::Format('UI:Button:Create');
$oBlock = DashletFactory::MakeForDashletBadge($sClassIconUrl, $sHyperlink, $iCount, $sClassLabel, $sCreateActionUrl,
$sCreateActionLabel, $aRefreshParams);
} else {
$oBlock = DashletFactory::MakeForDashletBadge($sClassIconUrl, $sHyperlink, $iCount, $sClassLabel, null, null, $aRefreshParams);
}
$sClassDescription = MetaModel::GetClassDescription($sClass);
if (utils::IsNotNullOrEmptyString($sClassDescription)) {
$oBlock->SetClassDescription($sClassDescription);
}
return $oBlock;
}
/**
* @param array $aExtraParams
*
* @return \Combodo\iTop\Application\UI\Base\iUIBlock
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \Exception
*/
protected function RenderCount(array $aExtraParams): iUIBlock
{
if (isset($aExtraParams['group_by'])) {
$this->MakeGroupByQuery($aExtraParams, $oGroupByExp, $sGroupByLabel, $aGroupBy, $sAggregationFunction, $sFctVar, $sAggregationAttr, $sSql);
$aRes = CMDBSource::QueryToArray($sSql);
$aGroupBy = array();
$aLabels = array();
$aValues = array();
$iTotalCount = 0;
foreach ($aRes as $iRow => $aRow) {
$sValue = $aRow['grouped_by_1'];
$aValues[$iRow] = $sValue;
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
$aLabels[$iRow] = $sHtmlValue;
$aGroupBy[$iRow] = (float)$aRow[$sFctVar];
$iTotalCount += $aRow['_itop_count_'];
}
$aData = array();
$oAppContext = new ApplicationContext();
$sParams = $oAppContext->GetForLink(true);
foreach ($aGroupBy as $iRow => $iCount) {
// Build the search for this subset
$oSubsetSearch = $this->m_oFilter->DeepClone();
$oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($aValues[$iRow]));
$oSubsetSearch->AddConditionExpression($oCondition);
if (isset($aExtraParams['query_params'])) {
$aQueryParams = $aExtraParams['query_params'];
} else {
$aQueryParams = array();
}
$sFilter = rawurlencode($oSubsetSearch->serialize(false, $aQueryParams));
$aData[] = array(
'group' => $aLabels[$iRow],
'value' => "$iCount"
); // TO DO: add the context information
}
$aAttribs = array(
'group' => array('label' => $sGroupByLabel, 'description' => ''),
'value' => array(
'label' => Dict::S('UI:GroupBy:'.$sAggregationFunction),
'description' => Dict::Format('UI:GroupBy:'.$sAggregationFunction.'+', $sAggregationAttr),
),
);
$sFormat = isset($aExtraParams['format']) ? $aExtraParams['format'] : 'UI:Pagination:HeaderNoSelection';
$aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams();
$aOption['dom'] = 'pl';
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
$sTitle = Dict::Format($sFormat, $iTotalCount);
$oBlock = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
$oBlock->AddSubTitleBlock(new Html($sTitle));
$oBlock->AddCSSClass('ibo-datatable-panel');
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
$oBlock->SetIcon($aExtraParams["panel_icon"]);
}
$oDataTable = DataTableUIBlockFactory::MakeForStaticData("", $aAttribs, $aData, null, $aExtraParams, $this->m_oFilter->ToOQL(), $aOption);
$oBlock->AddSubBlock($oDataTable);
} else {
$sTitle = Dict::Format($sFormat, $iTotalCount);
$oBlock = DataTableUIBlockFactory::MakeForStaticData($sTitle, $aAttribs, $aData, null, $aExtraParams, $this->m_oFilter->ToOQL(), $aOption);
}
} else {
// Simply count the number of elements in the set
$iCount = $this->m_oSet->Count();
$sFormat = 'UI:CountOfObjects';
if (isset($aExtraParams['format'])) {
$sFormat = $aExtraParams['format'];
}
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
$oBlock = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
$oBlock->SetIcon($aExtraParams["panel_icon"]);
}
$oBlock->AddSubBlock(new Html(''.Dict::Format($sFormat, $iCount).'
'));
} else {
$oBlock = new Html(''.Dict::Format($sFormat, $iCount).'
');
}
}
return $oBlock;
}
/**
* @param WebPage $oPage
* @param array $aExtraParams
*
* @return \Combodo\iTop\Application\UI\Base\iUIBlock
*/
protected function RenderSearch(WebPage $oPage, array $aExtraParams): iUIBlock
{
$oBlock = null;
if (!$oPage->IsPrintableVersion()) {
$oSearchForm = new SearchForm();
$oBlock = $oSearchForm->GetSearchFormUIBlock($oPage, $this->m_oSet, $aExtraParams);
}
return $oBlock;
}
protected function RenderListSearch(array $aExtraParams, WebPage $oPage)
{
return $this->RenderList($aExtraParams, $oPage);
}
/**
* @param array $aExtraParams
* @param WebPage $oPage
*
* @throws \ArchivedObjectException
* @throws \ConfigException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
* @throws \ReflectionException
*/
protected function RenderList(array $aExtraParams, WebPage $oPage)
{
$oBlock = new BlockList();
$aClasses = $this->m_oSet->GetSelectedClasses();
$aAuthorizedClasses = [];
$oBlock->bEmptySet = false;
$oBlock->bNotAuthorized = false;
$oBlock->bCreateNew = false;
$oBlock->sLinkTarget = '';
$oBlock->sClass = '';
$oBlock->sClassLabel = '';
$oBlock->sParams = '';
$oBlock->sDefault = '';
$oBlock->sEventAttachedData = '';
$oBlock->sAbsoluteUrlAppRoot = utils::GetAbsoluteUrlAppRoot();
$oBlock->aExtraParams = $aExtraParams;
$oBlock->sFilter = $this->m_oFilter->ToOQL();
// Check the classes that can be read (i.e authorized) by this user...
foreach ($aClasses as $sAlias => $sClassName) {
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) != UR_ALLOWED_NO) {
$aAuthorizedClasses[$sAlias] = $sClassName;
}
}
if (count($aAuthorizedClasses) === 0) {
// Not authorized
$oBlock->bNotAuthorized = true;
$oBlock->bEmptySet = true;
$oBlock->sClass = $this->m_oFilter->GetClass();
$oBlock->sClassLabel = MetaModel::GetName($oBlock->sClass);
return $oBlock;
}
if (isset($aExtraParams['update_history']) && true == $aExtraParams['update_history']) {
$sSearchFilter = $this->m_oSet->GetFilter()->serialize();
// Limit the size of the URL (N°1585 - request uri too long)
if (strlen($sSearchFilter) < SERVER_MAX_URL_LENGTH) {
$oBlock->sEventAttachedData = json_encode(array(
'filter' => $sSearchFilter,
'breadcrumb_id' => "ui-search-".$this->m_oSet->GetClass(),
'breadcrumb_label' => MetaModel::GetName($this->m_oSet->GetClass()),
'breadcrumb_max_count' => utils::GetConfig()->Get('breadcrumb.max_count'),
'breadcrumb_instance_id' => MetaModel::GetConfig()->GetItopInstanceid(),
'breadcrumb_icon' => 'fas fa-search',
'breadcrumb_icon_type' => iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES,
));
}
}
$iListId = utils::IsNullOrEmptyString($aExtraParams['currentId']) ? utils::GetUniqueId() /* Works only if not in an Ajax page !! */ : $aExtraParams['currentId'];
// Note: Method calls not factorized into a single call with method name as a variable to keep track of DataTableUIBlockFactory calls as it is a critical / unstable component.
$oBlock->AddSubBlock(
$this->m_sStyle === static::ENUM_STYLE_LIST_IN_OBJECT ?
DataTableUIBlockFactory::MakeForObject($oPage, $iListId, $this->m_oSet, $aExtraParams) :
DataTableUIBlockFactory::MakeForResult($oPage, $iListId, $this->m_oSet, $aExtraParams)
);
return $oBlock;
}
/**
* @param array $aExtraParams
* @param WebPage $oPage
*
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
* @throws \ApplicationException
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MySQLException
* @throws \OQLException
*/
protected function RenderJoin(array $aExtraParams, WebPage $oPage)
{
$oContentBlock = new UIContentBlock();
$oHtml = new Html();
$oContentBlock->AddSubBlock($oHtml);
$aDisplayAliases = isset($aExtraParams['display_aliases']) ? explode(',', $aExtraParams['display_aliases']) : array();
if (!isset($aExtraParams['group_by'])) {
$oHtml->AddHtml(''.Dict::S('UI:Error:MandatoryTemplateParameter_group_by').'
');
} else {
$aGroupByFields = array();
$aGroupBy = explode(',', $aExtraParams['group_by']);
foreach ($aGroupBy as $sGroupBy) {
$aMatches = array();
if (preg_match('/^(.+)\.(.+)$/', $sGroupBy, $aMatches) > 0) {
$aGroupByFields[] = array('alias' => $aMatches[1], 'att_code' => $aMatches[2]);
}
}
if (count($aGroupByFields) == 0) {
$oHtml->AddHtml(''.Dict::Format('UI:Error:InvalidGroupByFields', $aExtraParams['group_by']).'
');
} else {
$aResults = array();
$aCriteria = array();
while ($aObjects = $this->m_oSet->FetchAssoc()) {
$aKeys = array();
foreach ($aGroupByFields as $aField) {
$sAlias = $aField['alias'];
if (is_null($aObjects[$sAlias])) {
$aKeys[$sAlias.'.'.$aField['att_code']] = '';
} else {
$aKeys[$sAlias.'.'.$aField['att_code']] = $aObjects[$sAlias]->Get($aField['att_code']);
}
}
$sCategory = implode($aKeys, ' ');
$aResults[$sCategory][] = $aObjects;
$aCriteria[$sCategory] = $aKeys;
}
$oHtml->AddHtml("\n");
// Construct a new (parametric) query that will return the content of this block
$oBlockFilter = $this->m_oFilter->DeepClone();
$aExpressions = array();
$index = 0;
foreach ($aGroupByFields as $aField) {
$aExpressions[] = '`'.$aField['alias'].'`.`'.$aField['att_code'].'` = :param'.$index++;
}
$sExpression = implode(' AND ', $aExpressions);
$oExpression = Expression::FromOQL($sExpression);
$oBlockFilter->AddConditionExpression($oExpression);
$aExtraParams['menu'] = false;
foreach ($aResults as $sCategory => $aObjects) {
$oHtml->AddHtml("$sCategory |
\n");
if (count($aDisplayAliases) == 1) {
$aSimpleArray = array();
foreach ($aObjects as $aRow) {
$oObj = $aRow[$aDisplayAliases[0]];
if (!is_null($oObj)) {
$aSimpleArray[] = $oObj;
}
}
$oSet = CMDBObjectSet::FromArray($this->m_oFilter->GetClass(), $aSimpleArray);
$oHtml->AddHtml("| ");
$oBlock = cmdbAbstractObject::GetDisplaySetBlock($oPage, $oSet, $aExtraParams);
$oContentBlock->AddSubBlock($oBlock);
$oHtml = new Html();
$oContentBlock->AddSubBlock($oHtml);
$oHtml->AddHtml(" |
\n");
} else {
$index = 0;
$aArgs = array();
foreach ($aGroupByFields as $aField) {
$aArgs['param'.$index] = $aCriteria[$sCategory][$aField['alias'].'.'.$aField['att_code']];
$index++;
}
$oSet = new CMDBObjectSet($oBlockFilter, array(), $aArgs);
if (empty($aExtraParams['currentId'])) {
$iListId = utils::GetUniqueId(); // Works only if not in an Ajax page !!
} else {
$iListId = $aExtraParams['currentId'];
}
$oBlock = DataTableUIBlockFactory::MakeForRendering($iListId, $oSet, $aExtraParams);
$oHtml->AddHtml("| ");
$oContentBlock->AddSubBlock($oBlock);
$oHtml = new Html();
$oContentBlock->AddSubBlock($oHtml);
$oHtml->AddHtml(" |
\n");
}
}
$oHtml->AddHtml("
\n");
}
}
return $oContentBlock;
}
/**
* @deprecated 3.1.0 N°5957
*
* @param WebPage $oPage
* @param array $aExtraParams
*
* @return \Combodo\iTop\Application\UI\Base\Component\Html\Html|\Combodo\iTop\Application\UI\Base\Layout\UIContentBlock|string
* @throws \ApplicationException
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
* @throws \ReflectionException
*/
protected function RenderLinks(WebPage $oPage, array $aExtraParams)
{
// Note: No deprecation ticket yet as we want to wait and see if people / code actually use this method, in which case we might keep it.
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('This method is most likely not used throughout the application and will be removed soon. If you ever see this message, please inform us.');
$oBlock = null;
if (($this->m_oSet->CountWithLimit(1) > 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES)) {
$oBlock = cmdbAbstractObject::GetDisplaySetBlock($oPage, $this->m_oSet, $aExtraParams);
} else {
$sClass = $this->m_oFilter->GetClass();
$oAttDef = MetaModel::GetAttributeDef($sClass, $this->m_aParams['target_attr']);
$sTargetClass = $oAttDef->GetTargetClass();
$oBlock = new Html(''.Dict::Format('UI:NoObject_Class_ToDisplay', MetaModel::GetName($sTargetClass)).'
');
$bDisplayMenu = isset($this->m_aParams['menu']) ? $this->m_aParams['menu'] == true : true;
if ($bDisplayMenu) {
if ((UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) {
$sDefaults = '';
if (isset($this->m_aParams['default'])) {
foreach ($this->m_aParams['default'] as $sName => $sValue) {
$sDefaults .= '&'.urlencode($sName).'='.urlencode($sValue);
}
}
$oBlock->AddHtml("".Dict::Format('UI:ClickToCreateNew',
Metamodel::GetName($sClass))."
\n");
}
}
}
return $oBlock;
}
/**
* @param string|null $sChartId
* @param array $aQueryParams
*
* @param array $aExtraParams
*
* @return \Combodo\iTop\Application\UI\DisplayBlock\BlockChart\BlockChart
* @throws \ArchivedObjectException
* @throws \CoreException
*/
protected function RenderChart(?string $sChartId, array $aQueryParams, array $aExtraParams)
{
static $iChartCounter = 0;
$iChartCounter++;
$oBlock = new BlockChart();
$oBlock->iChartCounter = $iChartCounter;
$oBlock->sChartId = $sChartId;
$sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie';
$sGroupBy = isset($aExtraParams['group_by']) ? $aExtraParams['group_by'] : '';
$sGroupByExpr = isset($aExtraParams['group_by_expr']) ? '¶ms[group_by_expr]='.$aExtraParams['group_by_expr'] : '';
$sFilter = $this->m_oFilter->serialize(false, $aQueryParams);
$oContext = new ApplicationContext();
$sContextParam = $oContext->GetForLink(true);
$sAggregationFunction = isset($aExtraParams['aggregation_function']) ? $aExtraParams['aggregation_function'] : '';
$sAggregationAttr = isset($aExtraParams['aggregation_attribute']) ? $aExtraParams['aggregation_attribute'] : '';
$sLimit = isset($aExtraParams['limit']) ? $aExtraParams['limit'] : '';
$sOrderBy = isset($aExtraParams['order_by']) ? $aExtraParams['order_by'] : '';
$sOrderDirection = isset($aExtraParams['order_direction']) ? $aExtraParams['order_direction'] : '';
if (isset($aExtraParams['group_by_label'])) {
$sUrl = utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart¶ms[group_by]=$sGroupBy{$sGroupByExpr}¶ms[group_by_label]={$aExtraParams['group_by_label']}¶ms[chart_type]=$sChartType¶ms[currentId]=$sChartId{$iChartCounter}¶ms[order_direction]=$sOrderDirection¶ms[order_by]=$sOrderBy¶ms[limit]=$sLimit¶ms[aggregation_function]=$sAggregationFunction¶ms[aggregation_attribute]=$sAggregationAttr&id=$sChartId{$iChartCounter}&filter=".rawurlencode($sFilter).$sContextParam;
} else {
$sUrl = utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart¶ms[group_by]=$sGroupBy{$sGroupByExpr}¶ms[chart_type]=$sChartType¶ms[currentId]=$sChartId{$iChartCounter}¶ms[order_direction]=$sOrderDirection¶ms[order_by]=$sOrderBy¶ms[limit]=$sLimit¶ms[aggregation_function]=$sAggregationFunction¶ms[aggregation_attribute]=$sAggregationAttr&id=$sChartId{$iChartCounter}&filter=".rawurlencode($sFilter).'&'.$sContextParam;
}
$oBlock->sUrl = $sUrl;
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
$oPanel = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
$oPanel->SetIcon($aExtraParams["panel_icon"]);
}
$oPanel->AddSubBlock($oBlock);
return $oPanel;
}
return $oBlock;
}
/**
* @param array $aExtraParams
* @param WebPage $oPage
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \MySQLException
* @throws \Exception
*/
protected function RenderChartAjax(array $aExtraParams)
{
$sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie';
$sId = utils::ReadParam('id', '');
$aValues = array();
$oBlock = null;
$sJSURLs = '';
$oContext = new ApplicationContext();
$sContextParam = $oContext->GetForLink(true);
if (isset($aExtraParams['group_by'])) {
$this->MakeGroupByQuery($aExtraParams, $oGroupByExp, $sGroupByLabel, $aGroupBy, $sAggregationFunction, $sFctVar, $sAggregationAttr, $sSql);
$aRes = CMDBSource::QueryToArray($sSql);
$iTotalCount = 0;
$aURLs = array();
foreach ($aRes as $iRow => $aRow) {
$sValue = $aRow['grouped_by_1'];
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
$iTotalCount += $aRow['_itop_count_'];
$aValues[] = array(
'label' => html_entity_decode(strip_tags($sHtmlValue), ENT_QUOTES, 'UTF-8'),
'label_html' => $sHtmlValue,
'value' => (float)$aRow[$sFctVar],
);
// Build the search for this subset
$oSubsetSearch = $this->m_oFilter->DeepClone();
$oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($sValue));
$oSubsetSearch->AddConditionExpression($oCondition);
$aURLs[] = utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&format=html&filter=".rawurlencode($oSubsetSearch->serialize()).$sContextParam;
}
$sJSURLs = json_encode($aURLs);
}
if (isset($aExtraParams['group_by_label'])) {
$sUrl = utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart¶ms[group_by]=$aExtraParams[group_by]¶ms[group_by_label]={$aExtraParams['group_by_label']}¶ms[chart_type]=$sChartType¶ms[currentId]=$aExtraParams[currentId]¶ms[order_direction]=$aExtraParams[order_direction]¶ms[order_by]=$aExtraParams[order_by]¶ms[limit]=$aExtraParams[limit]¶ms[aggregation_function]=$sAggregationFunction¶ms[aggregation_attribute]=$sAggregationAttr&id=$sId&filter=".rawurlencode($this->m_oFilter->ToOQL()).$sContextParam;
} else {
$sUrl = utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart¶ms[group_by]=$aExtraParams[group_by]¶ms[chart_type]=$sChartType¶ms[currentId]=$aExtraParams[currentId]¶ms[order_direction]=$aExtraParams[order_direction]¶ms[order_by]=$aExtraParams[order_by]¶ms[limit]=$aExtraParams[limit]¶ms[aggregation_function]=$sAggregationFunction¶ms[aggregation_attribute]=$sAggregationAttr&id=$sId&filter=".rawurlencode($this->m_oFilter->ToOQL()).$sContextParam;
}
switch ($sChartType) {
case 'bars':
$iMaxNbCharsInLabel = 0;
$aNames = [];
foreach ($aValues as $idx => $aValue) {
$aNames[$idx] = $aValue['label'];
if ($iMaxNbCharsInLabel < mb_strlen($aValue['label'])) {
$iMaxNbCharsInLabel = mb_strlen($aValue['label']);
}
}
$oBlock = new BlockChartAjaxBars();
$oBlock->sJSNames = json_encode($aNames);
$oBlock->sJson = json_encode($aValues);
$oBlock->sId = $sId;
$oBlock->sJSURLs = $sJSURLs;
$oBlock->sURLForRefresh = str_replace("'", "\'", $sUrl);
$oBlock->iMaxNbCharsInLabel = $iMaxNbCharsInLabel;
break;
case 'pie':
$aColumns = [];
$aNames = [];
foreach ($aValues as $idx => $aValue) {
$aColumns[] = array('series_'.$idx, (float)$aValue['value']);
$aNames['series_'.$idx] = $aValue['label'];
}
$iNbLinesToAddForName = 0;
if (count($aNames) > 50) {
// Calculation of the number of legends line add to the height of the graph to have a maximum of 5 legend columns
$iNbLinesIncludedInChartHeight = 10;
$iNbLinesToAddForName = ceil(count($aNames) / 5) - $iNbLinesIncludedInChartHeight;
}
$oBlock = new BlockChartAjaxPie();
$oBlock->sJSColumns = json_encode($aColumns);
$oBlock->sJSNames = json_encode($aNames);
$oBlock->sId = $sId;
$oBlock->sJSURLs = $sJSURLs;
$oBlock->sURLForRefresh = str_replace("'", "\'", $sUrl);
$oBlock->iNbLinesToAddForName = $iNbLinesToAddForName;
break;
}
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
$oPanel = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
$oPanel->SetIcon($aExtraParams["panel_icon"]);
}
$oPanel->AddSubBlock($oBlock);
return $oPanel;
}
return $oBlock;
}
/**
* @param \ApplicationContext $oAppContext
*
* @return iUIBlock
* @throws \ArchivedObjectException
* @throws \CoreException
*/
protected function RenderCsv(ApplicationContext $oAppContext)
{
$oBlock = new BlockCsv();
$oBlock->bAdvancedMode = utils::ReadParam('advanced', false);
$oBlock->sCsvFile = strtolower($this->m_oFilter->GetClass()).'.csv';
$oBlock->sDownloadLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?expression='.urlencode($this->m_oFilter->ToOQL(true)).'&format=csv&filename='.urlencode($oBlock->sCsvFile);
$oBlock->sLinkToToggle = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search'.$oAppContext->GetForLink(true).'&filter='.rawurlencode($this->m_oFilter->serialize()).'&format=csv';
// Pass the parameters via POST, since expression may be very long
$aParamsToPost = array(
'expression' => $this->m_oFilter->ToOQL(true),
'format' => 'csv',
'filename' => $oBlock->sCsvFile,
'charset' => 'UTF-8',
);
if ($oBlock->bAdvancedMode) {
$oBlock->sDownloadLink .= '&fields_advanced=1';
$aParamsToPost['fields_advanced'] = 1;
$oBlock->sChecked = 'CHECKED';
} else {
$oBlock->sLinkToToggle = $oBlock->sLinkToToggle.'&advanced=1';
$oBlock->sChecked = '';
}
$oBlock->sAjaxLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php';
$oBlock->sCharsetNotice = false;
$oBlock->sJsonParams = json_encode($aParamsToPost);
return $oBlock;
}
}
/**
* Displays the 'Actions' menu for a given (list of) object(s)
* The 'style' of the list (see constructor of DisplayBlock) can be either 'list' or 'details'
* For backward compatibility 'popup' is equivalent to 'list'...
*/
class MenuBlock extends DisplayBlock
{
/**
* @var string Prefix to use for the ID of the actions toolbar
* @used-by static::GetRenderContent
* @since 3.0.0
*/
public const ACTIONS_TOOLBAR_ID_PREFIX = 'ibo-actions-toolbar-';
/**
* Renders the "Actions" popup menu for the given set of objects
*
* Note that the menu links containing (or ending) with a hash (#) will have their fragment
* part (whatever is after the hash) dynamically replaced (by javascript) when the menu is
* displayed, to correspond to the current hash/fragment in the page. This allows modifying
* an object in with the same tab active by default as the tab that was active when selecting
* the "Modify..." action.
*
* @param WebPage $oPage
* @param array $aExtraParams
* @param string|null $sId
*
* @return \Combodo\iTop\Application\UI\Base\iUIBlock
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
*
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value and add type hinting on $aExtraParams for PHP 8.0 compatibility
*/
public function GetRenderContent(WebPage $oPage, array $aExtraParams, string $sId)
{
$oRouter = Router::GetInstance();
$oRenderBlock = new UIContentBlock();
if ($this->m_sStyle == 'popup') // popup is a synonym of 'list' for backward compatibility
{
$this->m_sStyle = static::ENUM_STYLE_LIST;
}
$sClass = $this->GetFilter()->GetClass();
$aSelectedClasses = $this->GetFilter()->GetSelectedClasses();
$bIsForLinkset = isset($aExtraParams['target_attr']);
$oSet = new CMDBObjectSet($this->GetFilter());
$iSetCount = $oSet->Count();
/** @var string $sRefreshAction JS snippet to run when clicking on the refresh button of the menu */
$sRefreshAction = $aExtraParams['refresh_action'] ?? '';
$bIsCreationInModal = isset($aExtraParams['creation_in_modal']) && $aExtraParams['creation_in_modal'] === true;
$bIsCreationDisallowed = isset($aExtraParams['creation_disallowed']) && $aExtraParams['creation_disallowed'] === true;
// Check rights
$oReflectionClass = new ReflectionClass($sClass);
$bIsCreationAllowed = (!$bIsCreationDisallowed && UserRights::IsActionAllowed($sClass, UR_ACTION_CREATE) === UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
/** @var array $aRegularActions Any action other than a transition */
$aRegularActions = [];
/** @var array $aTransitionActions Only transitions */
$aTransitionActions = [];
/** @var array $aToolkitActions Any "legacy" toolkit menu item, which are now displayed in the same menu as the $aRegularActions, after them */
$aToolkitActions = [];
// Display menu actions only if...
if (
// ... NOT in a selection mode
(!isset($aExtraParams['selection_mode']) || ($aExtraParams['selection_mode'] == ""))
// ... "menu" parameter is NOT EXPLICITLY disabled
&& (!isset($aExtraParams['menu']) || $aExtraParams['menu'] === "1" || $aExtraParams['menu'] === true)
) {
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink(true);
$sFilter = $this->GetFilter()->serialize();
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass);
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
// Common params that will be applied to actions
$aActionParams = $this->GetDefaultParamsForMenuAction();
// 1:n links, populate the target object as a default value when creating a new linked object
if ($bIsForLinkset) {
$aExtraParams['default'][$aExtraParams['target_attr']] = $aExtraParams['object_id'];
}
/** @var string $sDefaultValuesAsUrlParams Default values for the object to create, already formatted as URL params (eg. "&default[org_id]=3&default[title]=Foo") */
$sDefaultValuesAsUrlParams = '';
if (!empty($aExtraParams['default'])) {
foreach ($aExtraParams['default'] as $sKey => $sValue) {
$sDefaultValuesAsUrlParams .= "&default[$sKey]=$sValue";
}
}
// Check concurrent lock (can only be lock if we are handling a single object
$bLocked = false;
if ($iSetCount === 1) {
$oObj = $oSet->Fetch();
if (false === is_null($oObj)) {
if (MetaModel::GetConfig()->Get('concurrent_lock_enabled')) {
$aLockInfo = iTopOwnershipLock::IsLocked(get_class($oObj), $oObj->GetKey());
if ($aLockInfo['locked']) {
$bLocked = true;
}
}
}
unset($oObj);
$oSet->Rewind();
}
//--------------------------------------------
// Any object count, any style. Common actions
//--------------------------------------------
// Create in new tab
if ($bIsCreationAllowed && !$bIsCreationInModal) {
$this->AddNewObjectMenuAction($aRegularActions, $sClass, $sDefaultValuesAsUrlParams);
}
// Filter this list (provided by extra params has it need filter param)
if (array_key_exists('filter_this_list_url', $aExtraParams)) {
$aToolkitActions[] = [
'label' => Dict::S('UI:Menu:FilterList'),
'url' => $aExtraParams['filter_this_list_url'],
];
}
// Transitions
// - Do not perform time-consuming computations if there are too many objects in the list
$iLimit = MetaModel::GetConfig()->Get('complex_actions_limit');
if (
($iSetCount > 0) && (false === $bLocked) && MetaModel::HasLifecycle($sClass) &&
( ($iLimit == 0) || ($iSetCount < $iLimit) )
) {
$aTransitions = [];
// Processing (optimizations) and endpoints are not exactly the same depending on if there is only 1 object or a set
if ($iSetCount === 1) {
$oObj = $oSet->Fetch();
if (false === is_null($oObj)) {
$sLifecycleClass = get_class($oObj);
$aTransitions = $oObj->EnumTransitions();
$aUrlParams = [
'operation' => 'stimulus',
'id' => $oObj->GetKey(),
];
}
unset($oObj);
$oSet->Rewind();
} else {
// Life cycle actions may be available... if all objects are in the same state
// Group by
$oGroupByExp = new FieldExpression(MetaModel::GetStateAttributeCode($sClass), $this->m_oFilter->GetClassAlias());
$aGroupBy = array('__state__' => $oGroupByExp);
$aQueryParams = array();
if (isset($aExtraParams['query_params'])) {
$aQueryParams = $aExtraParams['query_params'];
}
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy);
$aRes = CMDBSource::QueryToArray($sSql);
if (count($aRes) === 1) {
$sLifecycleClass = $sClass;
// All objects are in the same state...
$sState = $aRes[0]['__state__'];
$aTransitions = Metamodel::EnumTransitions($sLifecycleClass, $sState);
$aUrlParams = [
'operation' => 'select_bulk_stimulus',
'state' => $sState,
'filter' => $sFilter,
];
}
}
if (count($aTransitions)) {
$sUrlQueryString = http_build_query($aUrlParams).$sContext;
$aStimuli = Metamodel::EnumStimuli($sLifecycleClass);
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
$oSet->Rewind();
// As soon as the user rights implementation will browse the object set,
// then we might consider using OptimizeColumnLoad() here
$iActionAllowed = UserRights::IsStimulusAllowed($sLifecycleClass, $sStimulusCode, $oSet);
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) === 'StimulusUserAction') ? $iActionAllowed : UR_ALLOWED_NO;
switch ($iActionAllowed) {
case UR_ALLOWED_YES:
case UR_ALLOWED_DEPENDS:
$aTransitionActions[$sStimulusCode] = array(
'label' => $aStimuli[$sStimulusCode]->GetLabel(),
'url' => "{$sRootUrl}pages/UI.php?stimulus=$sStimulusCode&class=$sLifecycleClass&{$sUrlQueryString}",
) + $aActionParams;
break;
default:
// Do nothing
}
}
$oSet->Rewind();
}
}
//------------------------------------------
// Several objects (bulk), any style actions
//------------------------------------------
if ($iSetCount > 1) {
// Bulk actions for each selected classes (eg. "link" and "remote" on n:n relations)
foreach ($aSelectedClasses as $sSelectedAlias => $sSelectedClass) {
$sSelectedClassName = MetaModel::GetName($sSelectedClass);
// Check rights on class
$bIsBulkModifyAllowed = (!MetaModel::IsAbstract($sSelectedClass)) && UserRights::IsActionAllowed($sSelectedClass, UR_ACTION_BULK_MODIFY) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
$bIsBulkDeleteAllowed = (bool) UserRights::IsActionAllowed($sSelectedClass, UR_ACTION_BULK_DELETE);
// Refine filter on selected class so bullk actions occur on the right class
$oSelectedClassFilter = $this->GetFilter()->DeepClone();
$oSelectedClassFilter->SetSelectedClasses([$sSelectedAlias]);
// Action identifier is using the alias on purpose so they can be used as "shortcut actions" easily for "Link" or "Remote" aliases on linksets.
// Action label dict code has a specific suffix for "Link" / "Remote" aliases to allow dedicated labels in linksets.
$sActionLabelCodeSuffix = in_array($sSelectedAlias, ['Link', 'Remote']) ? $sSelectedAlias : 'Class';
if ($bIsBulkModifyAllowed) {
$this->AddBulkModifyObjectsMenuAction($aRegularActions, $sSelectedClass, $oSelectedClassFilter->serialize(), 'UI:Menu:ModifyAll:'.$sSelectedAlias, Dict::Format('UI:Menu:ModifyAll_'.$sActionLabelCodeSuffix, $sSelectedClassName));
}
if ($bIsBulkDeleteAllowed) {
$this->AddBulkDeleteObjectsMenuAction($aRegularActions, $sSelectedClass, $oSelectedClassFilter->serialize(), 'UI:Menu:BulkDelete:'.$sSelectedAlias, Dict::Format('UI:Menu:BulkDelete_'.$sActionLabelCodeSuffix, $sSelectedClassName));
}
}
}
//----------------------------------------------------
// Any style but NOT \DisplayBlock::ENUM_STYLE_LIST_IN_OBJECT (linksets) actions
//----------------------------------------------------
if ($this->m_sStyle !== static::ENUM_STYLE_LIST_IN_OBJECT) {
switch ($iSetCount) {
case 1:
$oObj = $oSet->Fetch();
if (false === is_null($oObj)) {
$id = $oObj->GetKey();
if (empty($sRefreshAction) && utils::ReadParam('operation') == 'details') {
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
$sRefreshAction = "window.location.reload();";
} else {
$sRefreshAction = "window.location.href='".ApplicationContext::MakeObjectUrl(get_class($oObj), $id)."';";
}
}
$bRawModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
$bIsModifyAllowed = !$bLocked && $bRawModifiedAllowed;
$bIsDeleteAllowed = !$bLocked && UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet);
// Just one object in the set, possible actions are "new / clone / modify and delete"
if (!isset($aExtraParams['link_attr'])) {
if ($bIsModifyAllowed) {
$aRegularActions['UI:Menu:Modify'] = array(
'label' => Dict::S('UI:Menu:Modify'),
'url' => $oRouter->GenerateUrl('object.modify', ['class' => $sClass, 'id' => $id]) . "{$sContext}#",
) + $aActionParams;
}
if ($bIsDeleteAllowed) {
$aRegularActions['UI:Menu:Delete'] = array(
'label' => Dict::S('UI:Menu:Delete'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$id{$sContext}",
) + $aActionParams;
}
// Relations...
$aRelations = MetaModel::EnumRelationsEx($sClass);
if (count($aRelations)) {
$this->AddMenuSeparator($aRegularActions);
foreach ($aRelations as $sRelationCode => $aRelationInfo) {
if (array_key_exists('down', $aRelationInfo)) {
$aRegularActions[$sRelationCode.'_down'] = array(
'label' => $aRelationInfo['down'],
'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=down&class=$sClass&id=$id{$sContext}",
) + $aActionParams;
}
if (array_key_exists('up', $aRelationInfo)) {
$aRegularActions[$sRelationCode.'_up'] = array(
'label' => $aRelationInfo['up'],
'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=up&class=$sClass&id=$id{$sContext}",
) + $aActionParams;
}
}
}
// Add a special menu to kill the lock, but only to allowed users who can also modify this object
if ($bLocked && $bRawModifiedAllowed) {
/** @var array $aAllowedProfiles */
$aAllowedProfiles = MetaModel::GetConfig()->Get('concurrent_lock_override_profiles');
$bCanKill = false;
$oUser = UserRights::GetUserObject();
$aUserProfiles = array();
if (!is_null($oUser)) {
$oProfileSet = $oUser->Get('profile_list');
while ($oProfile = $oProfileSet->Fetch()) {
$aUserProfiles[$oProfile->Get('profile')] = true;
}
}
foreach ($aAllowedProfiles as $sProfile) {
if (array_key_exists($sProfile, $aUserProfiles)) {
$bCanKill = true;
break;
}
}
// Lock removal button is not supported in modals yet
if (utils::IsXmlHttpRequest()) {
$bCanKill = false;
}
if ($bCanKill) {
$this->AddMenuSeparator($aRegularActions);
$aRegularActions['concurrent_lock_unlock'] = array(
'label' => Dict::S('UI:Menu:KillConcurrentLock'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=kill_lock&class=$sClass&id=$id{$sContext}",
);
}
}
}
$this->AddMenuSeparator($aRegularActions);
$this->GetEnumAllowedActions($oSet, function ($sLabel, $data) use (&$aRegularActions, $aActionParams) {
$aRegularActions[$sLabel] = array('label' => $sLabel, 'url' => $data) + $aActionParams;
});
}
break;
}
}
$this->AddMenuSeparator($aRegularActions);
$this->GetEnumAllowedActions($oSet, function ($sLabel, $data) use (&$aRegularActions, $aActionParams) {
if (is_array($data)) {
// New plugins can provide javascript handlers via the 'onclick' property
//TODO: enable extension of different menus by checking the 'target' property ??
$aRegularActions[$sLabel] = [
'label' => $sLabel,
'url' => isset($data['url']) ? $data['url'] : '#',
'onclick' => isset($data['onclick']) ? $data['onclick'] : '',
];
} else {
// Backward compatibility with old plugins
$aRegularActions[$sLabel] = ['label' => $sLabel, 'url' => $data] + $aActionParams;
}
});
if (empty($sRefreshAction) && $this->m_sStyle == static::ENUM_STYLE_LIST) {
//for the detail page this var is defined way beyond this line
$sRefreshAction = "window.location.reload();";
}
} else {
// It's easier just display configure this list and MENU_OBJLIST_TOOLKIT
}
$param = null;
if (is_null($sId)) {
$sId = uniqid();
}
// New extensions based on iPopupMenuItem interface
$oPopupMenuItemsBlock = new UIContentBlock();
switch ($this->m_sStyle) {
case static::ENUM_STYLE_LIST:
case static::ENUM_STYLE_LIST_IN_OBJECT:
$oSet->Rewind();
$param = $oSet;
utils::GetPopupMenuItemsBlock($oPopupMenuItemsBlock, iPopupMenuExtension::MENU_OBJLIST_ACTIONS, $param, $aRegularActions, $sId);
utils::GetPopupMenuItemsBlock($oPopupMenuItemsBlock, iPopupMenuExtension::MENU_OBJLIST_TOOLKIT, $param, $aToolkitActions, $sId);
break;
case 'details':
$oSet->Rewind();
$param = $oSet->Fetch();
utils::GetPopupMenuItemsBlock($oPopupMenuItemsBlock, iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS, $param, $aRegularActions, $sId);
break;
}
if ($oPopupMenuItemsBlock->HasSubBlocks()) {
$oRenderBlock->AddSubBlock($oPopupMenuItemsBlock);
} else {
foreach ($oPopupMenuItemsBlock->GetJsFilesRelPaths() as $sJsPath) {
$oRenderBlock->AddJsFileRelPath($sJsPath);
}
foreach ($oPopupMenuItemsBlock->GetCssFilesRelPaths() as $sCssPath) {
$oRenderBlock->AddCssFileRelPath($sCssPath);
}
}
// Extract favorite actions from their menus
$aFavoriteRegularActions = [];
$aFavoriteTransitionActions = [];
if (is_callable([$sClass, 'GetShortcutActions'])) {
/** @var cmdbAbstractObject $sClass */
$aShortcutActions = $sClass::GetShortcutActions($sClass);
foreach ($aShortcutActions as $key) {
// Regular actions
if (isset($aRegularActions[$key])) {
$aFavoriteRegularActions[$key] = $aRegularActions[$key];
unset($aRegularActions[$key]);
}
// Transitions
if (isset($aTransitionActions[$key])) {
$aFavoriteTransitionActions[$key] = $aTransitionActions[$key];
unset($aTransitionActions[$key]);
}
}
}
$oActionsToolbar = ToolbarUIBlockFactory::MakeForAction(static::ACTIONS_TOOLBAR_ID_PREFIX.$sId);
$oRenderBlock->AddSubBlock($oActionsToolbar);
$sRegularActionsMenuTogglerId = "ibo-regular-actions-menu-toggler-{$sId}";
$sRegularActionsPopoverMenuId = "ibo-regular-actions-popover-{$sId}";
$sTransitionActionsMenuTogglerId = "ibo-transition-actions-menu-toggler-{$sId}";
$sTransitionActionsPopoverMenuId = "ibo-transition-actions-popover-{$sId}";
if (!$oPage->IsPrintableVersion()) {
// Transitions actions
// - Favorites
foreach ($aFavoriteTransitionActions as $sActionId => $aAction) {
$sIconClass = '';
$sLabel = $aAction['label'];
$sUrl = $aAction['url'];
$sTarget = isset($aAction['target']) ? $aAction['target'] : '';
$oActionButton = ButtonUIBlockFactory::MakeLinkNeutral($sUrl, $sLabel, $sIconClass, $sTarget, $sActionId);
$oActionButton->AddCSSClasses(['ibo-action-button', 'ibo-transition-action-button']);
if (empty($sLabel)) {
$oActionButton->SetTooltip(Dict::S($sActionId));
}
$oActionsToolbar->AddSubBlock($oActionButton);
}
// - Others
if (!empty($aTransitionActions)) {
if (count($aFavoriteTransitionActions) > 0) {
$sName = 'UI:Menu:OtherTransitions';
} else {
$sName = 'UI:Menu:Transitions';
}
$oActionButton = ButtonUIBlockFactory::MakeIconAction('fas fa-map-signs', Dict::S($sName), $sName, '', false, $sTransitionActionsMenuTogglerId)
->AddCSSClasses(['ibo-action-button', 'ibo-transition-action-button']);
$oTransitionActionsMenu = $oPage->GetPopoverMenu($sTransitionActionsPopoverMenuId, $aTransitionActions)
->SetTogglerJSSelector("#$sTransitionActionsMenuTogglerId")
->AddVisualHintToToggler();
$oActionsToolbar->AddSubBlock($oActionButton)
->AddSubBlock($oTransitionActionsMenu);
}
// Separator between transitions and regulars
if ((!empty($aFavoriteTransitionActions) || !empty($aTransitionActions)) &&
(!empty($aFavoriteRegularActions) || !empty($aRegularActions))) {
$oActionsToolbar->AddSubBlock(ToolbarSeparatorUIBlockFactory::MakeVertical());
}
// Regular actions
// - Favorites
foreach ($aFavoriteRegularActions as $sActionId => $aAction) {
$sIconClass = '';
$sLabel = $aAction['label'];
$sUrl = $aAction['url'];
switch ($sActionId) {
case 'UI:Menu:New':
$sIconClass = 'fas fa-plus';
$sLabel = '';
break;
case 'UI:Menu:ModifyAll':
case 'UI:Menu:ModifyAll:Link': // Link class on linkset
case 'UI:Menu:ModifyAll:Remote': // Remote class on linkset
case 'UI:Menu:Modify':
$sIconClass = 'fas fa-pen';
$sLabel = '';
break;
case 'UI:Menu:BulkDelete':
case 'UI:Menu:BulkDelete:Link': // Link class on linkset
case 'UI:Menu:BulkDelete:Remote': // Remote class on linkset
case 'UI:Menu:Delete':
$sIconClass = 'fas fa-trash';
$sLabel = '';
break;
case 'UI:Menu:EMail':
$sIconClass = 'fas fa-share-alt';
$sLabel = '';
break;
default:
if (isset($aAction['icon_class']) && (strlen($aAction['icon_class']) > 0)) {
$sIconClass = $aAction['icon_class'];
$sLabel = '';
}
}
$sTarget = isset($aAction['target']) ? $aAction['target'] : '';
$oActionButton = ButtonUIBlockFactory::MakeLinkNeutral($sUrl, $sLabel, $sIconClass, $sTarget, utils::Sanitize($sActionId, '', utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER));
// ResourceId should not be sanitized
$oActionButton->AddDataAttribute('resource-id', $sActionId);
$oActionButton->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);
if (empty($sLabel)) {
if (empty($aAction['tooltip'])) {
$oActionButton->SetTooltip(Dict::S($sActionId));
} else {
$oActionButton->SetTooltip($aAction['tooltip']);
}
}
$oActionsToolbar->AddSubBlock($oActionButton);
}
// - Creation in modal
if ($bIsCreationAllowed && $bIsCreationInModal) {
$oAddLinkActionButton = ButtonUIBlockFactory::MakeIconAction(
'fas fa-plus',
// Allow button tooltip customization
array_key_exists('creation_in_modal_tooltip', $aExtraParams) ?
$aExtraParams['creation_in_modal_tooltip'] :
Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($sClass)),
'UI:Links:New',
'',
false
);
// creation form title
if (array_key_exists('creation_in_modal_form_title', $aExtraParams) && $aExtraParams['creation_in_modal_form_title'] !== null) {
$oAddLinkActionButton->AddDataAttribute('modal-title', $aExtraParams['creation_in_modal_form_title']);
}
// - If we are used in a Datatable, 'datatable_' will be prefixed to our $sId, so we do the same here
$sRealId = $sId;
if (in_array($this->m_sStyle, [static::ENUM_STYLE_LIST, 'links', static::ENUM_STYLE_LIST_IN_OBJECT])) {
$sRealId = 'datatable_'.$sId;
}
$oAddLinkActionButton->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button'])
->SetOnClickJsCode("$('#$sRealId').trigger('open_creation_modal.object.itop');");
$oActionsToolbar->AddSubBlock($oAddLinkActionButton);
}
// - Refresh
if (utils::IsNotNullOrEmptyString($sRefreshAction)) {
$oActionButton = ButtonUIBlockFactory::MakeAlternativeNeutral('', 'UI:Button:Refresh');
$oActionButton->SetIconClass('fas fa-sync-alt')
->SetOnClickJsCode($sRefreshAction)
->SetTooltip(Dict::S('UI:Button:Refresh'))
->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);
$oActionsToolbar->AddSubBlock($oActionButton);
}
// - Search
if ($this->m_sStyle === 'details') {
$oActionButton = ButtonUIBlockFactory::MakeIconLink('fas fa-search', Dict::Format('UI:SearchFor_Class', MetaModel::GetName($sClass)), "{$sRootUrl}pages/UI.php?operation=search_form&do_search=0&class=$sClass{$sContext}", '', 'UI:SearchFor_Class');
$oActionButton->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);
$oActionsToolbar->AddSubBlock($oActionButton);
}
// - Others
if (!empty($aRegularActions) || !empty($aToolkitActions)) {
if (count($aFavoriteRegularActions) > 0) {
$sName = 'UI:Menu:OtherActions';
} else {
$sName = 'UI:Menu:Actions';
}
$oActionButton = ButtonUIBlockFactory::MakeIconAction('fas fa-ellipsis-v', Dict::S($sName), $sName, '', false, $sRegularActionsMenuTogglerId)
->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);
$oRegularActionsMenu = $oPage->GetPopoverMenu($sRegularActionsPopoverMenuId, $aRegularActions)
->SetTogglerJSSelector("#$sRegularActionsMenuTogglerId")
->SetContainer(PopoverMenu::ENUM_CONTAINER_BODY);
$oActionsToolbar->AddSubBlock($oActionButton)
->AddSubBlock($oRegularActionsMenu);
// Toolkit actions
if (!empty($aToolkitActions)) {
// Add separator if necessary
if (count($aRegularActions) > 0) {
$oRegularActionsMenu->AddItem('separator-regular-actions-toolkit-actions', PopoverMenuItemFactory::MakeSeparator());
}
// Add actions
foreach ($aToolkitActions as $sActionId => $aActionData) {
$oRegularActionsMenu->AddItem('toolkit-actions', PopoverMenuItemFactory::MakeFromApplicationPopupMenuItemData($sActionId, $aActionData));
}
}
}
}
return $oRenderBlock;
}
/**
* If an extension doesn't return an array as expected :
* - calls IssueLog:Warning
* - if is dev env, then throw CoreUnexpectedValue exception
*
* @param \DBObjectSet $oSet
* @param callable $callback EnumAllowedActions returns an array, we will call this anonymous function on each of its value
* with two parameters : label (array index), data (array value)
*
* @throws \CoreUnexpectedValue
*
* @uses \MetaModel::EnumPlugins()
* @uses \iApplicationUIExtension::EnumAllowedActions()
* @uses \utils::IsDevelopmentEnvironment()
*
* @since 3.0.0
*/
private function GetEnumAllowedActions(DBObjectSet $oSet, callable $callback): void
{
$aInvalidExtensions = [];
/** @var \iApplicationUIExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) {
$oSet->Rewind();
$aExtEnumAllowedActions = $oExtensionInstance->EnumAllowedActions($oSet);
if (!is_array($aExtEnumAllowedActions)) {
$aInvalidExtensions[] = get_class($oExtensionInstance);
continue;
}
foreach ($aExtEnumAllowedActions as $sLabel => $data) {
$callback($sLabel, $data);
}
}
if (!empty($aInvalidExtensions)) {
$sMessage = 'Some extensions returned non array value for EnumAllowedActions() method impl';
IssueLog::Warning(
$sMessage,
null,
['extensions' => $aInvalidExtensions]
);
if (utils::IsDevelopmentEnvironment()) {
throw new CoreUnexpectedValue($sMessage, $aInvalidExtensions);
}
}
}
/**
* Appends a menu separator to the current list of actions
*
* @param array $aActions The current actions list
*
* @return void
*/
protected function AddMenuSeparator(&$aActions)
{
$sSeparator = '';
if (count($aActions) > 0) // Make sure that the separator is not the first item in the menu
{
$aKeys = array_keys($aActions);
$sLastKey = array_pop($aKeys);
if ($aActions[$sLastKey]['label'] != $sSeparator) // Make sure there are no 2 consecutive separators
{
$aActions['sep_'.(count($aActions)-1)] = array('label' => $sSeparator, 'url' => '');
}
}
}
/**
* Add a create action (to $aActions) for an $sClass object
*
* @param array &$aActions Pointer to the array in which the action will be added
* @param string $sClass Datamodel class concerned by the action
* @param string $sDefaultValuesAsUrlParams Default values for the new object form,
*
* @return void
* @since 3.1.0
* @internal
*/
protected function AddNewObjectMenuAction(array &$aActions, string $sClass, string $sDefaultValuesAsUrlParams = ''): void
{
$aActions['UI:Menu:New'] = [
'label' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($sClass)),
'url' => $this->PrepareUrlForStandardMenuAction($sClass, "operation=new&class=$sClass{$sDefaultValuesAsUrlParams}"),
] + $this->GetDefaultParamsForMenuAction();
}
/**
* Add a bulk modify action (to $aActions) on objects defined by $sFilter
*
* @param array &$aActions Pointer to the array in which the action will be added
* @param string $sClass Datamodel class concerned by the action
* @param string $sFilter OQL of the objects to propose for bulk modify
* @param string $sActionIdentifier Unique identifier for the action. Default if 'UI:Menu:ModifyAll'
* @param string $sActionLabel Label for the action, can be either a dict code to translate or an hardcoded label. Default is 'UI:Menu:ModifyAll'.
*
* @return void
* @since 3.1.0
* @internal
*/
protected function AddBulkModifyObjectsMenuAction(array &$aActions, string $sClass, string $sFilter, string $sActionIdentifier = 'UI:Menu:ModifyAll', $sActionLabel = 'UI:Menu:ModifyAll'): void
{
$aActions[$sActionIdentifier] = [
'label' => Dict::S($sActionLabel),
'url' => $this->PrepareUrlForStandardMenuAction($sClass, "operation=select_for_modify_all&class=$sClass&filter=".urlencode($sFilter)),
] + $this->GetDefaultParamsForMenuAction();
}
/**
* Add a bulk delete action (to $aActions) on objects defined by $sFilter
*
* @param array &$aActions Pointer to the array in which the action will be added
* @param string $sClass Datamodel class concerned by the action
* @param string $sFilter OQL of the objects to propose for bulk deletion
* @param string $sActionIdentifier Unique identifier for the action. Default if 'UI:Menu:BulkDelete'
* @param string $sActionLabel Label for the action, can be either a dict code to translate or an hardcoded label. Default is 'UI:Menu:BulkDelete'.
*
* @return void
* @since 3.1.0
* @internal
*/
protected function AddBulkDeleteObjectsMenuAction(array &$aActions, string $sClass, string $sFilter, string $sActionIdentifier = 'UI:Menu:BulkDelete', $sActionLabel = 'UI:Menu:BulkDelete')
{
$aActions[$sActionIdentifier] = array(
'label' => Dict::S($sActionLabel),
'url' => $this->PrepareUrlForStandardMenuAction($sClass, "operation=select_for_deletion&filter=".urlencode($sFilter)),
) + $this->GetDefaultParamsForMenuAction();
}
/**
* @return array Default parameters of a menu action
* @since 3.1.0
* @internal
*/
private function GetDefaultParamsForMenuAction(): array
{
$aDefaultParams = [];
if (isset($aExtraParams['menu_actions_target'])) {
$aDefaultParams['target'] = $aExtraParams['menu_actions_target'];
}
return $aDefaultParams;
}
/**
* @param string $sClass Datamodel class for which the URL is prepared
* @param string $sUrlParams URL parameters to add to the URL, must already be concatenated as a string (eg. "foo=bar&some=thing&third=param")
*
* @return string An absolute URL for a menu action on the $sClass class with $sUrlParams
* @throws \Exception
* @internal
*/
private function PrepareUrlForStandardMenuAction(string $sClass, string $sUrlParams)
{
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass);
$sUrl = "{$sRootUrl}pages/{$sUIPage}?{$sUrlParams}";
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink(true);
return $sUrl . $sContext;
}
}