diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index 2e39d6382..0b1087a4d 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -1219,7 +1219,6 @@ class NewObjectMenuNode extends MenuNode } } -require_once(APPROOT.'application/dashboard.class.inc.php'); /** * This class defines a menu item which content is based on XML dashboard. */ diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 8d6fe8ebc..86f8de148 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -131,6 +131,7 @@ return array( 'Collator' => $vendorDir . '/symfony/polyfill-intl-icu/Resources/stubs/Collator.php', 'Combodo\\iTop\\Application\\Branding' => $baseDir . '/sources/Application/Branding.php', 'Combodo\\iTop\\Application\\Dashboard\\Controller\\DashboardController' => $baseDir . '/sources/Application/Dashboard/Controller/DashboardController.php', + 'Combodo\\iTop\\Application\\Dashboard\\Dashboard' => $baseDir . '/sources/Application/Dashboard/Dashboard.php', 'Combodo\\iTop\\Application\\Dashboard\\DashboardException' => $baseDir . '/sources/Application/Dashboard/DashboardException.php', 'Combodo\\iTop\\Application\\Dashboard\\FormBlock\\DashboardFormBlock' => $baseDir . '/sources/Application/Dashboard/FormBlock/DashboardFormBlock.php', 'Combodo\\iTop\\Application\\Dashboard\\FormBlock\\DashletFormBlock' => $baseDir . '/sources/Application/Dashboard/FormBlock/DashletFormBlock.php', @@ -702,7 +703,6 @@ return array( 'DBUnionSearch' => $baseDir . '/core/dbunionsearch.class.php', 'DOMSanitizer' => $baseDir . '/core/htmlsanitizer.class.inc.php', 'DailyRotatingLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php', - 'Dashboard' => $baseDir . '/application/dashboard.class.inc.php', 'DashboardLayout' => $baseDir . '/application/dashboardlayout.class.inc.php', 'DashboardLayoutMultiCol' => $baseDir . '/application/dashboardlayout.class.inc.php', 'DashboardLayoutOneCol' => $baseDir . '/application/dashboardlayout.class.inc.php', @@ -1440,7 +1440,7 @@ return array( 'RowStatus_NewObj' => $baseDir . '/core/bulkchange.class.inc.php', 'RowStatus_NoChange' => $baseDir . '/core/bulkchange.class.inc.php', 'RunTimeIconSelectionField' => $baseDir . '/application/forms.class.inc.php', - 'RuntimeDashboard' => $baseDir . '/application/dashboard.class.inc.php', + 'RuntimeDashboard' => $baseDir . '/sources/Application/Dashboard/RuntimeDashboard.php', 'SQLExpression' => $baseDir . '/core/oql/expression.class.inc.php', 'SQLObjectQuery' => $baseDir . '/core/sqlobjectquery.class.inc.php', 'SQLObjectQueryBuilder' => $baseDir . '/core/sqlobjectquerybuilder.class.inc.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 7a397c21e..1b5a8c729 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -517,6 +517,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Collator' => __DIR__ . '/..' . '/symfony/polyfill-intl-icu/Resources/stubs/Collator.php', 'Combodo\\iTop\\Application\\Branding' => __DIR__ . '/../..' . '/sources/Application/Branding.php', 'Combodo\\iTop\\Application\\Dashboard\\Controller\\DashboardController' => __DIR__ . '/../..' . '/sources/Application/Dashboard/Controller/DashboardController.php', + 'Combodo\\iTop\\Application\\Dashboard\\Dashboard' => __DIR__ . '/../..' . '/sources/Application/Dashboard/Dashboard.php', 'Combodo\\iTop\\Application\\Dashboard\\DashboardException' => __DIR__ . '/../..' . '/sources/Application/Dashboard/DashboardException.php', 'Combodo\\iTop\\Application\\Dashboard\\FormBlock\\DashboardFormBlock' => __DIR__ . '/../..' . '/sources/Application/Dashboard/FormBlock/DashboardFormBlock.php', 'Combodo\\iTop\\Application\\Dashboard\\FormBlock\\DashletFormBlock' => __DIR__ . '/../..' . '/sources/Application/Dashboard/FormBlock/DashletFormBlock.php', @@ -1088,7 +1089,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'DBUnionSearch' => __DIR__ . '/../..' . '/core/dbunionsearch.class.php', 'DOMSanitizer' => __DIR__ . '/../..' . '/core/htmlsanitizer.class.inc.php', 'DailyRotatingLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php', - 'Dashboard' => __DIR__ . '/../..' . '/application/dashboard.class.inc.php', 'DashboardLayout' => __DIR__ . '/../..' . '/application/dashboardlayout.class.inc.php', 'DashboardLayoutMultiCol' => __DIR__ . '/../..' . '/application/dashboardlayout.class.inc.php', 'DashboardLayoutOneCol' => __DIR__ . '/../..' . '/application/dashboardlayout.class.inc.php', @@ -1826,7 +1826,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'RowStatus_NewObj' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php', 'RowStatus_NoChange' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php', 'RunTimeIconSelectionField' => __DIR__ . '/../..' . '/application/forms.class.inc.php', - 'RuntimeDashboard' => __DIR__ . '/../..' . '/application/dashboard.class.inc.php', + 'RuntimeDashboard' => __DIR__ . '/../..' . '/sources/Application/Dashboard/RuntimeDashboard.php', 'SQLExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php', 'SQLObjectQuery' => __DIR__ . '/../..' . '/core/sqlobjectquery.class.inc.php', 'SQLObjectQueryBuilder' => __DIR__ . '/../..' . '/core/sqlobjectquerybuilder.class.inc.php', diff --git a/sources/Application/Dashboard/Dashboard.php b/sources/Application/Dashboard/Dashboard.php new file mode 100644 index 000000000..dfa7c399f --- /dev/null +++ b/sources/Application/Dashboard/Dashboard.php @@ -0,0 +1,773 @@ +sTitle = ''; + $this->sLayoutClass = 'DashboardLayoutOneCol'; + $this->bAutoReload = false; + $this->iAutoReloadSec = MetaModel::GetConfig()->GetStandardReloadInterval(); + $this->aCells = []; + $this->oDOMNode = null; + $this->sId = $sId; + $this->oDashletFactory = DashletFactory::GetInstance(); + } + + /** + * @param string $sXml + * + * @throws \Exception + */ + public function FromXml($sXml) + { + $this->aCells = []; // reset the content of the dashboard + set_error_handler(['Dashboard', 'ErrorHandler']); + $oDoc = new PropertyTypeDesign(); + $oDoc->loadXML($sXml); + restore_error_handler(); + $this->FromDOMDocument($oDoc); + } + + /** + * @param \DOMDocument $oDoc + */ + public function FromDOMDocument(DesignDocument $oDoc) + { + $this->oDOMNode = $oDoc->getElementsByTagName('dashboard')->item(0); + + if ($this->oDOMNode->getElementsByTagName('cells')->count() === 0) { + $this->FromDOMDocumentV2($oDoc); + return; + } + + if ($oLayoutNode = $this->oDOMNode->getElementsByTagName('layout')->item(0)) { + $this->sLayoutClass = $oLayoutNode->textContent; + } else { + $this->sLayoutClass = 'DashboardLayoutOneCol'; + } + + if ($oTitleNode = $this->oDOMNode->getElementsByTagName('title')->item(0)) { + $this->sTitle = $oTitleNode->textContent; + } else { + $this->sTitle = ''; + } + + $this->bAutoReload = false; + $this->iAutoReloadSec = MetaModel::GetConfig()->GetStandardReloadInterval(); + if ($oAutoReloadNode = $this->oDOMNode->getElementsByTagName('auto_reload')->item(0)) { + if ($oAutoReloadEnabled = $oAutoReloadNode->getElementsByTagName('enabled')->item(0)) { + $this->bAutoReload = ($oAutoReloadEnabled->textContent == 'true'); + } + if ($oAutoReloadInterval = $oAutoReloadNode->getElementsByTagName('interval')->item(0)) { + $this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int)$oAutoReloadInterval->textContent); + } + } + + if ($oCellsNode = $this->oDOMNode->getElementsByTagName('cells')->item(0)) { + $oCellsList = $oCellsNode->getElementsByTagName('cell'); + $aCellOrder = []; + $iCellRank = 0; + /** @var \DOMElement $oCellNode */ + foreach ($oCellsList as $oCellNode) { + $oCellRank = $oCellNode->getElementsByTagName('rank')->item(0); + if ($oCellRank) { + $iCellRank = (float)$oCellRank->textContent; + } + $oDashletsNode = $oCellNode->getElementsByTagName('dashlets')->item(0); + { + $oDashletList = $oDashletsNode->getElementsByTagName('dashlet'); + $iRank = 0; + $aDashletOrder = []; + /** @var DesignElement $oDomNode */ + foreach ($oDashletList as $oDomNode) { + $oRank = $oDomNode->getElementsByTagName('rank')->item(0); + if ($oRank) { + $iRank = (float)$oRank->textContent; + } + + $oNewDashlet = $this->InitDashletFromDOMNode($oDomNode); + $aDashletOrder[] = ['rank' => $iRank, 'dashlet' => $oNewDashlet]; + } + usort($aDashletOrder, [get_class($this), 'SortOnRank']); + $aDashletList = []; + foreach ($aDashletOrder as $aItem) { + $aDashletList[] = $aItem['dashlet']; + } + $aCellOrder[] = ['rank' => $iCellRank, 'dashlets' => $aDashletList]; + } + } + usort($aCellOrder, [get_class($this), 'SortOnRank']); + foreach ($aCellOrder as $aItem) { + $this->aCells[] = $aItem['dashlets']; + } + } else { + $this->aCells = []; + } + } + + /** + * @param \DOMDocument $oDoc + */ + public function FromDOMDocumentV2(DesignDocument $oDoc) + { + $this->oDOMNode = $oDoc->getElementsByTagName('dashboard')->item(0); + + $this->sLayoutClass = DashboardLayoutGrid::class; + $this->sTitle = $this->oDOMNode->GetChildText('title', ''); + + $iRefresh = intval($this->oDOMNode->GetChildText('refresh', '0')); + + $this->bAutoReload = $iRefresh > 0; + $this->iAutoReloadSec = $iRefresh; + + $oDashletsNode = $this->oDOMNode->GetUniqueElement('pos_dashlets'); + $oDashletList = $oDashletsNode->getElementsByTagName('pos_dashlet'); + foreach ($oDashletList as $oPosDashletNode) { + $aGridDashlet = []; + $aGridDashlet['position_x'] = intval($oPosDashletNode->GetChildText('position_x', '0')); + $aGridDashlet['position_y'] = intval($oPosDashletNode->GetChildText('position_y', '0')); + $aGridDashlet['width'] = intval($oPosDashletNode->GetChildText('width', '2')); + $aGridDashlet['height'] = intval($oPosDashletNode->GetChildText('height', '1')); + $oDashletNode = $oPosDashletNode->GetUniqueElement('dashlet'); + $sId = $oPosDashletNode->getAttribute('id'); + $oDashlet = $this->InitDashletFromDOMNode($oDashletNode); + $oDashlet->SetID($sId); + $aGridDashlet['dashlet'] = $oDashlet; + $this->aGridDashlets[] = $aGridDashlet; + } + } + + /** + * @param DesignElement $oDomNode + * + * @return mixed + */ + protected function InitDashletFromDOMNode($oDomNode) + { + $sId = $oDomNode->getAttribute('id'); + + $sDashletType = $oDomNode->getAttribute('xsi:type'); + + // Test if dashlet can be instantiated, otherwise (uninstalled, broken, ...) we display a placeholder + $sClass = static::GetDashletClassFromType($sDashletType); + /** @var \Dashlet $oNewDashlet */ + $oNewDashlet = $this->oDashletFactory->CreateDashlet($sClass, $sId); + $oNewDashlet->FromDOMNode($oDomNode); + + return $oNewDashlet; + } + + /** + * @param array $aItem1 + * @param array $aItem2 + * + * @return int + */ + public static function SortOnRank($aItem1, $aItem2) + { + return ($aItem1['rank'] > $aItem2['rank']) ? +1 : -1; + } + + /** + * Error handler to turn XML loading warnings into exceptions + * + * @param $errno + * @param $errstr + * @param $errfile + * @param $errline + * + * @return bool + * @throws \DOMException + */ + public static function ErrorHandler($errno, $errstr, $errfile, $errline) + { + if ($errno == E_WARNING && (substr_count($errstr, "DOMDocument::loadXML()") > 0)) { + throw new DOMException($errstr); + } else { + return false; + } + } + + /** + * @return string + * @throws \Exception + */ + public function ToXml() + { + $oMainNode = $this->CreateEmptyDashboard(); + + $this->ToDOMNode($oMainNode); + + $sXml = $oMainNode->ownerDocument->saveXML(); + + return $sXml; + } + + /** + * @return DesignElement + * @throws \DOMException + */ + public function CreateEmptyDashboard(): DesignElement + { + $oDoc = new DesignDocument(); + $oDoc->formatOutput = true; // indent (must be loaded with option LIBXML_NOBLANKS) + $oDoc->preserveWhiteSpace = true; // otherwise the formatOutput option would have no effect + + /** @var DesignElement $oMainNode */ + $oMainNode = $oDoc->createElement('dashboard'); + $oMainNode->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $oDoc->appendChild($oMainNode); + + return $oMainNode; + } + + /** + * @param \DOMElement $oDefinition + */ + public function ToDOMNode($oDefinition) + { + /** @var \DOMDocument $oDoc */ + $oDoc = $oDefinition->ownerDocument; + + $oNode = $oDoc->createElement('layout', $this->sLayoutClass); + $oDefinition->appendChild($oNode); + + $oNode = $oDoc->createElement('title', $this->sTitle); + $oDefinition->appendChild($oNode); + + $oAutoReloadNode = $oDoc->createElement('auto_reload'); + $oDefinition->appendChild($oAutoReloadNode); + $oNode = $oDoc->createElement('enabled', $this->bAutoReload ? 'true' : 'false'); + $oAutoReloadNode->appendChild($oNode); + $oNode = $oDoc->createElement('interval', $this->iAutoReloadSec); + $oAutoReloadNode->appendChild($oNode); + + $oCellsNode = $oDoc->createElement('cells'); + $oDefinition->appendChild($oCellsNode); + + $iCellRank = 0; + foreach ($this->aCells as $aCell) { + $oCellNode = $oDoc->createElement('cell'); + $oCellNode->setAttribute('id', $iCellRank); + $oCellsNode->appendChild($oCellNode); + $oCellRank = $oDoc->createElement('rank', $iCellRank); + $oCellNode->appendChild($oCellRank); + $iCellRank++; + + $iDashletRank = 0; + $oDashletsNode = $oDoc->createElement('dashlets'); + $oCellNode->appendChild($oDashletsNode); + /** @var \Dashlet $oDashlet */ + foreach ($aCell as $oDashlet) { + $oNode = $oDoc->createElement('dashlet'); + $oDashletsNode->appendChild($oNode); + $oNode->setAttribute('id', $oDashlet->GetID()); + $oNode->setAttribute('xsi:type', $oDashlet->GetDashletType()); + $oDashletRank = $oDoc->createElement('rank', $iDashletRank); + $oNode->appendChild($oDashletRank); + $iDashletRank++; + $oDashlet->ToDOMNode($oNode); + } + } + } + + /** + * @param array $aParams + */ + public function FromParams($aParams) + { + $this->sLayoutClass = $aParams['layout_class']; + if (!is_subclass_of($this->sLayoutClass, DashboardLayout::class)) { + throw new InvalidParameterException('Invalid parameter layout_class "'.$aParams['layout_class'].'"'); + } + $this->sTitle = $aParams['title']; + $this->bAutoReload = $aParams['auto_reload'] == 'true'; + $this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int)$aParams['auto_reload_sec']); + + foreach ($aParams['cells'] as $aCell) { + $aCellDashlets = []; + foreach ($aCell as $aDashletParams) { + $sDashletClass = $aDashletParams['dashlet_class']; + $sId = $aDashletParams['dashlet_id']; + /** @var \Dashlet $oNewDashlet */ + $oNewDashlet = $this->oDashletFactory->CreateDashlet($sDashletClass, $sId); + if (isset($aDashletParams['dashlet_type'])) { + $oNewDashlet->SetDashletType($aDashletParams['dashlet_type']); + } + $oForm = $oNewDashlet->GetForm(); + $oForm->SetParamsContainer($sId); + $oForm->SetPrefix(''); + $aValues = $oForm->ReadParams(); + $oNewDashlet->FromParams($aValues); + $aCellDashlets[] = $oNewDashlet; + } + $this->aCells[] = $aCellDashlets; + } + + } + + public function Save() + { + } + + public function PersistDashboard(string $sXml): bool + { + return true; + } + + /** + * @return mixed + */ + public function GetId() + { + return $this->sId; + } + + /** + * Return a sanitize ID for usages in XML/HTML attributes + * + * @return string + * @since 2.7.0 + */ + public function GetSanitizedId() + { + return utils::Sanitize($this->GetId(), '', 'element_identifier'); + } + + /** + * @return string + */ + public function GetLayout() + { + return $this->sLayoutClass; + } + + /** + * @param string $sLayoutClass + */ + public function SetLayout($sLayoutClass) + { + $this->sLayoutClass = $sLayoutClass; + } + + /** + * @return string + */ + public function GetTitle() + { + return $this->sTitle; + } + + /** + * @param string $sTitle + */ + public function SetTitle($sTitle) + { + $this->sTitle = $sTitle; + } + + /** + * @return bool + */ + public function GetAutoReload() + { + return $this->bAutoReload; + } + + /** + * @param bool $bAutoReload + */ + public function SetAutoReload($bAutoReload) + { + $this->bAutoReload = $bAutoReload; + } + + /** + * @return float|int + */ + public function GetAutoReloadInterval() + { + return $this->iAutoReloadSec; + } + + /** + * @param bool $iAutoReloadSec + */ + public function SetAutoReloadInterval($iAutoReloadSec) + { + $this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int)$iAutoReloadSec); + } + + /** + * @param \Dashlet $oDashlet + */ + public function AddDashlet($oDashlet) + { + $sId = $this->GetNewDashletId(); + $oDashlet->SetId($sId); + $this->aCells[] = [$oDashlet]; + } + + /** + * @param WebPage $oPage * + * @param array $aExtraParams + * + * @throws \ReflectionException + * @throws \Exception + */ + public function RenderProperties($oPage, $aExtraParams = []) + { + // menu to pick a layout and edit other properties of the dashboard + $oPage->add('
'.Dict::S('UI:DashboardEdit:Properties').'
'); + $sUrl = utils::GetAbsoluteUrlAppRoot(); + + $oPage->add('
'.Dict::S('UI:DashboardEdit:Layout').'
'); + $oPage->add('
'); + foreach (get_declared_classes() as $sLayoutClass) { + if (is_subclass_of($sLayoutClass, 'DashboardLayout')) { + $oReflection = new ReflectionClass($sLayoutClass); + if (!$oReflection->isAbstract()) { + $aCallSpec = [$sLayoutClass, 'GetInfo']; + $aInfo = call_user_func($aCallSpec); + $sChecked = ($this->sLayoutClass == $sLayoutClass) ? 'checked' : ''; + $oPage->add(''); // title="" on either the img or the label does nothing ! + } + } + } + $oPage->add('
'); + + $oForm = new DesignerForm(); + + $oField = new DesignerHiddenField('dashboard_id', '', $this->sId); + $oForm->AddField($oField); + + $oField = new DesignerTextField('dashboard_title', Dict::S('UI:DashboardEdit:DashboardTitle'), $this->sTitle); + $oForm->AddField($oField); + + $oField = new DesignerBooleanField('auto_reload', Dict::S('UI:DashboardEdit:AutoReload'), $this->bAutoReload); + $oForm->AddField($oField); + + $oField = new DesignerIntegerField('auto_reload_sec', Dict::S('UI:DashboardEdit:AutoReloadSec'), $this->iAutoReloadSec); + $oField->SetBoundaries(MetaModel::GetConfig()->Get('min_reload_interval'), null); // no upper limit + $oForm->AddField($oField); + + $this->SetFormParams($oForm, $aExtraParams); + $oForm->RenderAsPropertySheet($oPage, false, '.itop-dashboard'); + + $oPage->add('
'); + + $sRateTitle = addslashes(Dict::Format('UI:DashboardEdit:AutoReloadSec+', MetaModel::GetConfig()->Get('min_reload_interval'))); + $oPage->add_ready_script( + <<GetId(), utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER); + + /** @var \DashboardLayout $oLayout */ + $oLayout = new $this->sLayoutClass(); + + foreach ($this->aCells as $iCellIdx => $aDashlets) { + foreach ($aDashlets as $oDashlet) { + $aDashletCoordinates = $oLayout->GetDashletCoordinates($iCellIdx); + $this->PrepareDashletForRendering($oDashlet, $aDashletCoordinates, $aExtraParams); + } + } + + if (count($this->aCells) > 0) { + $aDashlets = $this->aCells; + } else { + $aDashlets = $this->aGridDashlets; + } + + $oDashboard = $oLayout->Render($oPage, $aDashlets, $bEditMode, $aExtraParams); + $oPage->AddUiBlock($oDashboard); + + $bFromDasboardPage = isset($aExtraParams['from_dashboard_page']) ? isset($aExtraParams['from_dashboard_page']) : false; + + if ($bFromDasboardPage) { + $sTitleForHTML = utils::HtmlEntities(Dict::S($this->sTitle)); + $sHtml = "
{$sTitleForHTML}
"; + if ($oPage instanceof iTopWebPage) { + $oTopBar = $oPage->GetTopBarLayout(); + $oToolbar = ToolbarUIBlockFactory::MakeStandard(); + $oTopBar->SetToolbar($oToolbar); + + $oToolbar->AddHtml($sHtml); + } else { + $oPage->add_script( + <<').html("$sTitleForHTML").text()); +JS + ); + } + } else { + $oDashboard->SetTitle(Dict::S($this->sTitle)); + } + + if (!$bEditMode) { + $oPage->LinkScriptFromAppRoot('js/dashlet.js'); + $oPage->LinkScriptFromAppRoot('js/dashboard.js'); + } + + return $oDashboard; + } + + /** + * @param WebPage $oPage + * + * @throws \ReflectionException + * @throws \Exception + */ + public function RenderDashletsSelection(WebPage $oPage) + { + // Toolbox/palette to drag and drop dashlets + $oPage->add('
'.Dict::S('UI:DashboardEdit:Dashlets').'
'); + $sUrl = utils::GetAbsoluteUrlAppRoot(); + + $oPage->add('
'); + $aAvailableDashlets = $this->GetAvailableDashlets(); + foreach ($aAvailableDashlets as $sDashletClass => $aInfo) { + $oPage->add(''); + } + $oPage->add('
'); + + $oPage->add('
'); + $oPage->add_ready_script("$('.dashlet_icon').draggable({cursor: 'move', helper: 'clone', appendTo: 'body', zIndex: 10000, revert:'invalid'});"); + } + + /** + * @param WebPage $oPage + * @param array $aExtraParams + */ + public function RenderDashletsProperties(WebPage $oPage, $aExtraParams = []) + { + // Toolbox/palette to edit the properties of each dashlet + $oPage->add('
'.Dict::S('UI:DashboardEdit:DashletProperties').'
'); + + /** @var \DashboardLayoutMultiCol $oLayout */ + $oLayout = new $this->sLayoutClass(); + + $oPage->add('
'); + foreach ($this->aCells as $iCellIdx => $aCell) { + /** @var \Dashlet $oDashlet */ + foreach ($aCell as $oDashlet) { + if ($oDashlet->IsVisible()) { + $oPage->add(''); + } + } + } + $oPage->add('
'); + + $oPage->add('
'); + } + + /** + * Return an array of dashlets available for selection. + * + * @return array + * @throws \Combodo\iTop\Application\Dashlet\DashletException + * @throws \DOMFormatException + */ + protected function GetAvailableDashlets(): array + { + return DashletService::GetInstance()->GetAvailableDashlets(); + } + + /** + * @return int|mixed + */ + protected function GetNewDashletId() + { + $iNewId = 0; + foreach ($this->aCells as $aDashlets) { + /** @var \Dashlet $oDashlet */ + foreach ($aDashlets as $oDashlet) { + $iNewId = max($iNewId, (int)$oDashlet->GetID()); + } + } + + return $iNewId + 1; + } + + /** + * Prepare dashlet for rendering (eg. change its ID or another processing). + * Meant to be overloaded. + * + * @param \Dashlet $oDashlet + * @param array $aCoordinates + * @param array $aExtraParams + * + * @return void + */ + abstract protected function PrepareDashletForRendering(Dashlet $oDashlet, $aCoordinates, $aExtraParams = []); + + /** + * @param \DesignerForm $oForm + * @param array $aExtraParams + * + * @return mixed + */ + abstract protected function SetFormParams($oForm, $aExtraParams = []); + + /** + * @param string $sType + * @param \ModelFactory|null $oFactory + * + * @return string + */ + public static function GetDashletClassFromType($sType, $oFactory = null) + { + if (is_subclass_of($sType, 'Dashlet')) { + return $sType; + } + + return 'DashletUnknown'; + } + + /** + * N°2634: we must have a unique id per dashlet! + * To avoid collision with other dashlets with the same ID we prefix it with row/cell id + * Collisions typically happen with extensions. + * + * @param boolean $bIsCustomized + * @param string $sDashboardDivId + * @param int $iRow + * @param int $iCol + * @param string $sDashletOrigId + * + * @return string + * + * @since 2.7.0 N°2735 + */ + public static function GetDashletUniqueId($bIsCustomized, $sDashboardDivId, $iRow, $iCol, $sDashletOrigId) + { + if (strpos($sDashletOrigId, '_ID_row') !== false) { + return $sDashletOrigId; + } + + $sDashletId = $sDashboardDivId."_ID_row".$iRow."_col".$iCol."_".$sDashletOrigId; + if ($bIsCustomized) { + $sDashletId = 'CUSTOM_'.$sDashletId; + } + + return $sDashletId; + } +} diff --git a/sources/Application/Dashboard/dashboard.class.inc.php b/sources/Application/Dashboard/RuntimeDashboard.php similarity index 57% rename from sources/Application/Dashboard/dashboard.class.inc.php rename to sources/Application/Dashboard/RuntimeDashboard.php index 6929b4c8d..125cc6c54 100644 --- a/sources/Application/Dashboard/dashboard.class.inc.php +++ b/sources/Application/Dashboard/RuntimeDashboard.php @@ -1,768 +1,20 @@ sTitle = ''; - $this->sLayoutClass = 'DashboardLayoutOneCol'; - $this->bAutoReload = false; - $this->iAutoReloadSec = MetaModel::GetConfig()->GetStandardReloadInterval(); - $this->aCells = []; - $this->oDOMNode = null; - $this->sId = $sId; - $this->oDashletFactory = DashletFactory::GetInstance(); - } - - /** - * @param string $sXml - * - * @throws \Exception - */ - public function FromXml($sXml) - { - $this->aCells = []; // reset the content of the dashboard - set_error_handler(['Dashboard', 'ErrorHandler']); - $oDoc = new PropertyTypeDesign(); - $oDoc->loadXML($sXml); - restore_error_handler(); - $this->FromDOMDocument($oDoc); - } - - /** - * @param \DOMDocument $oDoc - */ - public function FromDOMDocument(DesignDocument $oDoc) - { - $this->oDOMNode = $oDoc->getElementsByTagName('dashboard')->item(0); - - if ($this->oDOMNode->getElementsByTagName('cells')->count() === 0) { - $this->FromDOMDocumentV2($oDoc); - return; - } - - if ($oLayoutNode = $this->oDOMNode->getElementsByTagName('layout')->item(0)) { - $this->sLayoutClass = $oLayoutNode->textContent; - } else { - $this->sLayoutClass = 'DashboardLayoutOneCol'; - } - - if ($oTitleNode = $this->oDOMNode->getElementsByTagName('title')->item(0)) { - $this->sTitle = $oTitleNode->textContent; - } else { - $this->sTitle = ''; - } - - $this->bAutoReload = false; - $this->iAutoReloadSec = MetaModel::GetConfig()->GetStandardReloadInterval(); - if ($oAutoReloadNode = $this->oDOMNode->getElementsByTagName('auto_reload')->item(0)) { - if ($oAutoReloadEnabled = $oAutoReloadNode->getElementsByTagName('enabled')->item(0)) { - $this->bAutoReload = ($oAutoReloadEnabled->textContent == 'true'); - } - if ($oAutoReloadInterval = $oAutoReloadNode->getElementsByTagName('interval')->item(0)) { - $this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int)$oAutoReloadInterval->textContent); - } - } - - if ($oCellsNode = $this->oDOMNode->getElementsByTagName('cells')->item(0)) { - $oCellsList = $oCellsNode->getElementsByTagName('cell'); - $aCellOrder = []; - $iCellRank = 0; - /** @var \DOMElement $oCellNode */ - foreach ($oCellsList as $oCellNode) { - $oCellRank = $oCellNode->getElementsByTagName('rank')->item(0); - if ($oCellRank) { - $iCellRank = (float)$oCellRank->textContent; - } - $oDashletsNode = $oCellNode->getElementsByTagName('dashlets')->item(0); - { - $oDashletList = $oDashletsNode->getElementsByTagName('dashlet'); - $iRank = 0; - $aDashletOrder = []; - /** @var \DOMElement $oDomNode */ - foreach ($oDashletList as $oDomNode) { - $oRank = $oDomNode->getElementsByTagName('rank')->item(0); - if ($oRank) { - $iRank = (float)$oRank->textContent; - } - - $oNewDashlet = $this->InitDashletFromDOMNode($oDomNode); - $aDashletOrder[] = ['rank' => $iRank, 'dashlet' => $oNewDashlet]; - } - usort($aDashletOrder, [get_class($this), 'SortOnRank']); - $aDashletList = []; - foreach ($aDashletOrder as $aItem) { - $aDashletList[] = $aItem['dashlet']; - } - $aCellOrder[] = ['rank' => $iCellRank, 'dashlets' => $aDashletList]; - } - } - usort($aCellOrder, [get_class($this), 'SortOnRank']); - foreach ($aCellOrder as $aItem) { - $this->aCells[] = $aItem['dashlets']; - } - } else { - $this->aCells = []; - } - } - - /** - * @param \DOMDocument $oDoc - */ - public function FromDOMDocumentV2(DesignDocument $oDoc) - { - $this->oDOMNode = $oDoc->getElementsByTagName('dashboard')->item(0); - - $this->sLayoutClass = DashboardLayoutGrid::class; - $this->sTitle = $this->oDOMNode->GetChildText('title', ''); - - $iRefresh = intval($this->oDOMNode->GetChildText('refresh', '0')); - - $this->bAutoReload = $iRefresh > 0; - $this->iAutoReloadSec = $iRefresh; - - $oDashletsNode = $this->oDOMNode->GetUniqueElement('pos_dashlets'); - $oDashletList = $oDashletsNode->getElementsByTagName('pos_dashlet'); - foreach ($oDashletList as $oPosDashletNode) { - $aGridDashlet = []; - $aGridDashlet['position_x'] = intval($oPosDashletNode->GetChildText('position_x', '0')); - $aGridDashlet['position_y'] = intval($oPosDashletNode->GetChildText('position_y', '0')); - $aGridDashlet['width'] = intval($oPosDashletNode->GetChildText('width', '2')); - $aGridDashlet['height'] = intval($oPosDashletNode->GetChildText('height', '1')); - $oDashletNode = $oPosDashletNode->GetUniqueElement('dashlet'); - $sId = $oPosDashletNode->getAttribute('id'); - $oDashlet = $this->InitDashletFromDOMNode($oDashletNode); - $oDashlet->SetID($sId); - $aGridDashlet['dashlet'] = $oDashlet; - $this->aGridDashlets[] = $aGridDashlet; - } - } - - /** - * @param DesignElement $oDomNode - * - * @return mixed - */ - protected function InitDashletFromDOMNode($oDomNode) - { - $sId = $oDomNode->getAttribute('id'); - - $sDashletType = $oDomNode->getAttribute('xsi:type'); - - // Test if dashlet can be instantiated, otherwise (uninstalled, broken, ...) we display a placeholder - $sClass = static::GetDashletClassFromType($sDashletType); - /** @var \Dashlet $oNewDashlet */ - $oNewDashlet = $this->oDashletFactory->CreateDashlet($sClass, $sId); - $oNewDashlet->FromDOMNode($oDomNode); - - return $oNewDashlet; - } - - /** - * @param array $aItem1 - * @param array $aItem2 - * - * @return int - */ - public static function SortOnRank($aItem1, $aItem2) - { - return ($aItem1['rank'] > $aItem2['rank']) ? +1 : -1; - } - - /** - * Error handler to turn XML loading warnings into exceptions - * - * @param $errno - * @param $errstr - * @param $errfile - * @param $errline - * - * @return bool - * @throws \DOMException - */ - public static function ErrorHandler($errno, $errstr, $errfile, $errline) - { - if ($errno == E_WARNING && (substr_count($errstr, "DOMDocument::loadXML()") > 0)) { - throw new DOMException($errstr); - } else { - return false; - } - } - - /** - * @return string - * @throws \Exception - */ - public function ToXml() - { - $oMainNode = $this->CreateEmptyDashboard(); - - $this->ToDOMNode($oMainNode); - - $sXml = $oMainNode->ownerDocument->saveXML(); - - return $sXml; - } - - /** - * @return DesignElement - * @throws \DOMException - */ - public function CreateEmptyDashboard(): DesignElement - { - $oDoc = new DesignDocument(); - $oDoc->formatOutput = true; // indent (must be loaded with option LIBXML_NOBLANKS) - $oDoc->preserveWhiteSpace = true; // otherwise the formatOutput option would have no effect - - /** @var DesignElement $oMainNode */ - $oMainNode = $oDoc->createElement('dashboard'); - $oMainNode->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); - $oDoc->appendChild($oMainNode); - - return $oMainNode; - } - - /** - * @param \DOMElement $oDefinition - */ - public function ToDOMNode($oDefinition) - { - /** @var \DOMDocument $oDoc */ - $oDoc = $oDefinition->ownerDocument; - - $oNode = $oDoc->createElement('layout', $this->sLayoutClass); - $oDefinition->appendChild($oNode); - - $oNode = $oDoc->createElement('title', $this->sTitle); - $oDefinition->appendChild($oNode); - - $oAutoReloadNode = $oDoc->createElement('auto_reload'); - $oDefinition->appendChild($oAutoReloadNode); - $oNode = $oDoc->createElement('enabled', $this->bAutoReload ? 'true' : 'false'); - $oAutoReloadNode->appendChild($oNode); - $oNode = $oDoc->createElement('interval', $this->iAutoReloadSec); - $oAutoReloadNode->appendChild($oNode); - - $oCellsNode = $oDoc->createElement('cells'); - $oDefinition->appendChild($oCellsNode); - - $iCellRank = 0; - foreach ($this->aCells as $aCell) { - $oCellNode = $oDoc->createElement('cell'); - $oCellNode->setAttribute('id', $iCellRank); - $oCellsNode->appendChild($oCellNode); - $oCellRank = $oDoc->createElement('rank', $iCellRank); - $oCellNode->appendChild($oCellRank); - $iCellRank++; - - $iDashletRank = 0; - $oDashletsNode = $oDoc->createElement('dashlets'); - $oCellNode->appendChild($oDashletsNode); - /** @var \Dashlet $oDashlet */ - foreach ($aCell as $oDashlet) { - $oNode = $oDoc->createElement('dashlet'); - $oDashletsNode->appendChild($oNode); - $oNode->setAttribute('id', $oDashlet->GetID()); - $oNode->setAttribute('xsi:type', $oDashlet->GetDashletType()); - $oDashletRank = $oDoc->createElement('rank', $iDashletRank); - $oNode->appendChild($oDashletRank); - $iDashletRank++; - $oDashlet->ToDOMNode($oNode); - } - } - } - - /** - * @param array $aParams - */ - public function FromParams($aParams) - { - $this->sLayoutClass = $aParams['layout_class']; - if (!is_subclass_of($this->sLayoutClass, DashboardLayout::class)) { - throw new InvalidParameterException('Invalid parameter layout_class "'.$aParams['layout_class'].'"'); - } - $this->sTitle = $aParams['title']; - $this->bAutoReload = $aParams['auto_reload'] == 'true'; - $this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int)$aParams['auto_reload_sec']); - - foreach ($aParams['cells'] as $aCell) { - $aCellDashlets = []; - foreach ($aCell as $aDashletParams) { - $sDashletClass = $aDashletParams['dashlet_class']; - $sId = $aDashletParams['dashlet_id']; - /** @var \Dashlet $oNewDashlet */ - $oNewDashlet = $this->oDashletFactory->CreateDashlet($sDashletClass, $sId); - if (isset($aDashletParams['dashlet_type'])) { - $oNewDashlet->SetDashletType($aDashletParams['dashlet_type']); - } - $oForm = $oNewDashlet->GetForm(); - $oForm->SetParamsContainer($sId); - $oForm->SetPrefix(''); - $aValues = $oForm->ReadParams(); - $oNewDashlet->FromParams($aValues); - $aCellDashlets[] = $oNewDashlet; - } - $this->aCells[] = $aCellDashlets; - } - - } - - public function Save() - { - } - - public function PersistDashboard(string $sXml): bool - { - return true; - } - - /** - * @return mixed - */ - public function GetId() - { - return $this->sId; - } - - /** - * Return a sanitize ID for usages in XML/HTML attributes - * - * @return string - * @since 2.7.0 - */ - public function GetSanitizedId() - { - return utils::Sanitize($this->GetId(), '', 'element_identifier'); - } - - /** - * @return string - */ - public function GetLayout() - { - return $this->sLayoutClass; - } - - /** - * @param string $sLayoutClass - */ - public function SetLayout($sLayoutClass) - { - $this->sLayoutClass = $sLayoutClass; - } - - /** - * @return string - */ - public function GetTitle() - { - return $this->sTitle; - } - - /** - * @param string $sTitle - */ - public function SetTitle($sTitle) - { - $this->sTitle = $sTitle; - } - - /** - * @return bool - */ - public function GetAutoReload() - { - return $this->bAutoReload; - } - - /** - * @param bool $bAutoReload - */ - public function SetAutoReload($bAutoReload) - { - $this->bAutoReload = $bAutoReload; - } - - /** - * @return float|int - */ - public function GetAutoReloadInterval() - { - return $this->iAutoReloadSec; - } - - /** - * @param bool $iAutoReloadSec - */ - public function SetAutoReloadInterval($iAutoReloadSec) - { - $this->iAutoReloadSec = max(MetaModel::GetConfig()->Get('min_reload_interval'), (int)$iAutoReloadSec); - } - - /** - * @param \Dashlet $oDashlet - */ - public function AddDashlet($oDashlet) - { - $sId = $this->GetNewDashletId(); - $oDashlet->SetId($sId); - $this->aCells[] = [$oDashlet]; - } - - /** - * @param WebPage $oPage * - * @param array $aExtraParams - * - * @throws \ReflectionException - * @throws \Exception - */ - public function RenderProperties($oPage, $aExtraParams = []) - { - // menu to pick a layout and edit other properties of the dashboard - $oPage->add('
'.Dict::S('UI:DashboardEdit:Properties').'
'); - $sUrl = utils::GetAbsoluteUrlAppRoot(); - - $oPage->add('
'.Dict::S('UI:DashboardEdit:Layout').'
'); - $oPage->add('
'); - foreach (get_declared_classes() as $sLayoutClass) { - if (is_subclass_of($sLayoutClass, 'DashboardLayout')) { - $oReflection = new ReflectionClass($sLayoutClass); - if (!$oReflection->isAbstract()) { - $aCallSpec = [$sLayoutClass, 'GetInfo']; - $aInfo = call_user_func($aCallSpec); - $sChecked = ($this->sLayoutClass == $sLayoutClass) ? 'checked' : ''; - $oPage->add(''); // title="" on either the img or the label does nothing ! - } - } - } - $oPage->add('
'); - - $oForm = new DesignerForm(); - - $oField = new DesignerHiddenField('dashboard_id', '', $this->sId); - $oForm->AddField($oField); - - $oField = new DesignerTextField('dashboard_title', Dict::S('UI:DashboardEdit:DashboardTitle'), $this->sTitle); - $oForm->AddField($oField); - - $oField = new DesignerBooleanField('auto_reload', Dict::S('UI:DashboardEdit:AutoReload'), $this->bAutoReload); - $oForm->AddField($oField); - - $oField = new DesignerIntegerField('auto_reload_sec', Dict::S('UI:DashboardEdit:AutoReloadSec'), $this->iAutoReloadSec); - $oField->SetBoundaries(MetaModel::GetConfig()->Get('min_reload_interval'), null); // no upper limit - $oForm->AddField($oField); - - $this->SetFormParams($oForm, $aExtraParams); - $oForm->RenderAsPropertySheet($oPage, false, '.itop-dashboard'); - - $oPage->add('
'); - - $sRateTitle = addslashes(Dict::Format('UI:DashboardEdit:AutoReloadSec+', MetaModel::GetConfig()->Get('min_reload_interval'))); - $oPage->add_ready_script( - <<GetId(), utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER); - - /** @var \DashboardLayout $oLayout */ - $oLayout = new $this->sLayoutClass(); - - foreach ($this->aCells as $iCellIdx => $aDashlets) { - foreach ($aDashlets as $oDashlet) { - $aDashletCoordinates = $oLayout->GetDashletCoordinates($iCellIdx); - $this->PrepareDashletForRendering($oDashlet, $aDashletCoordinates, $aExtraParams); - } - } - - if (count($this->aCells) > 0) { - $aDashlets = $this->aCells; - } else { - $aDashlets = $this->aGridDashlets; - } - - $oDashboard = $oLayout->Render($oPage, $aDashlets, $bEditMode, $aExtraParams); - $oPage->AddUiBlock($oDashboard); - - $bFromDasboardPage = isset($aExtraParams['from_dashboard_page']) ? isset($aExtraParams['from_dashboard_page']) : false; - - if ($bFromDasboardPage) { - $sTitleForHTML = utils::HtmlEntities(Dict::S($this->sTitle)); - $sHtml = "
{$sTitleForHTML}
"; - if ($oPage instanceof iTopWebPage) { - $oTopBar = $oPage->GetTopBarLayout(); - $oToolbar = ToolbarUIBlockFactory::MakeStandard(); - $oTopBar->SetToolbar($oToolbar); - - $oToolbar->AddHtml($sHtml); - } else { - $oPage->add_script( - <<').html("$sTitleForHTML").text()); -JS - ); - } - } else { - $oDashboard->SetTitle(Dict::S($this->sTitle)); - } - - if (!$bEditMode) { - $oPage->LinkScriptFromAppRoot('js/dashlet.js'); - $oPage->LinkScriptFromAppRoot('js/dashboard.js'); - } - - return $oDashboard; - } - - /** - * @param WebPage $oPage - * - * @throws \ReflectionException - * @throws \Exception - */ - public function RenderDashletsSelection(WebPage $oPage) - { - // Toolbox/palette to drag and drop dashlets - $oPage->add('
'.Dict::S('UI:DashboardEdit:Dashlets').'
'); - $sUrl = utils::GetAbsoluteUrlAppRoot(); - - $oPage->add('
'); - $aAvailableDashlets = $this->GetAvailableDashlets(); - foreach ($aAvailableDashlets as $sDashletClass => $aInfo) { - $oPage->add(''); - } - $oPage->add('
'); - - $oPage->add('
'); - $oPage->add_ready_script("$('.dashlet_icon').draggable({cursor: 'move', helper: 'clone', appendTo: 'body', zIndex: 10000, revert:'invalid'});"); - } - - /** - * @param WebPage $oPage - * @param array $aExtraParams - */ - public function RenderDashletsProperties(WebPage $oPage, $aExtraParams = []) - { - // Toolbox/palette to edit the properties of each dashlet - $oPage->add('
'.Dict::S('UI:DashboardEdit:DashletProperties').'
'); - - /** @var \DashboardLayoutMultiCol $oLayout */ - $oLayout = new $this->sLayoutClass(); - - $oPage->add('
'); - foreach ($this->aCells as $iCellIdx => $aCell) { - /** @var \Dashlet $oDashlet */ - foreach ($aCell as $oDashlet) { - if ($oDashlet->IsVisible()) { - $oPage->add(''); - } - } - } - $oPage->add('
'); - - $oPage->add('
'); - } - - /** - * Return an array of dashlets available for selection. - * - * @return array - * @throws \Combodo\iTop\Application\Dashlet\DashletException - * @throws \DOMFormatException - */ - protected function GetAvailableDashlets(): array - { - return DashletService::GetInstance()->GetAvailableDashlets(); - } - - /** - * @return int|mixed - */ - protected function GetNewDashletId() - { - $iNewId = 0; - foreach ($this->aCells as $aDashlets) { - /** @var \Dashlet $oDashlet */ - foreach ($aDashlets as $oDashlet) { - $iNewId = max($iNewId, (int)$oDashlet->GetID()); - } - } - - return $iNewId + 1; - } - - /** - * Prepare dashlet for rendering (eg. change its ID or another processing). - * Meant to be overloaded. - * - * @param \Dashlet $oDashlet - * @param array $aCoordinates - * @param array $aExtraParams - * - * @return void - */ - abstract protected function PrepareDashletForRendering(Dashlet $oDashlet, $aCoordinates, $aExtraParams = []); - - /** - * @param \DesignerForm $oForm - * @param array $aExtraParams - * - * @return mixed - */ - abstract protected function SetFormParams($oForm, $aExtraParams = []); - - /** - * @param string $sType - * @param \ModelFactory|null $oFactory - * - * @return string - */ - public static function GetDashletClassFromType($sType, $oFactory = null) - { - if (is_subclass_of($sType, 'Dashlet')) { - return $sType; - } - - return 'DashletUnknown'; - } - - /** - * N°2634: we must have a unique id per dashlet! - * To avoid collision with other dashlets with the same ID we prefix it with row/cell id - * Collisions typically happen with extensions. - * - * @param boolean $bIsCustomized - * @param string $sDashboardDivId - * @param int $iRow - * @param int $iCol - * @param string $sDashletOrigId - * - * @return string - * - * @since 2.7.0 N°2735 - */ - public static function GetDashletUniqueId($bIsCustomized, $sDashboardDivId, $iRow, $iCol, $sDashletOrigId) - { - if (strpos($sDashletOrigId, '_ID_row') !== false) { - return $sDashletOrigId; - } - - $sDashletId = $sDashboardDivId."_ID_row".$iRow."_col".$iCol."_".$sDashletOrigId; - if ($bIsCustomized) { - $sDashletId = 'CUSTOM_'.$sDashletId; - } - - return $sDashletId; - } -} - /** * Class RuntimeDashboard */ diff --git a/sources/alias.php b/sources/alias.php index 725504fa5..6e509a5aa 100644 --- a/sources/alias.php +++ b/sources/alias.php @@ -97,7 +97,8 @@ class_alias(\Combodo\iTop\Core\AttributeDefinition\AttributeURL::class, 'Attribu class_alias(\Combodo\iTop\Core\AttributeDefinition\iAttributeNoGroupBy::class, 'iAttributeNoGroupBy'); class_alias(\Combodo\iTop\Core\AttributeDefinition\MissingColumnException::class, 'MissingColumnException'); -// Dashlets +// Dashboards/Dashlets +class_alias(\Combodo\iTop\Application\Dashboard\Dashboard::class, 'Dashboard'); class_alias(\Combodo\iTop\Application\Dashlet\Dashlet::class, 'Dashlet'); class_alias(\Combodo\iTop\Application\Dashlet\Base\DashletBadge::class, 'DashletBadge'); class_alias(\Combodo\iTop\Application\Dashlet\Base\DashletGroupBy::class, 'DashletGroupBy');