diff --git a/application/dashboard.class.inc.php b/application/dashboard.class.inc.php
index f9f3c4b44..ee5358497 100644
--- a/application/dashboard.class.inc.php
+++ b/application/dashboard.class.inc.php
@@ -115,15 +115,13 @@ abstract class Dashboard
$aDashletOrder = array();
foreach($oDashletList as $oDomNode)
{
- $sDashletClass = $oDomNode->getAttribute('xsi:type');
$oRank = $oDomNode->getElementsByTagName('rank')->item(0);
if ($oRank)
{
$iRank = (float)$oRank->textContent;
}
- $sId = $oDomNode->getAttribute('id');
- $oNewDashlet = new $sDashletClass($this->oMetaModel, $sId);
- $oNewDashlet->FromDOMNode($oDomNode);
+
+ $oNewDashlet = $this->InitDashletFromDOMNode($oDomNode);
$aDashletOrder[] = array('rank' => $iRank, 'dashlet' => $oNewDashlet);
}
usort($aDashletOrder, array(get_class($this), 'SortOnRank'));
@@ -147,6 +145,22 @@ abstract class Dashboard
}
}
+ protected function InitDashletFromDOMNode($oDomNode)
+ {
+ $sId = $oDomNode->getAttribute('id');
+ $sClass = $oDomNode->getAttribute('xsi:type');
+
+ // Test if dashlet can be instanciated, otherwise (uninstalled, broken, ...) we display a placeholder
+ if(!class_exists($sClass))
+ {
+ $sClass = 'DashletUnknown';
+ }
+ $oNewDashlet = new $sClass($this->oMetaModel, $sId);
+ $oNewDashlet->FromDOMNode($oDomNode);
+
+ return $oNewDashlet;
+ }
+
static function SortOnRank($aItem1, $aItem2)
{
return ($aItem1['rank'] > $aItem2['rank']) ? +1 : -1;
@@ -414,24 +428,11 @@ EOF
$oPage->add('
');
$sUrl = utils::GetAbsoluteUrlAppRoot();
- $oPage->add('
');
- foreach( get_declared_classes() as $sDashletClass)
+ $oPage->add('
');
+ $aAvailableDashlets = $this->GetAvailableDashlets();
+ foreach($aAvailableDashlets as $sDashletClass => $aInfo)
{
- if (is_subclass_of($sDashletClass, 'Dashlet'))
- {
- $oReflection = new ReflectionClass($sDashletClass);
- if (!$oReflection->isAbstract())
- {
- $aCallSpec = array($sDashletClass, 'IsVisible');
- $bVisible = call_user_func($aCallSpec);
- if ($bVisible)
- {
- $aCallSpec = array($sDashletClass, 'GetInfo');
- $aInfo = call_user_func($aCallSpec);
- $oPage->add('

');
- }
- }
- }
+ $oPage->add('

');
}
$oPage->add('
');
@@ -465,6 +466,38 @@ EOF
$oPage->add('
');
}
+
+ /**
+ * Return an array of dashlets available for selection.
+ *
+ * @return array
+ */
+ protected function GetAvailableDashlets()
+ {
+ $aDashlets = array();
+
+ foreach( get_declared_classes() as $sDashletClass)
+ {
+ // DashletUnknown is not among the selection as it is just a fallback for dashlets that can't instanciated.
+ if ( is_subclass_of($sDashletClass, 'Dashlet') && !in_array($sDashletClass, array('DashletUnknown', 'DashletProxy')) )
+ {
+ $oReflection = new ReflectionClass($sDashletClass);
+ if (!$oReflection->isAbstract())
+ {
+ $aCallSpec = array($sDashletClass, 'IsVisible');
+ $bVisible = call_user_func($aCallSpec);
+ if ($bVisible)
+ {
+ $aCallSpec = array($sDashletClass, 'GetInfo');
+ $aInfo = call_user_func($aCallSpec);
+ $aDashlets[$sDashletClass] = $aInfo;
+ }
+ }
+ }
+ }
+
+ return $aDashlets;
+ }
protected function GetNewDashletId()
{
diff --git a/application/dashlet.class.inc.php b/application/dashlet.class.inc.php
index 505d46e93..2c21628aa 100644
--- a/application/dashlet.class.inc.php
+++ b/application/dashlet.class.inc.php
@@ -1,5 +1,5 @@
value}
protected $aCSSClasses;
-
+
public function __construct(ModelReflection $oModelReflection, $sId)
{
$this->oModelReflection = $oModelReflection;
@@ -135,7 +135,7 @@ abstract class Dashlet
$oDomDoc->loadXml($sXml);
$this->FromDOMNode($oDomDoc->firstChild);
}
-
+
public function FromParams($aParams)
{
foreach ($this->aProperties as $sProperty => $value)
@@ -147,7 +147,7 @@ abstract class Dashlet
}
$this->OnUpdate();
}
-
+
public function DoRender($oPage, $bEditMode = false, $bEnclosingDiv = true, $aExtraParams = array())
{
$sCSSClasses = implode(' ', $this->aCSSClasses);
@@ -170,7 +170,7 @@ abstract class Dashlet
$oPage->add_ready_script("$('#dashlet_".$sId."').addClass('$sCSSClass');");
}
}
-
+
try
{
if (get_class($this->oModelReflection) == 'ModelReflectionRuntime')
@@ -205,12 +205,12 @@ abstract class Dashlet
$oPage->p($e->getMessage());
$oPage->add('
');
}
-
+
if ($bEnclosingDiv)
{
$oPage->add('');
}
-
+
if ($bEditMode)
{
$sClass = get_class($this);
@@ -221,17 +221,17 @@ EOF
);
}
}
-
+
public function SetID($sId)
{
$this->sId = $sId;
}
-
+
public function GetID()
{
return $this->sId;
}
-
+
abstract public function Render($oPage, $bEditMode = false, $aExtraParams = array());
/* Rendering without the real data */
@@ -239,12 +239,12 @@ EOF
{
$this->Render($oPage, $bEditMode, $aExtraParams);
}
-
+
abstract public function GetPropertiesFields(DesignerForm $oForm);
-
+
public function ToXml(DOMNode $oContainerNode)
{
-
+
}
public function Update($aValues, $aUpdatedFields)
@@ -259,18 +259,18 @@ EOF
$this->OnUpdate();
return $this;
}
-
+
public function IsRedrawNeeded()
{
return $this->bRedrawNeeded;
}
-
+
public function IsFormRedrawNeeded()
{
return $this->bFormRedrawNeeded;
}
-
+
static public function GetInfo()
{
return array(
@@ -279,47 +279,262 @@ EOF
'description' => '',
);
}
-
+
public function GetForm()
{
$oForm = new DesignerForm();
$oForm->SetPrefix("dashlet_". $this->GetID());
$oForm->SetParamsContainer('params');
-
+
$this->GetPropertiesFields($oForm);
-
+
$oDashletClassField = new DesignerHiddenField('dashlet_class', '', get_class($this));
$oForm->AddField($oDashletClassField);
-
+
$oDashletIdField = new DesignerHiddenField('dashlet_id', '', $this->GetID());
$oForm->AddField($oDashletIdField);
-
+
return $oForm;
}
-
+
static public function IsVisible()
{
return true;
}
-
+
static public function CanCreateFromOQL()
{
return false;
}
-
+
public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL = null)
{
// Default: do nothing since it's not supported
}
}
+/**
+ * Class DashletUnknown
+ *
+ * Used as a fallback in iTop for unknown dashlet classes.
+ *
+ * @since 2.5
+ */
+class DashletUnknown extends Dashlet
+{
+ static protected $aClassList = null;
+
+ protected $sOriginalDashletClass;
+ protected $sOriginalDashletXML;
+
+ public function __construct($oModelReflection, $sId)
+ {
+ parent::__construct($oModelReflection, $sId);
+ $this->sOriginalDashletClass = 'Unknown';
+ $this->sOriginalDashletXML = '';
+ $this->aCSSClasses[] = 'dashlet-unknown';
+ }
+
+ public function GetOriginalDashletClass()
+ {
+ return $this->sOriginalDashletClass;
+ }
+
+ public function SetOriginalDashletClass($sOriginalDashletClass)
+ {
+ $this->sOriginalDashletClass = $sOriginalDashletClass;
+ }
+
+ public function FromDOMNode($oDOMNode)
+ {
+ // Parent won't do anything as there is no property declared
+ parent::FromDOMNode($oDOMNode);
+
+ // Original dashlet
+ // - Class
+ if($oDOMNode->hasAttribute('xsi:type'))
+ {
+ $this->sOriginalDashletClass = $oDOMNode->getAttribute('xsi:type');
+ }
+
+ // Build properties from XML
+ $this->sOriginalDashletXML = "";
+ foreach($oDOMNode->childNodes as $oDOMChildNode)
+ {
+ if($oDOMChildNode instanceof DOMElement)
+ {
+ $sProperty = $oDOMChildNode->tagName;
+
+ // For all properties but "rank" as it is handle by the dashboard.
+ if($sProperty !== 'rank')
+ {
+ // We need to initialize the property before setting it, otherwise it will guessed as NULL and not used.
+ $this->aProperties[$sProperty] = '';
+ $this->aProperties[$sProperty] = $this->PropertyFromDOMNode($oDOMChildNode, $sProperty);
+
+ // And build the original XML
+ $this->sOriginalDashletXML .= $oDOMChildNode->ownerDocument->saveXML($oDOMChildNode)."\n";
+ }
+ }
+ }
+
+ $this->OnUpdate();
+ }
+
+ public function FromParams($aParams)
+ {
+ // For unknown dashlet, parameters are not parsed but passed as a raw xml
+ if(array_key_exists('xml', $aParams))
+ {
+ // A namspace must be present for the "xsi:type" attribute, otherwise a warning will be thrown.
+ $sXML = 'This dashlet is not supposed to be rendered as it is just a proxy for third-party widgets.
');
+ }
+
+ public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
+ {
+ // TODO
+ $oPage->add('');
if ($sHtmlTitle != '')
{
@@ -668,21 +883,21 @@ abstract class DashletGroupBy extends Dashlet
$aValues = array();
switch($this->sFunction)
{
- case 'hour':
- $aValues = array(8, 9, 15, 18);
- break;
+ case 'hour':
+ $aValues = array(8, 9, 15, 18);
+ break;
- case 'month':
- $aValues = array('2013 '.Dict::S('Month-11'), '2013 '.Dict::S('Month-12'), '2014 '.Dict::S('Month-01'), '2014 '.Dict::S('Month-02'), '2014 '.Dict::S('Month-03'));
- break;
+ case 'month':
+ $aValues = array('2013 '.Dict::S('Month-11'), '2013 '.Dict::S('Month-12'), '2014 '.Dict::S('Month-01'), '2014 '.Dict::S('Month-02'), '2014 '.Dict::S('Month-03'));
+ break;
- case 'day_of_week':
- $aValues = array(Dict::S('DayOfWeek-Monday'), Dict::S('DayOfWeek-Wednesday'), Dict::S('DayOfWeek-Thursday'), Dict::S('DayOfWeek-Friday'));
- break;
+ case 'day_of_week':
+ $aValues = array(Dict::S('DayOfWeek-Monday'), Dict::S('DayOfWeek-Wednesday'), Dict::S('DayOfWeek-Thursday'), Dict::S('DayOfWeek-Friday'));
+ break;
- case 'day_of_month':
- $aValues = array(Dict::S('Month-03'). ' 30', Dict::S('Month-03'). ' 31', Dict::S('Month-04'). ' 01', Dict::S('Month-04'). ' 02', Dict::S('Month-04'). ' 03');
- break;
+ case 'day_of_month':
+ $aValues = array(Dict::S('Month-03'). ' 30', Dict::S('Month-03'). ' 31', Dict::S('Month-04'). ' 01', Dict::S('Month-04'). ' 02', Dict::S('Month-04'). ' 03');
+ break;
}
foreach ($aValues as $sValue)
{
@@ -735,7 +950,7 @@ abstract class DashletGroupBy extends Dashlet
if ($sAttType == 'AttributeExternalField') continue;
if (is_subclass_of($sAttType, 'AttributeExternalField')) continue;
if ($sAttType == 'AttributeOneWayPassword') continue;
-
+
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
$aGroupBy[$sAttCode] = $sLabel;
@@ -764,7 +979,7 @@ abstract class DashletGroupBy extends Dashlet
{
// Group by field: build the list of possible values (attribute codes + ...)
$aGroupBy = $this->GetGroupByOptions($this->aProperties['query']);
-
+
$oField = new DesignerComboField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), $this->aProperties['group_by']);
$oField->SetMandatory();
$oField->SetAllowedValues($aGroupBy);
@@ -781,13 +996,13 @@ abstract class DashletGroupBy extends Dashlet
'bars' => Dict::S('UI:DashletGroupByBars:Label'),
'table' => Dict::S('UI:DashletGroupByTable:Label'),
);
-
+
$oField = new DesignerComboField('style', Dict::S('UI:DashletGroupBy:Prop-Style'), $this->aProperties['style']);
$oField->SetMandatory();
$oField->SetAllowedValues($aStyles);
$oForm->AddField($oField);
}
-
+
public function Update($aValues, $aUpdatedFields)
{
if (in_array('query', $aUpdatedFields))
@@ -797,11 +1012,11 @@ abstract class DashletGroupBy extends Dashlet
$sCurrQuery = $aValues['query'];
$oCurrSearch = $this->oModelReflection->GetQuery($sCurrQuery);
$sCurrClass = $oCurrSearch->GetClass();
-
+
$sPrevQuery = $this->aProperties['query'];
$oPrevSearch = $this->oModelReflection->GetQuery($sPrevQuery);
$sPrevClass = $oPrevSearch->GetClass();
-
+
if ($sCurrClass != $sPrevClass)
{
$this->bFormRedrawNeeded = true;
@@ -815,23 +1030,23 @@ abstract class DashletGroupBy extends Dashlet
}
}
$oDashlet = parent::Update($aValues, $aUpdatedFields);
-
+
if (in_array('style', $aUpdatedFields))
{
switch($aValues['style'])
{
// Style changed, mutate to the specified type of chart
case 'pie':
- $oDashlet = new DashletGroupByPie($this->oModelReflection, $this->sId);
- break;
-
+ $oDashlet = new DashletGroupByPie($this->oModelReflection, $this->sId);
+ break;
+
case 'bars':
- $oDashlet = new DashletGroupByBars($this->oModelReflection, $this->sId);
- break;
-
+ $oDashlet = new DashletGroupByBars($this->oModelReflection, $this->sId);
+ break;
+
case 'table':
- $oDashlet = new DashletGroupByTable($this->oModelReflection, $this->sId);
- break;
+ $oDashlet = new DashletGroupByTable($this->oModelReflection, $this->sId);
+ break;
}
$oDashlet->FromParams($aValues);
$oDashlet->bRedrawNeeded = true;
@@ -849,12 +1064,12 @@ abstract class DashletGroupBy extends Dashlet
'description' => 'Grouped objects dashlet (abstract)',
);
}
-
+
static public function CanCreateFromOQL()
{
return true;
}
-
+
public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL = null)
{
$oField = new DesignerTextField('title', Dict::S('UI:DashletGroupBy:Prop-Title'), '');
@@ -863,7 +1078,7 @@ abstract class DashletGroupBy extends Dashlet
$oField = new DesignerHiddenField('query', Dict::S('UI:DashletGroupBy:Prop-Query'), $sOQL);
$oField->SetMandatory();
$oForm->AddField($oField);
-
+
if (!is_null($sOQL))
{
$oField = new DesignerComboField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), null);
@@ -892,7 +1107,7 @@ class DashletGroupByPie extends DashletGroupBy
parent::__construct($oModelReflection, $sId);
$this->aProperties['style'] = 'pie';
}
-
+
static public function GetInfo()
{
return array(
@@ -907,10 +1122,10 @@ class DashletGroupByPie extends DashletGroupBy
$sTitle = $this->aProperties['title'];
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
-
+
$HTMLsTitle = ($sTitle != '') ? '
'.htmlentities($sTitle, ENT_QUOTES, 'UTF-8').'
' : '';
$oPage->add("
");
-
+
$aDisplayValues = $this->MakeSimulatedData();
$aColumns = array();
@@ -955,7 +1170,7 @@ class DashletGroupByBars extends DashletGroupBy
parent::__construct($oModelReflection, $sId);
$this->aProperties['style'] = 'bars';
}
-
+
static public function GetInfo()
{
return array(
@@ -970,10 +1185,10 @@ class DashletGroupByBars extends DashletGroupBy
$sTitle = $this->aProperties['title'];
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
-
+
$HTMLsTitle = ($sTitle != '') ? '
'.htmlentities($sTitle, ENT_QUOTES, 'UTF-8').'
' : '';
$oPage->add("
");
-
+
$aDisplayValues = $this->MakeSimulatedData();
$aNames = array();
@@ -982,7 +1197,7 @@ class DashletGroupByBars extends DashletGroupBy
$aNames[$idx] = $aValue['label'];
}
$sJSNames = json_encode($aNames);
-
+
$sJson = json_encode($aDisplayValues);
$sJSCount = json_encode(Dict::S('UI:GroupBy:Count'));
$oPage->add_ready_script(
@@ -1044,7 +1259,7 @@ class DashletGroupByTable extends DashletGroupBy
parent::__construct($oModelReflection, $sId);
$this->aProperties['style'] = 'table';
}
-
+
static public function GetInfo()
{
return array(
@@ -1104,7 +1319,7 @@ class DashletHeaderStatic extends Dashlet
$oIconSelect = $this->oModelReflection->GetIconSelectionField('icon');
$this->aProperties['icon'] = $oIconSelect->GetDefaultValue('Contact');
}
-
+
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
$sTitle = $this->aProperties['title'];
@@ -1127,11 +1342,11 @@ class DashletHeaderStatic extends Dashlet
{
$oField = new DesignerTextField('title', Dict::S('UI:DashletHeaderStatic:Prop-Title'), $this->aProperties['title']);
$oForm->AddField($oField);
-
+
$oField = $this->oModelReflection->GetIconSelectionField('icon', Dict::S('UI:DashletHeaderStatic:Prop-Icon'), $this->aProperties['icon']);
$oForm->AddField($oField);
}
-
+
protected function PropertyFromDOMNode($oDOMNode, $sProperty)
{
if ($sProperty == 'icon')
@@ -1299,8 +1514,8 @@ class DashletHeaderDynamic extends Dashlet
foreach ($aValues as $sValue)
{
$sValueLabel = $this->oModelReflection->GetValueLabel($sClass, $sGroupBy, $sValue);
- $oPage->add('
'.$sValueLabel.' | ');
- }
+ $oPage->add('
'.$sValueLabel.' | ');
+ }
$oPage->add('');
$oPage->add('
');
foreach ($aValues as $sValue)
@@ -1379,7 +1594,7 @@ class DashletHeaderDynamic extends Dashlet
}
$oForm->AddField($oField);
}
-
+
public function Update($aValues, $aUpdatedFields)
{
if (in_array('query', $aUpdatedFields))
@@ -1389,11 +1604,11 @@ class DashletHeaderDynamic extends Dashlet
$sCurrQuery = $aValues['query'];
$oCurrSearch = $this->oModelReflection->GetQuery($sCurrQuery);
$sCurrClass = $oCurrSearch->GetClass();
-
+
$sPrevQuery = $this->aProperties['query'];
$oPrevSearch = $this->oModelReflection->GetQuery($sPrevQuery);
$sPrevClass = $oPrevSearch->GetClass();
-
+
if ($sCurrClass != $sPrevClass)
{
$this->bFormRedrawNeeded = true;
@@ -1414,7 +1629,7 @@ class DashletHeaderDynamic extends Dashlet
}
return parent::Update($aValues, $aUpdatedFields);
}
-
+
protected function PropertyFromDOMNode($oDOMNode, $sProperty)
{
if ($sProperty == 'icon')
@@ -1513,7 +1728,7 @@ class DashletBadge extends Dashlet
//
$aClasses = array();
foreach($this->oModelReflection->GetClasses('bizmodel', true /*exclude links*/) as $sClass)
- {
+ {
$aClasses[$sClass] = $this->oModelReflection->GetName($sClass);
}
asort($aClasses);
@@ -1534,10 +1749,10 @@ class DashletBadge extends Dashlet
$oField = new DesignerIconSelectionField('class', Dict::S('UI:DashletBadge:Prop-Class'), $this->aProperties['class']);
$oField->SetAllowedValues(self::$aClassList);
-
+
$oForm->AddField($oField);
}
-
+
static public function GetInfo()
{
return array(
@@ -1547,4 +1762,3 @@ class DashletBadge extends Dashlet
);
}
}
-?>
diff --git a/css/light-grey.css b/css/light-grey.css
index 53c550244..1c3555356 100644
--- a/css/light-grey.css
+++ b/css/light-grey.css
@@ -1599,6 +1599,14 @@ td.prop_icon {
.dashlet-content .display_block {
text-align: left;
}
+.dashlet-unknown .dashlet-content {
+ padding: 8px;
+ background-color: #f2f2f2;
+ text-align: center;
+}
+.dashlet-unknown .dashlet-content .dashlet-ukn-text {
+ margin-top: 10px;
+}
.prop_apply .ui-icon-alert {
display: none;
}
diff --git a/css/light-grey.scss b/css/light-grey.scss
index 6773f0b1b..aaf3e6658 100644
--- a/css/light-grey.scss
+++ b/css/light-grey.scss
@@ -1769,6 +1769,17 @@ td.prop_icon {
.dashlet-content .display_block {
text-align:left;
}
+.dashlet-unknown {
+ .dashlet-content {
+ padding: 8px;
+ background-color: #F2F2F2;
+ text-align: center;
+
+ .dashlet-ukn-text {
+ margin-top: 10px;
+ }
+ }
+}
.prop_apply .ui-icon-alert {
display: none;
}
diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php
index 24b91f9e1..4ecf30635 100644
--- a/dictionaries/en.dictionary.itop.ui.php
+++ b/dictionaries/en.dictionary.itop.ui.php
@@ -1164,7 +1164,11 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:DashletUnknown:RenderText:View' => 'Unable to render this dashlet.',
'UI:DashletUnknown:RenderText:Edit' => 'Unable to render this dashlet (class "%1$s"). Check with your administrator if it is still available.',
'UI:DashletUnknown:RenderNoDataText:Edit' => 'No preview available for this dashlet (class "%1$s").',
- 'UI:DashletUnknown:Prop-XMLConfiguration' => 'Configuration as XML',
+ 'UI:DashletUnknown:Prop-XMLConfiguration' => 'Configuration (shown as raw XML)',
+
+ 'UI:DashletProxy:Label' => 'Proxy',
+ 'UI:DashletProxy:Description' => 'Proxy dashlet',
+ 'UI:DashletProxy:Prop-XMLConfiguration' => 'Configuration (shown as raw XML)',
'UI:DashletPlainText:Label' => 'Text',
'UI:DashletPlainText:Description' => 'Plain text (no formatting)',