namespace Combodo\iTop\Application\Dashlet; use Combodo\iTop\Application\UI\Base\Component\Dashlet\DashletContainer; use Combodo\iTop\Application\UI\Base\iUIBlock; use Combodo\iTop\Application\UI\Base\UIBlock; use Combodo\iTop\Application\WebPage\WebPage; use Combodo\iTop\DesignDocument; use Combodo\iTop\DesignElement; use Combodo\iTop\PropertyType\Serializer\XMLNormalizer; use DesignerForm; use DesignerHiddenField; use Dict; use DOMException; use DOMNode; use Exception; use ModelReflection; use OQLException; use UnknownClassOqlException; use utils; /** * Base class for all 'dashlets' (i.e. widgets to be inserted into a dashboard) * * @copyright Copyright (C) 2010-2024 Combodo SAS * @license http://opensource.org/licenses/AGPL-3.0 */ abstract class Dashlet { /** @var string */ public const APPUSERPREFERENCES_PREFIX = 'Dashlet'; protected $oModelReflection; protected $sId; protected $bRedrawNeeded; protected $bFormRedrawNeeded; protected $aProperties; // array of {property => value} protected $aCSSClasses; protected $sDashletType; protected array $aDefinition; /** * Dashlet constructor. * * @param \ModelReflection $oModelReflection * @param string $sId * @param string|null $sDashletType */ public function __construct(ModelReflection $oModelReflection, $sId) { $this->oModelReflection = $oModelReflection; $this->sId = $sId; $this->bRedrawNeeded = true; // By default: redraw each time a property changes $this->bFormRedrawNeeded = false; // By default: no need to redraw the form (independent fields) $this->aProperties = []; // By default: there is no property $this->aCSSClasses = ['ibo-dashlet']; $this->sDashletType = get_class($this); } /** * Assuming that a property has the type of its default value, set in the constructor * * @param string $sProperty * @param string $sValue * * @return mixed */ public function Str2Prop($sProperty, $sValue) { $refValue = $this->aProperties[$sProperty]; $sRefType = gettype($refValue); if (gettype($sValue) == $sRefType) { // Do not change anything in that case! $ret = $sValue; } elseif ($sRefType == 'boolean') { $ret = ($sValue == 'true'); } elseif ($sRefType == 'array') { $ret = explode(',', $sValue); } elseif (is_array($sValue)) { $ret = $sValue; } else { $ret = $sValue; settype($ret, $sRefType); } return $ret; } /** * @param mixed $value * * @return string */ public function Prop2Str($value) { $sType = gettype($value); if ($sType == 'boolean') { $sRet = $value ? 'true' : 'false'; } elseif ($sType == 'array') { $sRet = implode(',', $value); } else { $sRet = (string)$value; } return $sRet; } protected function OnUpdate() { } /** */ public function FromDOMNode(DesignElement $oDOMNode) { foreach ($this->aProperties as $sProperty => $value) { $oPropNode = $oDOMNode->getElementsByTagName($sProperty)->item(0); if ($oPropNode != null) { $this->aProperties[$sProperty] = $this->PropertyFromDOMNode($oPropNode, $sProperty); } } $this->OnUpdate(); } /** * @param \DOMElement $oDOMNode */ public function ToDOMNode($oDOMNode) { foreach ($this->aProperties as $sProperty => $value) { $oPropNode = $oDOMNode->ownerDocument->createElement($sProperty); $oDOMNode->appendChild($oPropNode); $this->PropertyToDOMNode($oPropNode, $sProperty, $value); } } /** * @param \DOMElement $oDOMNode * @param string $sProperty * * @return mixed */ protected function PropertyFromDOMNode($oDOMNode, $sProperty) { $res = $this->Str2Prop($sProperty, $oDOMNode->textContent); return $res; } /** * @param \DOMElement $oDOMNode * @param string $sProperty * @param mixed $value */ protected function PropertyToDOMNode($oDOMNode, $sProperty, $value) { $sXmlValue = $this->Prop2Str($value); $oTextNode = $oDOMNode->ownerDocument->createTextNode($sXmlValue); $oDOMNode->appendChild($oTextNode); } /** * @param string $sXml * * @throws \DOMException */ public function FromXml($sXml) { $oDomDoc = new DesignDocument('1.0', 'UTF-8'); libxml_clear_errors(); $oDomDoc->loadXml($sXml); $aErrors = libxml_get_errors(); if (count($aErrors) > 0) { throw new DOMException("Malformed XML"); } /** @var DesignElement $oDOMNode */ $oDOMNode = $oDomDoc->firstChild; $this->FromDOMNode($oDOMNode); } /** * @param array $aParams */ public function FromParams($aParams) { foreach ($this->aProperties as $sProperty => $value) { if (array_key_exists($sProperty, $aParams)) { $this->aProperties[$sProperty] = $aParams[$sProperty]; } } $this->OnUpdate(); } public function FromDenormalizedParams(array $aDenormalizedParams) { $this->aProperties = XMLNormalizer::GetInstance()->Normalize($aDenormalizedParams, $this->sDashletType, 'Dashlet'); $this->OnUpdate(); } /** * @return array Rel. path to the app. root of the JS files required by the dashlet * @since 3.0.0 */ public function GetJSFilesRelPaths(): array { return []; } /** * @return array Rel. path to the app. root of the CSS files required by the dashlet * @since 3.0.0 */ public function GetCSSFilesRelPaths(): array { return []; } /** * @param WebPage $oPage * @param bool $bEditMode * @param bool $bEnclosingDiv * @param array $aExtraParams */ public function DoRender($oPage, $bEditMode = false, $bEnclosingDiv = true, $aExtraParams = []): UIBlock { $sId = $this->GetID(); if ($bEnclosingDiv) { if ($bEditMode) { $oDashletContainer = new DashletContainer("dashlet_{$sId}"); } else { $oDashletContainer = new DashletContainer(); } $oDashletContainer->AddCSSClasses($this->aCSSClasses); } else { $oDashletContainer = new DashletContainer(); $oDashletContainer->AddCSSClasses($this->aCSSClasses); } $oDashletContainer->AddMultipleJsFilesRelPaths($this->GetJSFilesRelPaths()); $oDashletContainer->AddMultipleCssFilesRelPaths($this->GetCSSFilesRelPaths()); try { if (get_class($this->oModelReflection) == 'ModelReflectionRuntime') { $oBlock = $this->Render($oPage, $bEditMode, $aExtraParams); } else { $oBlock = $this->RenderNoData($oPage, $bEditMode, $aExtraParams); } $oDashletContainer->AddSubBlock($oBlock); } catch (UnknownClassOqlException $e) { // Maybe the class is part of a non-installed module, fail silently // Except in Edit mode if ($bEditMode) { $oDashletContainer->AddCSSClass("dashlet-content"); $oDashletContainer->AddHtml('
'.utils::HtmlEntities($e->GetUserFriendlyDescription()).'
'); } catch (Exception $e) { $oDashletContainer->AddCSSClass("dashlet-content"); $oDashletContainer->AddHtml(''.$e->getMessage().'
'); } if ($bEditMode) { $sClass = $this->sDashletType; $sType = $this->sDashletType; $oPage->add_ready_script( <<