sTitle = ''; $this->sLayoutClass = 'DashboardLayoutOneCol'; $this->bAutoReload = false; $this->iAutoReloadSec = MetaModel::GetConfig()->GetStandardReloadInterval(); $this->aCells = []; $this->oDOMNode = null; $this->sId = $sId; $this->oDashletFactory = DashletFactory::GetInstance(); $this->oXMLSerializer = MetaModel::GetService('XMLSerializer'); $this->oDashletService = MetaModel::GetService('DashletService'); } /** * @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 string $sXml * * @throws \Exception */ public function ToXML(): string { $oDomNode = $this->CreateEmptyDashboard(); $aModelData = $this->ToModelData(); $this->oXMLSerializer->Serialize($aModelData, $oDomNode, 'DashboardGrid', 'Dashboard'); return $oDomNode->ownerDocument->saveXML(); } /** * @param \DOMDocument $oDoc */ public function FromDOMDocument(DesignDocument $oDoc) { $this->oDOMNode = $oDoc->getElementsByTagName('dashboard')->item(0); if ($this->oDOMNode->getElementsByTagName('cells')->count() === 0) { $this->FromDOMDocumentV2($this->oDOMNode); 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, [$this, 'SortOnRank']); $aDashletList = []; foreach ($aDashletOrder as $aItem) { $aDashletList[] = $aItem['dashlet']; } $aCellOrder[] = ['rank' => $iCellRank, 'dashlets' => $aDashletList]; } } usort($aCellOrder, [$this, 'SortOnRank']); foreach ($aCellOrder as $aItem) { $this->aCells[] = $aItem['dashlets']; } } else { $this->aCells = []; } switch ($this->sLayoutClass) { case 'DashboardLayoutTwoCols': $iNbCols = 2; break; case 'DashboardLayoutThreeCols': $iNbCols = 3; break; case 'DashboardLayoutOneCol': default: $iNbCols = 1; break; } $iCellIdx = 0; $iNbRows = ceil(count($this->aCells) / $iNbCols); // GRID LAYOUT: Global positioning $iGridCurrentX = 0; $iGridCurrentY = 0; $iGridColWidth = (int)(12 / $iNbCols); for ($iRows = 0; $iRows < $iNbRows; $iRows++) { // GRID LAYOUT: Store the maximum column Y in this row $iGridMaxColY = -1; for ($iCols = 0; $iCols < $iNbCols; $iCols++) { // GRID LAYOUT: Column positioning $iGridCurrentColX = 0; $iGridCurrentColY = 0; $iGridMaxHeightDashlet = -1; if (array_key_exists($iCellIdx, $this->aCells)) { $aDashlets = $this->aCells[$iCellIdx]; if (count($aDashlets) > 0) { /** @var \Dashlet $oDashlet */ foreach ($aDashlets as $oDashlet) { if ($oDashlet::IsVisible()) { $sDashletClass = $oDashlet->GetDashletType(); $aDashletsInfo = $this->oDashletService->GetDashletDefinition($sDashletClass); // GRID LAYOUT: Set position relative to grid $iPositionX = $iGridCurrentX + $iGridCurrentColX; $iPositionY = $iGridCurrentY + $iGridCurrentColY; $iWidth = array_key_exists('preferred_width', $aDashletsInfo) ? $aDashletsInfo['preferred_width'] : 1; // GRID LAYOUT: Limit dashlet width to fit column width if ($iWidth > $iGridColWidth) { $iWidth = $iGridColWidth; } $iHeight = array_key_exists('preferred_height', $aDashletsInfo) ? $aDashletsInfo['preferred_height'] : 1; // GRID LAYOUT: Store max height of dashlets in this current column if ($iHeight > $iGridMaxHeightDashlet) { $iGridMaxHeightDashlet = $iHeight; } // GRID LAYOUT: Ensure that dashlet fits in the current row of the column if ($iGridCurrentColX + $iWidth > $iGridColWidth) { $iPositionX = $iGridCurrentX; $iPositionY++; } $aPosDashlet = []; $aPosDashlet['dashlet'] = $oDashlet; $aPosDashlet['position_x'] = $iPositionX; $aPosDashlet['position_y'] = $iPositionY; $aPosDashlet['width'] = $iWidth; $aPosDashlet['height'] = $iHeight; $this->aGridDashlets[] = $aPosDashlet; // GRID LAYOUT: Update column cursor $iGridCurrentColX += $iWidth; if ($iGridCurrentColX >= $iGridColWidth) { $iGridCurrentColX = 0; $iGridCurrentColY += $iGridMaxHeightDashlet; $iGridMaxHeightDashlet = -1; } } } } } $iCellIdx++; // GRID LAYOUT: Store max y in this current row if ($iGridCurrentColY > $iGridMaxColY) { $iGridMaxColY = $iGridCurrentColY; } // GRID LAYOUT: Next column $iGridCurrentX += $iGridColWidth; } // GRID LAYOUT: Next Row $iGridCurrentY += ($iGridMaxColY + 1); $iGridCurrentX = 0; } $this->aCells = []; $this->sLayoutClass = DashboardLayoutGrid::class; } /** * @param \DOMDocument $oDOMNode */ public function FromDOMDocumentV2(DesignElement $oDOMNode) { $aDashboardValues = $this->oXMLSerializer->Deserialize($oDOMNode, 'DashboardGrid', 'Dashboard'); $this->FromModelData($aDashboardValues); } /** * @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 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 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; } 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 WebPage $oPage * @param bool $bEditMode * @param array $aExtraParams * @param bool $bCanEdit * * @return \Combodo\iTop\Application\UI\Base\Layout\Dashboard\DashboardLayout * @throws \Exception */ public function Render($oPage, $bEditMode = false, $aExtraParams = [], $bCanEdit = true) { $aExtraParams['dashboard_div_id'] = utils::Sanitize($aExtraParams['dashboard_div_id'] ?? null, $this->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); $oDashboard->SetFile(utils::LocalPath($this->GetDefinitionFile())); $oPage->AddUiBlock($oDashboard); $bFromDashboardPage = isset($aExtraParams['from_dashboard_page']) ? isset($aExtraParams['from_dashboard_page']) : false; if ($bFromDashboardPage) { $sTitleForHTML = utils::HtmlEntities(Dict::S($this->sTitle)); $sHtml = "
"; if ($oPage instanceof iTopWebPage) { $oTopBar = $oPage->GetTopBarLayout(); $oToolbar = ToolbarUIBlockFactory::MakeStandard(); $oTopBar->SetToolbar($oToolbar); $oToolbar->AddHtml($sHtml); } else { $oPage->add_script( <<