tag that can be rendered synchronously * or as a piece of Javascript/JQuery/Ajax that will get its content from another page (ajax.render.php). */ class DisplayBlock { const TAG_BLOCK = 'itopblock'; /** @var \DBSearch */ protected $m_oFilter; protected $m_aConditions; // Conditions added to the filter -> avoid duplicate conditions protected $m_sStyle; protected $m_bAsynchronous; protected $m_aParams; /** @var \DBObjectSet|null */ protected $m_oSet; protected $m_bShowObsoleteData = null; /** * @param \DBSearch $oFilter list of cmdbObjects to be displayed into the block * @param string $sStyle one of : *
{$e->getMessage()}
HTML;
$oExceptionAlert = AlertFactory::MakeForFailure('Cannot display results', $sExceptionContent);
$oHtml->AddSubBlock($oExceptionAlert);
}
IssueLog::Error('Exception during GetDisplay: '.$e->getMessage());
}
} else {
// render it as an Ajax (asynchronous) call
$oHtml->AddCSSClasses("display_block loading");
$oHtml->AddHtml("
".Dict::S('UI:Loading').'
'.Dict::Format($sFormat, $iCount).'
'); } return $oBlock; } /** * @param \WebPage $oPage * @param string|null $sId * @param array $aExtraParams * * @return \Combodo\iTop\Application\UI\Base\iUIBlock */ protected function RenderSearch(WebPage $oPage, ?string $sId, array $aExtraParams): iUIBlock { $oBlock = null; if (!$oPage->IsPrintableVersion()) { $aExtraParams['currentId'] = $sId; $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->sParams = ''; $oBlock->sDefault = ''; $oBlock->sEventAttachedData = ''; $oBlock->sAbsoluteUrlAppRoot = utils::GetAbsoluteUrlAppRoot(); if (count($aClasses) > 1) { // 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) { if ($this->m_oSet->CountWithLimit(1) > 0) { if (empty($aExtraParams['currentId'])) { $iListId = utils::GetUniqueId(); // Works only if not in an Ajax page !! } else { $iListId = $aExtraParams['currentId']; } $oBlock->AddSubBlock(DataTableFactory::MakeForObject($oPage, $iListId, $this->m_oSet, $aExtraParams)); } else { // Empty set $oBlock->bEmptySet = true; } } else { // Not authorized $oBlock->bNotAuthorized = true; } } else { // The list is made of only 1 class of objects, actions on the list are possible if (($this->m_oSet->CountWithLimit(1) > 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES)) { $oBlock->AddSubBlock(cmdbAbstractObject::GetDisplaySetBlock($oPage, $this->m_oSet, $aExtraParams)); } else { $oBlock->bEmptySet = true; $oBlock->sClass = $this->m_oFilter->GetClass(); $bDisplayMenu = isset($aExtraParams['menu']) ? ($aExtraParams['menu'] == true) : true; if ($bDisplayMenu) { if ((UserRights::IsActionAllowed($oBlock->sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) { $oBlock->sLinkTarget = ''; $oAppContext = new ApplicationContext(); $oBlock->sParams = $oAppContext->GetForLink(); // 1:n links, populate the target object as a default value when creating a new linked object if (isset($aExtraParams['target_attr'])) { $oBlock->sLinkTarget = ' target="_blank" '; $aExtraParams['default'][$aExtraParams['target_attr']] = $aExtraParams['object_id']; } if (!empty($aExtraParams['default'])) { foreach ($aExtraParams['default'] as $sKey => $sValue) { $oBlock->sDefault .= "&default[$sKey]=$sValue"; } } $oBlock->bCreateNew = true; } } } 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, )); } } } 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("$sCategory |
| "); $oBlock = cmdbAbstractObject::GetDisplaySetBlock($oPage, $oSet, $aExtraParams); $oContentBlock->AddSubBlock($oBlock); $oHtml = new Html(); $oContentBlock->AddSubBlock($oHtml); $oHtml->AddHtml(" |
| "); $oContentBlock->AddSubBlock($oBlock); $oHtml = new Html(); $oContentBlock->AddSubBlock($oHtml); $oHtml->AddHtml(" |
'.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 $sId * @param array $aQueryParams * * @param array $aExtraParams * * @return \Combodo\iTop\Application\UI\DisplayBlock\BlockChart\BlockChart * @throws \ArchivedObjectException * @throws \CoreException */ protected function RenderChart(?string $sId, array $aQueryParams, array $aExtraParams) { static $iChartCounter = 0; $iChartCounter++; $oBlock = new BlockChart(); $oBlock->iChartCounter = $iChartCounter; $oBlock->sId = $sId; $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(); $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]=$sId{$iChartCounter}¶ms[order_direction]=$sOrderDirection¶ms[order_by]=$sOrderBy¶ms[limit]=$sLimit¶ms[aggregation_function]=$sAggregationFunction¶ms[aggregation_attribute]=$sAggregationAttr&id=$sId{$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]=$sId{$iChartCounter}¶ms[order_direction]=$sOrderDirection¶ms[order_by]=$sOrderBy¶ms[limit]=$sLimit¶ms[aggregation_function]=$sAggregationFunction¶ms[aggregation_attribute]=$sAggregationAttr&id=$sId{$iChartCounter}&filter=".rawurlencode($sFilter).'&'.$sContextParam; } $oBlock->sUrl = $sUrl; 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 = ''; if (isset($aExtraParams['group_by'])) { $this->MakeGroupByQuery($aExtraParams, $oGroupByExp, $sGroupByLabel, $aGroupBy, $sAggregationFunction, $sFctVar, $sAggregationAttr, $sSql); $aRes = CMDBSource::QueryToArray($sSql); $oContext = new ApplicationContext(); $sContextParam = $oContext->GetForLink(); $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' => (int)$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); } switch ($sChartType) { case 'bars': $aNames = array(); foreach ($aValues as $idx => $aValue) { $aNames[$idx] = $aValue['label']; } $oBlock = new BlockChartAjaxBars(); $oBlock->sJSNames = json_encode($aNames); $oBlock->sJson = json_encode($aValues); $oBlock->sId = $sId; $oBlock->sJSURLs = $sJSURLs; break; case 'pie': $aColumns = array(); $aNames = array(); foreach ($aValues as $idx => $aValue) { $aColumns[] = array('series_'.$idx, (int)$aValue['value']); $aNames['series_'.$idx] = $aValue['label']; } $oBlock = new BlockChartAjaxPie(); $oBlock->sJSColumns = json_encode($aColumns); $oBlock->sJSNames = json_encode($aNames); $oBlock->sId = $sId; $oBlock->sJSURLs = $sJSURLs; break; } return $oBlock; } /** * @param \ApplicationContext $oAppContext * @param string|null $sId * * @return iUIBlock * @throws \ArchivedObjectException * @throws \CoreException */ protected function RenderCsv(ApplicationContext $oAppContext, string $sId = null) { $oBlock = new BlockCsv($sId); $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().'&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_advance'] = 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; } } /** * Helper class to manage 'blocks' of HTML pieces that are parts of a page and contain some list of cmdb objects * * Each block is actually rendered as a tag that can be rendered synchronously * or as a piece of Javascript/JQuery/Ajax that will get its content from another page (ajax.render.php). * The list of cmdbObjects to be displayed into the block is defined by a filter * Right now the type of display is either: list, count or details * - list produces a table listing the objects * - count produces a paragraphs with a sentence saying 'cont' objects found * - details display (as table) the details of each object found (best if only one) */ class HistoryBlock extends DisplayBlock { protected $iLimitCount; protected $iLimitStart; public function __construct(DBSearch $oFilter, $sStyle = 'list', $bAsynchronous = false, $aParams = array(), $oSet = null) { parent::__construct($oFilter, $sStyle, $bAsynchronous, $aParams, $oSet); $this->iLimitStart = 0; $this->iLimitCount = 0; } public function SetLimit($iCount, $iStart = 0) { $this->iLimitStart = $iStart; $this->iLimitCount = $iCount; } public function GetRenderContent(WebPage $oPage, array $aExtraParams = [], string $sId = null): iUIBlock { $sHtml = ''; $bTruncated = false; $oSet = new CMDBObjectSet($this->m_oFilter, array('date' => false)); if (!$oPage->IsPrintableVersion()) { if (($this->iLimitStart > 0) || ($this->iLimitCount > 0)) { $oSet->SetLimit($this->iLimitCount, $this->iLimitStart); if (($this->iLimitCount - $this->iLimitStart) < $oSet->Count()) { $bTruncated = true; } } } $sHtml .= "\n"; switch ($this->m_sStyle) { case 'toggle': // First the latest change that the user is allowed to see do { $oLatestChangeOp = $oSet->Fetch(); } while (is_object($oLatestChangeOp) && ($oLatestChangeOp->GetDescription() == '')); if (is_object($oLatestChangeOp)) { // There is one change in the list... only when the object has been created ! $sDate = $oLatestChangeOp->GetAsHTML('date'); $oChange = MetaModel::GetObject('CMDBChange', $oLatestChangeOp->Get('change')); $sUserInfo = $oChange->GetAsHTML('userinfo'); $sHtml .= $oPage->GetStartCollapsibleSection(Dict::Format('UI:History:LastModified_On_By', $sDate, $sUserInfo)); $sHtml .= $this->GetHistoryTable($oPage, $oSet); $sHtml .= $oPage->GetEndCollapsibleSection(); } break; case 'table': default: if ($bTruncated) { $sFilter = htmlentities($this->m_oFilter->serialize(), ENT_QUOTES, 'UTF-8'); $sHtml .= ''; $sHtml .= Dict::Format('UI:TruncatedResults', $this->iLimitCount, $oSet->Count()); $sHtml .= ' '; $sHtml .= ''.Dict::S('UI:DisplayAll').''; $sHtml .= $this->GetHistoryTable($oPage, $oSet); $sHtml .= '