diff --git a/application/dashboard.class.inc.php b/application/dashboard.class.inc.php
new file mode 100644
index 000000000..4eb8ac215
--- /dev/null
+++ b/application/dashboard.class.inc.php
@@ -0,0 +1,216 @@
+sLayoutClass = null;
+ $this->aDashlets = array();
+ $this->oDOMNode = null;
+ $this->sId = $sId;
+ }
+
+ public function FromXml($sXml)
+ {
+ $oDoc = new DOMDocument();
+ $oDoc->loadXML($sXml);
+ $this->oDOMNode = $oDoc->getElementsByTagName('dashboard')->item(0);
+
+ $oLayoutNode = $this->oDOMNode->getElementsByTagName('layout')->item(0);
+ $this->sLayoutClass = $oLayoutNode->textContent;
+
+ $oTitleNode = $this->oDOMNode->getElementsByTagName('title')->item(0);
+ $this->sTitle = $oTitleNode->textContent;
+
+ $oDashletsNode = $this->oDOMNode->getElementsByTagName('dashlets')->item(0);
+ $oDashletList = $oDashletsNode->getElementsByTagName('dashlet');
+ foreach($oDashletList as $oDomNode)
+ {
+ $sDashletClass = $oDomNode->getAttribute('xsi:type');
+ $oNewDashlet = new $sDashletClass;
+ $oNewDashlet->FromDOMNode($oDomNode);
+ $this->aDashlets[] = $oNewDashlet;
+ }
+ }
+
+ public function FromParams($aParams)
+ {
+
+ }
+
+ public function Save()
+ {
+
+ }
+
+ public function GetLayout()
+ {
+ return $this->sLayoutClass;
+ }
+
+ public function SetLayout($sLayoutClass)
+ {
+ $this->sLayoutClass = $sLayoutClass;
+ }
+
+ public function GetTitle()
+ {
+ return $this->sTitle;
+ }
+
+ public function SetTitle($sTitle)
+ {
+ $this->sTitle = $sTitle;
+ }
+
+ public function AddDashlet()
+ {
+ }
+
+ public function Render($oPage, $bEditMode = false, $aExtraParams = array())
+ {
+ $oPage->add('
'.$this->sTitle.'
');
+ $oLayout = new $this->sLayoutClass;
+ $oLayout->Render($oPage, $this->aDashlets, $bEditMode, $aExtraParams);
+ }
+
+ public function RenderProperties($oPage)
+ {
+ // menu to pick a layout and edit other properties of the dashboard
+ $oPage->add('
');
+ foreach( get_declared_classes() as $sLayoutClass)
+ {
+ if (is_subclass_of($sLayoutClass, 'DashboardLayout'))
+ {
+ $oReflection = new ReflectionClass($sLayoutClass);
+ if (!$oReflection->isAbstract())
+ {
+ $aInfo = $sLayoutClass::GetInfo();
+ $oPage->add(''); // title="" on either the img or the label does nothing !
+ }
+ }
+ }
+ $oPage->add('
');
+
+ $oPage->add('
');
+ $oPage->add_ready_script("$('#select_layout').buttonset();");
+ }
+
+ public function RenderDashletsSelection($oPage)
+ {
+ // Toolbox/palette to drag and drop dashlets
+ $oPage->add('
');
+ }
+}
+
+class DashboardLayoutOneCol extends DashboardLayoutMultiCol
+{
+ public function __construct()
+ {
+ parent::__construct();
+ $this->iNbCols = 1;
+ }
+ static public function GetInfo()
+ {
+ return array(
+ 'label' => 'One Column',
+ 'icon' => 'images/layout_1col.png',
+ 'description' => '',
+ );
+ }
+}
+
+class DashboardLayoutTwoCols extends DashboardLayoutMultiCol
+{
+ public function __construct()
+ {
+ parent::__construct();
+ $this->iNbCols = 2;
+ }
+ static public function GetInfo()
+ {
+ return array(
+ 'label' => 'Two Columns',
+ 'icon' => 'images/layout_2col.png',
+ 'description' => '',
+ );
+ }
+}
+
+class DashboardLayoutThreeCols extends DashboardLayoutMultiCol
+{
+ public function __construct()
+ {
+ parent::__construct();
+ $this->iNbCols = 3;
+ }
+ static public function GetInfo()
+ {
+ return array(
+ 'label' => 'Two Columns',
+ 'icon' => 'images/layout_3col.png',
+ 'description' => '',
+ );
+ }
+}
\ No newline at end of file
diff --git a/application/dashlet.class.inc.php b/application/dashlet.class.inc.php
new file mode 100644
index 000000000..2d4594842
--- /dev/null
+++ b/application/dashlet.class.inc.php
@@ -0,0 +1,228 @@
+ '',
+ 'icon' => '',
+ 'description' => '',
+ );
+ }
+}
+
+class DashletHelloWorld extends Dashlet
+{
+ public function __construct()
+ {
+
+ }
+
+ public function FromDOMNode($oDOMNode)
+ {
+
+ }
+
+ public function FromXml($sXml)
+ {
+
+ }
+
+ public function FromParams($aParams)
+ {
+
+ }
+
+ public function Render($oPage, $bEditMode = false, $aExtraParams = array())
+ {
+ $oPage->add('
');
+ $oPage->add('
Hello World!
');
+ $oPage->add('
');
+ }
+
+ public function ToXml(DOMNode $oContainerNode)
+ {
+ $oNewNodeNode = $oContainerNode->ownerDocument->createElement('hello_world', 'test');
+ $oContainerNode->appendChild($oNewNodeNode);
+ }
+
+ public function GetForm()
+ {
+
+ }
+
+ public function OnFieldUpdate($aParams, $sUpdatedFieldCode)
+ {
+ return array(
+ 'status_ok' => true,
+ 'redraw' => false,
+ 'fields' => array(),
+ );
+ }
+
+ static public function GetInfo()
+ {
+ return array(
+ 'label' => 'Hello World',
+ 'icon' => 'images/dashlet-text.png',
+ 'description' => 'Hello World test Dashlet',
+ );
+ }
+}
+
+
+class DashletFakeBarChart extends Dashlet
+{
+ public function __construct()
+ {
+
+ }
+
+ public function FromDOMNode($oDOMNode)
+ {
+
+ }
+
+ public function FromXml($sXml)
+ {
+
+ }
+
+ public function FromParams($aParams)
+ {
+
+ }
+
+ public function Render($oPage, $bEditMode = false, $aExtraParams = array())
+ {
+ $oPage->add('
');
+ $oPage->add('
Fake Bar Chart
');
+ $oPage->add('');
+ }
+
+ public function ToXml(DOMNode $oContainerNode)
+ {
+ $oNewNodeNode = $oContainerNode->ownerDocument->createElement('fake_bar_chart', 'test');
+ $oContainerNode->appendChild($oNewNodeNode);
+ }
+
+ public function GetForm()
+ {
+
+ }
+
+ public function OnFieldUpdate($aParams, $sUpdatedFieldCode)
+ {
+ return array(
+ 'status_ok' => true,
+ 'redraw' => false,
+ 'fields' => array(),
+ );
+ }
+
+ static public function GetInfo()
+ {
+ return array(
+ 'label' => 'Bar Chart',
+ 'icon' => 'images/dashlet-bar-chart.png',
+ 'description' => 'Fake Bar Chart (for testing)',
+ );
+ }
+}
+
+
+class DashletFakePieChart extends Dashlet
+{
+ public function __construct()
+ {
+
+ }
+
+ public function FromDOMNode($oDOMNode)
+ {
+
+ }
+
+ public function FromXml($sXml)
+ {
+
+ }
+
+ public function FromParams($aParams)
+ {
+
+ }
+
+ public function Render($oPage, $bEditMode = false, $aExtraParams = array())
+ {
+ $oPage->add('
');
+ $oPage->add('
Fake Pie Chart
');
+ $oPage->add('
');
+ }
+
+ public function ToXml(DOMNode $oContainerNode)
+ {
+ $oNewNodeNode = $oContainerNode->ownerDocument->createElement('fake_pie_chart', 'test');
+ $oContainerNode->appendChild($oNewNodeNode);
+ }
+
+ public function GetForm()
+ {
+
+ }
+
+ public function OnFieldUpdate($aParams, $sUpdatedFieldCode)
+ {
+ return array(
+ 'status_ok' => true,
+ 'redraw' => false,
+ 'fields' => array(),
+ );
+ }
+
+ static public function GetInfo()
+ {
+ return array(
+ 'label' => 'Pie Chart',
+ 'icon' => 'images/dashlet-pie-chart.png',
+ 'description' => 'Fake Pie Chart (for testing)',
+ );
+ }
+}
\ No newline at end of file
diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php
index 73ef30a34..ce2368056 100644
--- a/application/menunode.class.inc.php
+++ b/application/menunode.class.inc.php
@@ -771,4 +771,67 @@ class NewObjectMenuNode extends MenuNode
assert(false); // Shall never be called, the external web page will handle the display by itself
}
}
-?>
+
+require_once(APPROOT.'application/dashboard.class.inc.php');
+/**
+ * This class defines a menu item which content is based on XML dashboard.
+ */
+class DashboardMenuNode extends MenuNode
+{
+ protected $sDashboardFile;
+
+ /**
+ * Create a menu item based on a custom template and inserts it into the application's main menu
+ * @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary)
+ * @param string $sTemplateFile Path (or URL) to the file that will be used as a template for displaying the page's content
+ * @param integer $iParentIndex ID of the parent menu
+ * @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value
+ * @param string $sEnableClass Name of class of object
+ * @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE
+ * @param integer $iAllowedResults Expected "rights" for the action: either UR_ALLOWED_YES, UR_ALLOWED_NO, UR_ALLOWED_DEPENDS or a mix of them...
+ * @return MenuNode
+ */
+ public function __construct($sMenuId, $sDashboardFile, $iParentIndex, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
+ {
+ parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
+ $this->sDashboardFile = $sDashboardFile;
+ $this->aReflectionProperties['dashboard_file'] = $sDashboardFile;
+ }
+
+ public function GetHyperlink($aExtraParams)
+ {
+ if ($this->sDashboardFile == '') return '';
+ return parent::GetHyperlink($aExtraParams);
+ }
+
+ public function RenderContent(WebPage $oPage, $aExtraParams = array())
+ {
+ $sDashboardDefinition = @file_get_contents($this->sDashboardFile);
+ if ($sDashboardDefinition !== false)
+ {
+ $oDashboard = new RuntimeDashboard($this->sMenuId);
+ $oDashboard->FromXml($sDashboardDefinition);
+ $oDashboard->Render($oPage, false, $aExtraParams);
+ }
+ else
+ {
+ $oPage->p("Error: failed to load template file: '{$this->sDashboardFile}'"); // No need to translate ?
+ }
+ }
+
+ public function RenderEditor(WebPage $oPage)
+ {
+ $sDashboardDefinition = @file_get_contents($this->sDashboardFile);
+ if ($sDashboardDefinition !== false)
+ {
+ $oDashboard = new RuntimeDashboard($this->sMenuId);
+ $oDashboard->FromXml($sDashboardDefinition);
+ $oDashboard->RenderEditor($oPage);
+ }
+ else
+ {
+ $oPage->p("Error: failed to load template file: '{$this->sDashboardFile}'"); // No need to translate ?
+ }
+ }
+}
+