diff --git a/application/dashboard.class.inc.php b/application/dashboard.class.inc.php index 8d1fa7460..4970f6c38 100644 --- a/application/dashboard.class.inc.php +++ b/application/dashboard.class.inc.php @@ -178,10 +178,10 @@ abstract class Dashboard public function RenderProperties($oPage) { // menu to pick a layout and edit other properties of the dashboard - $oPage->add('
Dashboard Properties
'); + $oPage->add('
'.Dict::S('UI:DashboardEdit:Properties').'
'); $sUrl = utils::GetAbsoluteUrlAppRoot(); - $oPage->add('
Layout:
'); + $oPage->add('
'.Dict::S('UI:DashboardEdit:Layout').'
'); $oPage->add('
'); foreach( get_declared_classes() as $sLayoutClass) { @@ -200,7 +200,7 @@ abstract class Dashboard $oPage->add('
'); $oForm = new DesignerForm(); - $oField = new DesignerLongTextField('dashboard_title', 'Title', $this->sTitle); + $oField = new DesignerLongTextField('dashboard_title', Dict::S('UI:DashboardEdit:DashboardTitle'), $this->sTitle); $oForm->AddField($oField); $this->SetFormParams($oForm); $oForm->RenderAsPropertySheet($oPage); @@ -226,7 +226,7 @@ EOF public function RenderDashletsSelection($oPage) { // Toolbox/palette to drag and drop dashlets - $oPage->add('
Available Dashlets
'); + $oPage->add('
'.Dict::S('UI:DashboardEdit:Dashlets').'
'); $sUrl = utils::GetAbsoluteUrlAppRoot(); $oPage->add('
'); @@ -258,7 +258,7 @@ EOF public function RenderDashletsProperties($oPage) { // Toolbox/palette to edit the properties of each dashlet - $oPage->add('
Dashlet Properties
'); + $oPage->add('
'.Dict::S('UI:DashboardEdit:DashletProperties').'
'); $oPage->add('
'); foreach($this->aCells as $aCell) @@ -284,7 +284,15 @@ EOF protected function GetNewDashletId() { - return 999; + $iNewId = 0; + foreach($this->aCells as $aDashlets) + { + foreach($aDashlets as $oDashlet) + { + $iNewId = max($iNewId, (int)$oDashlet->GetID()); + } + } + return $iNewId + 1; } abstract protected function SetFormParams($oForm); @@ -357,10 +365,11 @@ class RuntimeDashboard extends Dashboard if (!$bEditMode) { $sEditMenu = ""; $sEditMenu = addslashes($sEditMenu); @@ -413,8 +422,8 @@ EOF $oPage->add('
'); // For exchanging messages between the panes, same as in the designer $oPage->add('
'); - $sDialogTitle = 'Dashboard Editor'; - $sOkButtonLabel = Dict::S('UI:Button:Ok'); + $sDialogTitle = Dict::S('UI:DashboardEdit:Title'); + $sOkButtonLabel = Dict::S('UI:Button:Save'); $sCancelButtonLabel = Dict::S('UI:Button:Cancel'); $sId = addslashes($this->sId); @@ -472,11 +481,13 @@ $('#event_bus').bind('dashlet-selected', function(event, data){ }); -dashboard_prop_size = GetUserPreference('dashboard_prop_size', 300); +dashboard_prop_size = GetUserPreference('dashboard_prop_size', 350); $('#dashboard_editor').layout({ east: { - minSize: 150, + minSize: 200, size: dashboard_prop_size, + togglerLength_open: 0, + togglerLength_closed: 0, onresize_end: function(name, elt, state, options, layout) { if (state.isSliding == false) @@ -486,13 +497,12 @@ $('#dashboard_editor').layout({ }, } }); - EOF ); $oPage->add_ready_script(""); } - public static function GetDashletCreationForm($sOQL) + public static function GetDashletCreationForm($sOQL = null) { $oForm = new DesignerForm(); @@ -502,15 +512,26 @@ EOF foreach($aAllMenus as $idx => $aMenu) { $oMenu = $aMenu['node']; + $sParentId = $aMenu['parent']; if ($oMenu instanceof DashboardMenuNode) { - $aAllowedDashboards[$oMenu->GetMenuId()] = Dict::S($oMenu->GetMenuId()); + $sMenuLabel = $oMenu->GetTitle(); + $sParentLabel = Dict::S('Menu:'.$sParentId); + if ($sParentLabel != $sMenuLabel) + { + $aAllowedDashboards[$oMenu->GetMenuId()] = $sParentLabel.' - '.$sMenuLabel; + } + else + { + $aAllowedDashboards[$oMenu->GetMenuId()] = $sMenuLabel; + } } } + asort($aAllowedDashboards); $aKeys = array_keys($aAllowedDashboards); // Select the first one by default $sDefaultDashboard = $aKeys[0]; - $oField = new DesignerComboField('menu_id', 'Dashboard', $sDefaultDashboard); + $oField = new DesignerComboField('menu_id', Dict::S('UI:DashletCreation:Dashboard'), $sDefaultDashboard); $oField->SetAllowedValues($aAllowedDashboards); $oField->SetMandatory(true); $oForm->AddField($oField); @@ -518,7 +539,7 @@ EOF // Get the list of possible dashlets that support a creation from // an OQL $aDashlets = array(); - foreach( get_declared_classes() as $sDashletClass) + foreach(get_declared_classes() as $sDashletClass) { if (is_subclass_of($sDashletClass, 'Dashlet')) { @@ -537,7 +558,7 @@ EOF } } - $oSelectorField = new DesignerFormSelectorField('dashlet_class', 'Dashlet Type', ''); + $oSelectorField = new DesignerFormSelectorField('dashlet_class', Dict::S('UI:DashletCreation:DashletType'), ''); $oForm->AddField($oSelectorField); foreach($aDashlets as $sDashletClass => $aDashletInfo) { @@ -547,7 +568,7 @@ EOF $oSelectorField->AddSubForm($oSubForm, $aDashletInfo['label'], $aDashletInfo['class']); } - $oField = new DesignerBooleanField('open_editor', 'Edit the Dashboard', true); + $oField = new DesignerBooleanField('open_editor', Dict::S('UI:DashletCreation:EditNow'), true); $oForm->AddField($oField); return $oForm; @@ -558,11 +579,11 @@ EOF $oPage->add('
'); $oForm = self::GetDashletCreationForm($sOQL); - + $oForm->Render($oPage); $oPage->add('
'); - $sDialogTitle = 'Create a new Dashlet'; + $sDialogTitle = Dict::S('UI:DashletCreation:Title'); $sOkButtonLabel = Dict::S('UI:Button:Ok'); $sCancelButtonLabel = Dict::S('UI:Button:Cancel'); diff --git a/application/dashlet.class.inc.php b/application/dashlet.class.inc.php index 5f2ef2f32..ee43515ee 100644 --- a/application/dashlet.class.inc.php +++ b/application/dashlet.class.inc.php @@ -145,13 +145,21 @@ abstract class Dashlet if ($bEditMode) { $oPage->add('
'); - $oPage->add('

Unknown Class: '.$e->GetWrongWord().', did you mean '.OqlException::FindClosestString($e->GetWrongWord(), $e->GetSuggestions()).'?

'); //TODO localize and + $oPage->add('

'.$e->GetUserFriendlyDescription().'

'); $oPage->add('
'); } } + catch(OqlException $e) + { + $oPage->add('
'); + $oPage->p($e->GetUserFriendlyDescription()); + $oPage->add('
'); + } catch(Exception $e) { + $oPage->add('
'); $oPage->p($e->getMessage()); + $oPage->add('
'); } if ($bEnclosingDiv) @@ -248,7 +256,7 @@ EOF return false; } - public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL) + public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL = null) { // Default: do nothing since it's not supported } @@ -285,34 +293,35 @@ class DashletEmptyCell extends Dashlet } } -class DashletRichText extends Dashlet +class DashletPlainText extends Dashlet { public function __construct($sId) { parent::__construct($sId); - $this->aProperties['text'] = "

Lorem ipsum

\n

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper.

Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit. Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue.

Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna. Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet.

Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet.

"; + $this->aProperties['text'] = Dict::S('UI:DashletPlainText:Prop-Text:Default'); } public function Render($oPage, $bEditMode = false, $aExtraParams = array()) { - $sText = $this->aProperties['text']; + $sText = htmlentities($this->aProperties['text'], ENT_QUOTES, 'UTF-8'); - $sId = 'richtext_'.($bEditMode? 'edit_' : '').$this->sId; + $sId = 'plaintext_'.($bEditMode? 'edit_' : '').$this->sId; $oPage->add('
'.$sText.'
'); } public function GetPropertiesFields(DesignerForm $oForm) { - $oField = new DesignerLongTextField('text', 'Text', $this->aProperties['text']); + $oField = new DesignerLongTextField('text', Dict::S('UI:DashletPlainText:Prop-Text'), $this->aProperties['text']); + $oField->SetMandatory(); $oForm->AddField($oField); } static public function GetInfo() { return array( - 'label' => 'Rich text', + 'label' => Dict::S('UI:DashletPlainText:Label'), 'icon' => 'images/dashlet-text.png', - 'description' => 'Text formatted with HTML tags', + 'description' => Dict::S('UI:DashletPlainText:Description'), ); } } @@ -322,8 +331,8 @@ class DashletObjectList extends Dashlet public function __construct($sId) { parent::__construct($sId); - $this->aProperties['title'] = 'Hardcoded list of "my requests"'; - $this->aProperties['query'] = 'SELECT UserRequest AS i WHERE i.caller_id = :current_contact_id AND status NOT IN ("closed", "resolved")'; + $this->aProperties['title'] = ''; + $this->aProperties['query'] = 'SELECT Contact'; $this->aProperties['menu'] = false; } @@ -352,22 +361,23 @@ class DashletObjectList extends Dashlet public function GetPropertiesFields(DesignerForm $oForm) { - $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']); + $oField = new DesignerTextField('title', Dict::S('UI:DashletObjectList:Prop-Title'), $this->aProperties['title']); $oForm->AddField($oField); - $oField = new DesignerLongTextField('query', 'Query', $this->aProperties['query']); + $oField = new DesignerLongTextField('query', Dict::S('UI:DashletObjectList:Prop-Query'), $this->aProperties['query']); + $oField->SetMandatory(); $oForm->AddField($oField); - $oField = new DesignerBooleanField('menu', 'Menu', $this->aProperties['menu']); + $oField = new DesignerBooleanField('menu', Dict::S('UI:DashletObjectList:Prop-Menu'), $this->aProperties['menu']); $oForm->AddField($oField); } static public function GetInfo() { return array( - 'label' => 'Object list', + 'label' => Dict::S('UI:DashletObjectList:Label'), 'icon' => 'images/dashlet-list.png', - 'description' => 'Object list dashlet', + 'description' => Dict::S('UI:DashletObjectList:Description'), ); } @@ -376,15 +386,16 @@ class DashletObjectList extends Dashlet return true; } - public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL) + public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL = null) { - $oField = new DesignerTextField('title', 'Title', ''); + $oField = new DesignerTextField('title', Dict::S('UI:DashletObjectList:Prop-Title'), ''); $oForm->AddField($oField); - $oField = new DesignerHiddenField('query', 'Query', $sOQL); + $oField = new DesignerHiddenField('query', Dict::S('UI:DashletObjectList:Prop-Query'), $sOQL); + $oField->SetMandatory(); $oForm->AddField($oField); - $oField = new DesignerBooleanField('menu', 'Menu', $this->aProperties['menu']); + $oField = new DesignerBooleanField('menu', Dict::S('UI:DashletObjectList:Prop-Menu'), $this->aProperties['menu']); $oForm->AddField($oField); } } @@ -394,9 +405,9 @@ abstract class DashletGroupBy extends Dashlet public function __construct($sId) { parent::__construct($sId); - $this->aProperties['title'] = 'Contacts grouped by location'; + $this->aProperties['title'] = ''; $this->aProperties['query'] = 'SELECT Contact'; - $this->aProperties['group_by'] = 'location_name'; + $this->aProperties['group_by'] = 'status'; $this->aProperties['style'] = 'table'; } @@ -407,43 +418,52 @@ abstract class DashletGroupBy extends Dashlet $sGroupBy = $this->aProperties['group_by']; $sStyle = $this->aProperties['style']; - if ($sQuery == '') + // First perform the query - if the OQL is not ok, it will generate an exception : no need to go further + $oFilter = DBObjectSearch::FromOQL($sQuery); + + $sClass = $oFilter->GetClass(); + $sClassAlias = $oFilter->GetClassAlias(); + + // Check groupby... it can be wrong at this stage + if (preg_match('/^(.*):(.*)$/', $sGroupBy, $aMatches)) { - $oPage->add('

Please enter a valid OQL query

'); - } - elseif ($sGroupBy == '') - { - $oPage->add('

Please select the field on which the objects will be grouped together

'); + $sAttCode = $aMatches[1]; + $sFunction = $aMatches[2]; } else { - $oFilter = DBObjectSearch::FromOQL($sQuery); - $sClassAlias = $oFilter->GetClassAlias(); - - if (preg_match('/^(.*):(.*)$/', $sGroupBy, $aMatches)) + $sAttCode = $sGroupBy; + $sFunction = null; + } + if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) + { + $oPage->add('

'.Dict::S('UI:DashletGroupBy:MissingGroupBy').'

'); + } + else + { + $sAttLabel = MetaModel::GetLabel($sClass, $sAttCode); + if (!is_null($sFunction)) { - $sAttCode = $aMatches[1]; $sFunction = $aMatches[2]; - switch($sFunction) { case 'hour': - $sGroupByLabel = 'Hour of '.$sAttCode. ' (0-23)'; - $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%H')"; // 0 -> 31 + $sGroupByLabel = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Hour', $sAttLabel); + $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%H')"; // 0 -> 23 break; case 'month': - $sGroupByLabel = 'Month of '.$sAttCode. ' (1 - 12)'; + $sGroupByLabel = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Month', $sAttLabel); $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%m')"; // 0 -> 31 break; case 'day_of_week': - $sGroupByLabel = 'Day of week for '.$sAttCode. ' (sunday to saturday)'; + $sGroupByLabel = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:DayOfWeek', $sAttLabel); $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%w')"; break; case 'day_of_month': - $sGroupByLabel = 'Day of month for'.$sAttCode; + $sGroupByLabel = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:DayOfMonth', $sAttLabel); $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%e')"; // 0 -> 31 break; @@ -454,10 +474,8 @@ abstract class DashletGroupBy extends Dashlet } else { - $sAttCode = $sGroupBy; - $sGroupByExpr = $sClassAlias.'.'.$sAttCode; - $sGroupByLabel = MetaModel::GetLabel($oFilter->GetClass(), $sAttCode); + $sGroupByLabel = $sAttLabel; } switch($sStyle) @@ -507,57 +525,65 @@ abstract class DashletGroupBy extends Dashlet } } + protected function GetGroupByOptions($sOql) + { + $oSearch = DBObjectSearch::FromOQL($sOql); + $sClass = $oSearch->GetClass(); + $aGroupBy = array(); + foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) + { + if (!$oAttDef->IsScalar()) continue; // skip link sets + if ($oAttDef instanceof AttributeFriendlyName) continue; + if ($oAttDef instanceof AttributeExternalField) continue; + + $sLabel = $oAttDef->GetLabel(); + $aGroupBy[$sAttCode] = $sLabel; + + if ($oAttDef instanceof AttributeDateTime) + { + $aGroupBy[$sAttCode.':hour'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Hour', $sLabel); + $aGroupBy[$sAttCode.':month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Month', $sLabel); + $aGroupBy[$sAttCode.':day_of_week'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek', $sLabel); + $aGroupBy[$sAttCode.':day_of_month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth', $sLabel); + } + } + asort($aGroupBy); + return $aGroupBy; + } + public function GetPropertiesFields(DesignerForm $oForm) { - $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']); + $oField = new DesignerTextField('title', Dict::S('UI:DashletGroupBy:Prop-Title'), $this->aProperties['title']); $oForm->AddField($oField); - $oField = new DesignerLongTextField('query', 'Query', $this->aProperties['query']); + $oField = new DesignerLongTextField('query', Dict::S('UI:DashletGroupBy:Prop-Query'), $this->aProperties['query']); + $oField->SetMandatory(); $oForm->AddField($oField); try { // Group by field: build the list of possible values (attribute codes + ...) - $oSearch = DBObjectSearch::FromOQL($this->aProperties['query']); - $sClass = $oSearch->GetClass(); - $aGroupBy = array(); - foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) - { - if (!$oAttDef->IsScalar()) continue; // skip link sets + $aGroupBy = $this->GetGroupByOptions($this->aProperties['query']); - $sLabel = $oAttDef->GetLabel(); - if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) - { - $sLabel = $oAttDef->GetLabel().' (strict)'; - } - - $aGroupBy[$sAttCode] = $sLabel; - - if ($oAttDef instanceof AttributeDateTime) - { - $aGroupBy[$sAttCode.':hour'] = $oAttDef->GetLabel().' (hour)'; - $aGroupBy[$sAttCode.':month'] = $oAttDef->GetLabel().' (month)'; - $aGroupBy[$sAttCode.':day_of_week'] = $oAttDef->GetLabel().' (day of week)'; - $aGroupBy[$sAttCode.':day_of_month'] = $oAttDef->GetLabel().' (day of month)'; - } - } - - $oField = new DesignerComboField('group_by', 'Group by', $this->aProperties['group_by']); + $oField = new DesignerComboField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), $this->aProperties['group_by']); + $oField->SetMandatory(); $oField->SetAllowedValues($aGroupBy); } catch(Exception $e) { - $oField = new DesignerTextField('group_by', 'Group by', $this->aProperties['group_by']); + $oField = new DesignerTextField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), $this->aProperties['group_by']); + $oField->SetReadOnly(); } $oForm->AddField($oField); $aStyles = array( - 'pie' => 'Pie chart', - 'bars' => 'Bar chart', - 'table' => 'Table', + 'pie' => Dict::S('UI:DashletGroupByPie:Label'), + 'bars' => Dict::S('UI:DashletGroupByBars:Label'), + 'table' => Dict::S('UI:DashletGroupByTable:Label'), ); - $oField = new DesignerComboField('style', 'Style', $this->aProperties['style']); + $oField = new DesignerComboField('style', Dict::S('UI:DashletGroupBy:Prop-Style'), $this->aProperties['style']); + $oField->SetMandatory(); $oField->SetAllowedValues($aStyles); $oForm->AddField($oField); } @@ -616,10 +642,11 @@ abstract class DashletGroupBy extends Dashlet static public function GetInfo() { + // Note: no need to translate, should never be visible to the end-user! return array( 'label' => 'Objects grouped by...', 'icon' => 'images/dashlet-object-grouped.png', - 'description' => 'Grouped objects dashlet', + 'description' => 'Grouped objects dashlet (abstract)', ); } @@ -628,44 +655,32 @@ abstract class DashletGroupBy extends Dashlet return true; } - public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL) + public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL = null) { - $oField = new DesignerTextField('title', 'Title', ''); + $oField = new DesignerTextField('title', Dict::S('UI:DashletGroupBy:Prop-Title'), ''); $oForm->AddField($oField); - $oField = new DesignerHiddenField('query', 'Query', $sOQL); + $oField = new DesignerHiddenField('query', Dict::S('UI:DashletGroupBy:Prop-Query'), $sOQL); + $oField->SetMandatory(); $oForm->AddField($oField); - try + if (!is_null($sOQL)) { - // Group by field: build the list of possible values (attribute codes + ...) - $oSearch = DBObjectSearch::FromOQL($this->aProperties['query']); - $sClass = $oSearch->GetClass(); - $aGroupBy = array(); - foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) - { - if (!$oAttDef->IsScalar()) continue; // skip link sets - if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) continue; // skip external keys - $aGroupBy[$sAttCode] = $oAttDef->GetLabel(); - - if ($oAttDef instanceof AttributeDateTime) - { - //date_format(start_date, '%d') - $aGroupBy['date_of_'.$sAttCode] = 'Day of '.$oAttDef->GetLabel(); - } - - } - $oField = new DesignerComboField('group_by', 'Group by', $this->aProperties['group_by']); + $oField = new DesignerComboField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), null); + $aGroupBy = $this->GetGroupByOptions($sOQL); $oField->SetAllowedValues($aGroupBy); } - catch(Exception $e) + else { - $oField = new DesignerTextField('group_by', 'Group by', $this->aProperties['group_by']); + // Creating a form for reading parameters! + $oField = new DesignerTextField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), null); } + $oField->SetMandatory(); $oForm->AddField($oField); $oField = new DesignerHiddenField('style', '', $this->aProperties['style']); + $oField->SetMandatory(); $oForm->AddField($oField); } } @@ -681,69 +696,9 @@ class DashletGroupByPie extends DashletGroupBy static public function GetInfo() { return array( - 'label' => 'Pie Chart', + 'label' => Dict::S('UI:DashletGroupByPie:Label'), 'icon' => 'images/dashlet-pie-chart.png', - 'description' => 'Pie Chart', - ); - } -} -class DashletGroupByPie2 extends DashletGroupByPie -{ - public function Render($oPage, $bEditMode = false, $aExtraParams = array()) - { - $sTitle = addslashes($this->aProperties['title']); - - $sQuery = $this->aProperties['query']; - $sGroupBy = $this->aProperties['group_by']; - - $oSearch = DBObjectSearch::FromOQL($sQuery); - $sClassAlias = $oSearch->GetClassAlias(); - $aQueryParams = array(); - - $aGroupBy = array(); - $oGroupByExp = Expression::FromOQL($sClassAlias.'.'.$sGroupBy); - $aGroupBy['grouped_by_1'] = $oGroupByExp; - $sSql = MetaModel::MakeGroupByQuery($oSearch, $aQueryParams, $aGroupBy); - $aRes = CMDBSource::QueryToArray($sSql); - - $aGroupBy = array(); - $aLabels = array(); - $iTotalCount = 0; - foreach ($aRes as $aRow) - { - $sValue = $aRow['grouped_by_1']; - $aLabels[] = ($sValue == '') ? 'Empty (%%.%%)' : $sValue.' (%%.%%)'; //TODO: localize - $aGroupBy[] = (int) $aRow['_itop_count_']; - $iTotalCount += $aRow['_itop_count_']; - } - - $aURLs = array(); - $sContext = ''; //TODO get the context ?? - foreach($aGroupBy as $sValue => $iValue) - { - // Build the search for this subset - $oSubsetSearch = clone $oSearch; - $oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($sValue)); - $oSubsetSearch->AddConditionExpression($oCondition); - // Todo - à finir - $aURLs[] = 'http://www.combodo.com/itop'; //utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&format=html{$sContext}&filter=".urlencode($oSubsetSearch->serialize()); - } - - $sJSValues = json_encode($aGroupBy); - $sJSHrefs = json_encode($aURLs); - $sJSLabels = json_encode($aLabels); - - $sId = 'chart_'.($bEditMode? 'edit_' : '').$this->sId; - $oPage->add('
'); - $oPage->add_ready_script("$('#chart_{$sId}').pie_chart({chart_label: '$sTitle', values: $sJSValues, labels: $sJSLabels, hrefs: $sJSHrefs });"); - } - - static public function GetInfo() - { - return array( - 'label' => 'Pie (Raphael)', - 'icon' => 'images/dashlet-pie-chart.png', - 'description' => 'Pure JS Pie Chart', + 'description' => Dict::S('UI:DashletGroupByPie:Description'), ); } } @@ -760,9 +715,9 @@ class DashletGroupByBars extends DashletGroupBy static public function GetInfo() { return array( - 'label' => 'Bar Chart', + 'label' => Dict::S('UI:DashletGroupByBars:Label'), 'icon' => 'images/dashlet-bar-chart.png', - 'description' => 'Bar Chart', + 'description' => Dict::S('UI:DashletGroupByBars:Description'), ); } } @@ -778,9 +733,9 @@ class DashletGroupByTable extends DashletGroupBy static public function GetInfo() { return array( - 'label' => 'Group By (table)', + 'label' => Dict::S('UI:DashletGroupByTable:Label'), + 'description' => Dict::S('UI:DashletGroupByTable:Description'), 'icon' => 'images/dashlet-groupby-table.png', - 'description' => 'List (Grouped by a field)', ); } } @@ -791,8 +746,10 @@ class DashletHeaderStatic extends Dashlet public function __construct($sId) { parent::__construct($sId); - $this->aProperties['title'] = 'Contacts'; - $this->aProperties['icon'] = 'itop-config-mgmt-1.0.0/images/contact.png'; + $this->aProperties['title'] = Dict::S('UI:DashletHeaderStatic:Prop-Title:Default'); + $sIcon = MetaModel::GetClassIcon('Contact', false); + $sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIcon); + $this->aProperties['icon'] = $sIcon; } public function Render($oPage, $bEditMode = false, $aExtraParams = array()) @@ -814,10 +771,10 @@ class DashletHeaderStatic extends Dashlet public function GetPropertiesFields(DesignerForm $oForm) { - $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']); + $oField = new DesignerTextField('title', Dict::S('UI:DashletHeaderStatic:Prop-Title'), $this->aProperties['title']); $oForm->AddField($oField); - $oField = new DesignerIconSelectionField('icon', 'Icon', $this->aProperties['icon']); + $oField = new DesignerIconSelectionField('icon', Dict::S('UI:DashletHeaderStatic:Prop-Icon'), $this->aProperties['icon']); $aAllIcons = self::FindIcons(APPROOT.'env-'.utils::GetCurrentEnvironment()); ksort($aAllIcons); $aValues = array(); @@ -832,9 +789,9 @@ class DashletHeaderStatic extends Dashlet static public function GetInfo() { return array( - 'label' => 'Header', + 'label' => Dict::S('UI:DashletHeaderStatic:Label'), 'icon' => 'images/dashlet-header.png', - 'description' => 'Header with stats (grouped by...)', + 'description' => Dict::S('UI:DashletHeaderStatic:Description'), ); } @@ -869,9 +826,11 @@ class DashletHeaderDynamic extends Dashlet public function __construct($sId) { parent::__construct($sId); - $this->aProperties['title'] = 'Contacts'; - $this->aProperties['icon'] = 'itop-config-mgmt-1.0.0/images/contact.png'; - $this->aProperties['subtitle'] = 'Contacts'; + $this->aProperties['title'] = Dict::S('UI:DashletHeaderDynamic:Prop-Title:Default'); + $sIcon = MetaModel::GetClassIcon('Contact', false); + $sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIcon); + $this->aProperties['icon'] = $sIcon; + $this->aProperties['subtitle'] = Dict::S('UI:DashletHeaderDynamic:Prop-Subtitle:Default'); $this->aProperties['query'] = 'SELECT Contact'; $this->aProperties['group_by'] = 'status'; $this->aProperties['values'] = array('active', 'inactive'); @@ -939,10 +898,10 @@ class DashletHeaderDynamic extends Dashlet public function GetPropertiesFields(DesignerForm $oForm) { - $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']); + $oField = new DesignerTextField('title', Dict::S('UI:DashletHeaderDynamic:Prop-Title'), $this->aProperties['title']); $oForm->AddField($oField); - $oField = new DesignerIconSelectionField('icon', 'Icon', $this->aProperties['icon']); + $oField = new DesignerIconSelectionField('icon', Dict::S('UI:DashletHeaderDynamic:Prop-Icon'), $this->aProperties['icon']); $aAllIcons = DashletHeaderStatic::FindIcons(APPROOT.'env-'.utils::GetCurrentEnvironment()); ksort($aAllIcons); $aValues = array(); @@ -953,10 +912,11 @@ class DashletHeaderDynamic extends Dashlet $oField->SetAllowedValues($aValues); $oForm->AddField($oField); - $oField = new DesignerTextField('subtitle', 'Subtitle', $this->aProperties['subtitle']); + $oField = new DesignerTextField('subtitle', Dict::S('UI:DashletHeaderDynamic:Prop-Subtitle'), $this->aProperties['subtitle']); $oForm->AddField($oField); - $oField = new DesignerTextField('query', 'Query', $this->aProperties['query']); + $oField = new DesignerTextField('query', Dict::S('UI:DashletHeaderDynamic:Prop-Query'), $this->aProperties['query']); + $oField->SetMandatory(); $oForm->AddField($oField); try @@ -971,16 +931,18 @@ class DashletHeaderDynamic extends Dashlet $sLabel = $oAttDef->GetLabel(); $aGroupBy[$sAttCode] = $sLabel; } - $oField = new DesignerComboField('group_by', 'Group by', $this->aProperties['group_by']); + $oField = new DesignerComboField('group_by', Dict::S('UI:DashletHeaderDynamic:Prop-GroupBy'), $this->aProperties['group_by']); + $oField->SetMandatory(); $oField->SetAllowedValues($aGroupBy); } catch(Exception $e) { - $oField = new DesignerTextField('group_by', 'Group by', $this->aProperties['group_by']); + $oField = new DesignerTextField('group_by', Dict::S('UI:DashletHeaderDynamic:Prop-GroupBy'), $this->aProperties['group_by']); + $oField->SetMandatory(); } $oForm->AddField($oField); - $oField = new DesignerComboField('values', 'Values (CSV list)', $this->aProperties['values']); + $oField = new DesignerComboField('values', Dict::S('UI:DashletHeaderDynamic:Prop-Values'), $this->aProperties['values']); $oField->MultipleSelection(true); if (MetaModel::IsValidAttCode($sClass, $this->aProperties['group_by'])) { @@ -1032,9 +994,9 @@ class DashletHeaderDynamic extends Dashlet static public function GetInfo() { return array( - 'label' => 'Header with statistics', + 'label' => Dict::S('UI:DashletHeaderDynamic:Label'), 'icon' => 'images/dashlet-header-stats.png', - 'description' => 'Header with stats (grouped by...)', + 'description' => Dict::S('UI:DashletHeaderDynamic:Description'), ); } } @@ -1092,7 +1054,7 @@ class DashletBadge extends Dashlet } - $oField = new DesignerIconSelectionField('class', 'Class', $this->aProperties['class']); + $oField = new DesignerIconSelectionField('class', Dict::S('UI:DashletBadge:Prop-Class'), $this->aProperties['class']); ksort($aClasses); $aValues = array(); foreach($aClasses as $sClass => $sClass) @@ -1106,7 +1068,7 @@ class DashletBadge extends Dashlet // The icon does not exist, leet's use a transparent one of the same size. $sIconUrl = utils::GetAbsoluteUrlAppRoot().'images/transparent_32_32.png'; } - $aValues[] = array('value' => $sClass, 'label' => $sClass, 'icon' => $sIconUrl); + $aValues[] = array('value' => $sClass, 'label' => MetaModel::GetName($sClass), 'icon' => $sIconUrl); } } $oField->SetAllowedValues($aValues); @@ -1117,292 +1079,10 @@ class DashletBadge extends Dashlet static public function GetInfo() { return array( - 'label' => 'Badge', + 'label' => Dict::S('UI:DashletBadge:Label'), 'icon' => 'images/dashlet-badge.png', - 'description' => 'Object Icon with new/search', - ); - } -} - - -class DashletProto extends Dashlet -{ - public function __construct($sId) - { - parent::__construct($sId); - $this->aProperties['class'] = 'Foo'; - } - - protected $aValues1; - protected $aValues2; - protected $aStats; - protected $sGroupByLabel1; - protected $sGroupByLabel2; - protected $iTotalCount; - - public function Render($oPage, $bEditMode = false, $aExtraParams = array()) - { - $this->Compute(); - - //$sHtmlTitle = $this->aProperties['title']; - $sHtmlTitle = "Group by made on two dimensions"; - - // Build the output - $oPage->add('
'); - - $oPage->add('

'.$sHtmlTitle.'

'); - $oPage->p(Dict::Format('UI:Pagination:HeaderNoSelection', $this->iTotalCount)); - - $oPage->add(''); - // Header - $oPage->add(''); - $oPage->add(''); - foreach ($this->aValues2 as $sValue2 => $sDisplayValue2) - { - $oPage->add(''); - } - $oPage->add(''); - // Contents - foreach ($this->aValues1 as $sValue1 => $sDisplayValue1) - { - $oPage->add(''); - $oPage->add(''); - foreach ($this->aValues2 as $sValue2 => $sDisplayValue2) - { - @$aResData = $this->aStats[$sValue1][$sValue2]; - if (is_array($aResData)) - { - $sRes = ''.$aResData['count'].''; - } - else - { - // Missing result => 0 - $sRes = '-'; - } - $oPage->add(''); - } - $oPage->add(''); - } - $oPage->add('
'.$sDisplayValue2.'
'.$sDisplayValue1.''.$sRes.'
'); - $oPage->add('
'); - - $sId = 'chart_'.($bEditMode? 'edit_' : '').$this->sId; - $oPage->add('
'); - $aAxisX = $this->aValues1; - $aAxisY = $this->aValues2; - $aData = array(); - $aHref = array(); - foreach ($this->aValues1 as $sValue1 => $sDisplayValue1) - { - foreach ($this->aValues2 as $sValue2 => $sDisplayValue2) - { - @$aResData = $this->aStats[$sValue1][$sValue2]; - if (is_array($aResData)) - { - $aData[$sValue1][$sValue2] = $aResData['count']; - $aHref[$sValue1][$sValue2] = $aResData['href']; - } - else - { - // Missing result => 0 - $aData[$sValue1][$sValue2] = 0; - $aHref[$sValue1][$sValue2] = ''; - } - } - } - - $sJSAxisX = json_encode($aAxisX); - $sJSAxisY = json_encode($aAxisY); - $sJSData = json_encode($aData); - $sJSHref = json_encode($aHref); - $sTitle = addslashes($sHtmlTitle); - $oPage->add_ready_script("$('#chart_{$sId}').heatmap_chart({chart_label: '$sTitle', values: $sJSData, hrefs: $sJSHref, axis_x: $sJSAxisX, axis_y: $sJSAxisY});"); - - } - - protected function Compute() - { - // Read and interpret parameters - // - //$sFoo = $this->aProperties['foo']; - - if (false) - { - $oFilter = DBObjectSearch::FromOQL('SELECT FunctionalCI'); - $sGroupBy1 = 'FunctionalCI.status'; - $this->sGroupByLabel1 = MetaModel::GetLabel('FunctionalCI', 'status'); - $aFill1 = array( - 'production', - 'implementation', - ); - $aFill1 = null; - //$sGroupBy2 = 'org_id_friendlyname'; - $sGroupBy2 = 'FunctionalCI.org_id'; - $this->sGroupByLabel2 = MetaModel::GetLabel('FunctionalCI', 'org_id'); - $aFill2 = array( - '2', - '1', - ); - //$aFill2 = null; - } - else - { - $oFilter = DBObjectSearch::FromOQL('SELECT Incident AS i'); - $sGroupBy1 = "DATE_FORMAT(i.start_date, '%H')"; - $this->sGroupByLabel1 = 'Hour of '.MetaModel::GetLabel('Incident', 'start_date'); - $aFill1 = array(9, 10, 11, 12, 13, 14, 15, 16, 17, 18); - //$aFill1 = null; - - $sGroupBy2 = "DATE_FORMAT(i.start_date, '%w')"; - $this->sGroupByLabel2 = 'Week day of '.MetaModel::GetLabel('Incident', 'start_date'); - $aFill2 = array(1, 2, 3, 4, 5); - //$aFill2 = null; - } - - // Do compute the statistics - // - $this->aValues1 = array(); - $this->aValues2 = array(); - $this->aStats = array(); - $this->iTotalCount = 0; - - $sAlias = $oFilter->GetClassAlias(); - - //$oGroupByExp1 = new FieldExpression($sGroupBy1, $sAlias); - $oGroupByExp1 = Expression::FromOQL($sGroupBy1); - - //$oGroupByExp2 = new FieldExpression($sGroupBy2, $sAlias); - $oGroupByExp2 = Expression::FromOQL($sGroupBy2); - - $aGroupBy = array(); - $aGroupBy['grouped_by_1'] = $oGroupByExp1; - $aGroupBy['grouped_by_2'] = $oGroupByExp2; - $sSql = MetaModel::MakeGroupByQuery($oFilter, array(), $aGroupBy); - $aRes = CMDBSource::QueryToArray($sSql); - - // Prepare a blank and ordered grid - if (is_array($aFill1)) - { - foreach ($aFill1 as $sValue1) - { - $sDisplayValue1 = $aGroupBy['grouped_by_1']->MakeValueLabel($oFilter, $sValue1, $sValue1); // default to the raw value - $this->aValues1[$sValue1] = $sDisplayValue1; - } - } - if (is_array($aFill2)) - { - foreach ($aFill2 as $sValue2) - { - $sDisplayValue2 = $aGroupBy['grouped_by_2']->MakeValueLabel($oFilter, $sValue2, $sValue2); // default to the raw value - $this->aValues2[$sValue2] = $sDisplayValue2; - } - } - - $oAppContext = new ApplicationContext(); - $sParams = $oAppContext->GetForLink(); - foreach ($aRes as $aRow) - { - $iCount = $aRow['_itop_count_']; - $this->iTotalCount += $iCount; - - $sValue1 = $aRow['grouped_by_1']; - $sValue2 = $aRow['grouped_by_2']; - - $bValidResult = true; - if (is_array($aFill1) && !in_array($sValue1, $aFill1)) - { - $bValidResult = false; - } - if (is_array($aFill2) && !in_array($sValue2, $aFill2)) - { - $bValidResult = false; - } - if ($bValidResult) - { - // Build the search for this subset - $oSubsetSearch = clone $oFilter; - $oCondition = new BinaryExpression($oGroupByExp1, '=', new ScalarExpression($sValue1)); - $oSubsetSearch->AddConditionExpression($oCondition); - $oCondition = new BinaryExpression($oGroupByExp2, '=', new ScalarExpression($sValue2)); - $oSubsetSearch->AddConditionExpression($oCondition); - $sFilter = urlencode($oSubsetSearch->serialize()); - - $this->aStats[$sValue1][$sValue2] = array ( - 'href' => utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&dosearch=1&$sParams&filter=".urlencode($sFilter), - 'count' => $iCount, - ); - if (!array_key_exists($sValue1, $this->aValues1)) - { - $sDisplayValue1 = $aGroupBy['grouped_by_1']->MakeValueLabel($oFilter, $sValue1, $sValue1); // default to the raw value - $this->aValues1[$sValue1] = $sDisplayValue1; - } - if (!array_key_exists($sValue2, $this->aValues2)) - { - $sDisplayValue2 = $aGroupBy['grouped_by_2']->MakeValueLabel($oFilter, $sValue2, $sValue2); // default to the raw value - $this->aValues2[$sValue2] = $sDisplayValue2; - } - } - } - } - - public function GetPropertiesFields(DesignerForm $oForm) - { - $oField = new DesignerTextField('class', 'Class', $this->aProperties['class']); - $oForm->AddField($oField); - } - - static public function GetInfo() - { - return array( - 'label' => 'Test3D', - 'icon' => 'images/dashlet-groupby2-table.png', - 'description' => 'Group by on two dimensions', - ); - } -} - -class DashletHeatMap extends Dashlet -{ - public function __construct($sId) - { - parent::__construct($sId); - $this->aProperties['class'] = 'Contact'; - $this->aProperties['title'] = 'Test'; - } - - public function Render($oPage, $bEditMode = false, $aExtraParams = array()) - { - $sTitle = addslashes($this->aProperties['title']); - - $sId = 'chart_'.($bEditMode? 'edit_' : '').$this->sId; - $oPage->add('
'); - $aAxisX = array(0 => 'Lun', 1 => 'Ma'); - $aAxisY = array(0 => '12h', 1 => '13h'); - $aData = array( - 0 => array(1, 2), - 1 => array(3, 4), - ); - $sJSAxisX = json_encode($aAxisX); - $sJSAxisY = json_encode($aAxisY); - $sJSData = json_encode($aData); - $oPage->add_ready_script("$('#chart_{$sId}').heatmap_chart({chart_label: '$sTitle', values: $sJSData, axis_x: $sJSAxisX, axis_y: $sJSAxisY});"); - } - - public function GetPropertiesFields(DesignerForm $oForm) - { - $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']); - $oForm->AddField($oField); - - $oField = new DesignerTextField('class', 'Class', $this->aProperties['class']); - $oForm->AddField($oField); - } - - static public function GetInfo() - { - return array( - 'label' => 'Heatmap (Raphael)', - 'icon' => 'images/dashlet-heatmap.png', - 'description' => 'Pure JS Heat Map Chart', + 'description' => Dict::S('UI:DashletBadge:Description'), ); } } +?> diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index f5f82ee60..24520875d 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -656,7 +656,8 @@ class DisplayBlock $iCount = $this->m_oSet->Count(); $sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.urlencode($this->m_oFilter->serialize()); $sHtml .= '

'; - $sHtml .= MetaModel::GetClassIcon($sClass, true, 'float;left;margin-right:10px;'); + // Note: border set to 0 due to various browser interpretations (IE9 adding a 2px border) + $sHtml .= MetaModel::GetClassIcon($sClass, true, 'float;left;margin-right:10px;border:0;'); $sHtml .= MetaModel::GetName($sClass).': '.$iCount.'

'; $sParams = $oAppContext->GetForLink(); $sHtml .= '

'; diff --git a/application/forms.class.inc.php b/application/forms.class.inc.php index 387131495..d70c5432a 100644 --- a/application/forms.class.inc.php +++ b/application/forms.class.inc.php @@ -92,7 +92,9 @@ class DesignerForm $aRow = $oField->Render($oP, $sFormId); if ($oField->IsVisible()) { - $aDetails[] = array('label' => $aRow['label'], 'value' => $aRow['value']); + $sValidation = ' '.$this->GetValidationArea($oField->GetCode()).''; + $sField = $aRow['value'].$sValidation; + $aDetails[] = array('label' => $aRow['label'], 'value' => $sField); } else { @@ -152,7 +154,7 @@ class DesignerForm $sFormId = $this->sFormId; $sReturn = '

'; $sReturn .= ''; - $sReturn .= ''; + $sReturn .= ''; } else { @@ -168,13 +170,14 @@ class DesignerForm } - $sValidationFields = ''; foreach($aFields as $oField) { $aRow = $oField->Render($oP, $sFormId, 'property'); if ($oField->IsVisible()) { $sFieldId = $this->GetFieldId($oField->GetCode()); + $sValidation = $this->GetValidationArea($oField->GetCode(), ''); + $sValidationFields = ''; $sReturn .= '
PropertyValue 
'.Dict::S('UI:Form:Property').''.Dict::S('UI:Form:Value').' 
'.$sValidation.'
'.$aRow['label'].''.$aRow['value']; if (!($oField instanceof DesignerFormSelectorField)) { @@ -359,9 +362,9 @@ EOF return 'attr_'.$sCode; } - public function GetValidationArea($sCode) + public function GetValidationArea($sCode, $sContent = '') { - return "sFormPrefix}attr_$sCode\">"; + return "sFormPrefix}attr_$sCode\">$sContent"; } public function GetAsyncActionClass() { @@ -600,7 +603,6 @@ class DesignerTextField extends DesignerFormField { $sId = $this->oForm->GetFieldId($this->sCode); $sName = $this->oForm->GetFieldName($this->sCode); - $sValidation = $this->oForm->GetValidationArea($this->sCode); $sPattern = addslashes($this->sValidationPattern); $sMandatory = $this->bMandatory ? 'true' : 'false'; $sReadOnly = $this->IsReadOnly() ? 'readonly' : ''; @@ -613,7 +615,7 @@ $('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId' } EOF ); - return array('label' => $this->sLabel, 'value' => "defaultValue, ENT_QUOTES, 'UTF-8')."\">".$sValidation); + return array('label' => $this->sLabel, 'value' => "defaultValue, ENT_QUOTES, 'UTF-8')."\">"); } public function ReadParam(&$aValues) @@ -633,7 +635,6 @@ class DesignerLongTextField extends DesignerTextField { $sId = $this->oForm->GetFieldId($this->sCode); $sName = $this->oForm->GetFieldName($this->sCode); - $sValidation = $this->oForm->GetValidationArea($this->sCode); $sPattern = addslashes($this->sValidationPattern); $sMandatory = $this->bMandatory ? 'true' : 'false'; $sReadOnly = $this->IsReadOnly() ? 'readonly' : ''; @@ -646,7 +647,7 @@ $('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId' } EOF ); - return array('label' => $this->sLabel, 'value' => "".$sValidation); + return array('label' => $this->sLabel, 'value' => ""); } } @@ -688,7 +689,6 @@ class DesignerComboField extends DesignerFormField $sChecked = $this->defaultValue ? 'checked' : ''; $sMandatory = $this->bMandatory ? 'true' : 'false'; $sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : ''; - $sValidation = $this->oForm->GetValidationArea($this->sCode); if ($this->bMultipleSelection) { $sHtml = "".$sValidation); + return array('label' => $this->sLabel, 'value' => ""); } public function ReadParam(&$aValues) @@ -800,7 +799,6 @@ class DesignerHiddenField extends DesignerFormField { $sId = $this->oForm->GetFieldId($this->sCode); $sName = $this->oForm->GetFieldName($this->sCode); - $sValidation = $this->oForm->GetValidationArea($this->sCode); $sChecked = $this->defaultValue ? 'checked' : ''; return array('label' =>'', 'value' => "defaultValue, ENT_QUOTES, 'UTF-8')."\">"); } @@ -868,7 +866,6 @@ class DesignerSortableField extends DesignerFormField $bOpen = false; $sId = $this->oForm->GetFieldId($this->sCode); $sName = $this->oForm->GetFieldName($this->sCode); - $sValidation = $this->oForm->GetValidationArea($this->sCode); $sHtml = ""; foreach($this->defaultValue as $sValue) { @@ -893,7 +890,7 @@ class DesignerSortableField extends DesignerFormField $('#sortable_$sId').disableSelection(); EOF ); - return array('label' => $this->sLabel, 'value' => $sHtml.$sValidation); + return array('label' => $this->sLabel, 'value' => $sHtml); } } diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index b0e1bc07b..1ce079bf5 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -397,17 +397,6 @@ EOF EOF ); - - // Build menus from module handlers - // - foreach(get_declared_classes() as $sPHPClass) - { - if (is_subclass_of($sPHPClass, 'ModuleHandlerAPI')) - { - $aCallSpec = array($sPHPClass, 'OnMenuCreation'); - call_user_func($aCallSpec); - } - } } public function AddToMenu($sHtml) diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index d2adf4877..c1440de90 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -60,10 +60,29 @@ require_once(APPROOT."/application/user.dashboard.class.inc.php"); class ApplicationMenu { + static $bAdditionalMenusLoaded = false; static $aRootMenus = array(); static $aMenusIndex = array(); static $sFavoriteSiloQuery = 'SELECT Organization'; + static public function LoadAdditionalMenus() + { + if (!self::$bAdditionalMenusLoaded) + { + // Build menus from module handlers + // + foreach(get_declared_classes() as $sPHPClass) + { + if (is_subclass_of($sPHPClass, 'ModuleHandlerAPI')) + { + $aCallSpec = array($sPHPClass, 'OnMenuCreation'); + call_user_func($aCallSpec); + } + } + self::$bAdditionalMenusLoaded = true; + } + } + /** * Set the query used to limit the list of displayed organizations in the drop-down menu * @param $sOQL string The OQL query returning a list of Organization objects @@ -127,6 +146,7 @@ class ApplicationMenu */ static public function ReflectionMenuNodes() { + self::LoadAdditionalMenus(); return self::$aMenusIndex; } @@ -135,6 +155,7 @@ class ApplicationMenu */ static public function DisplayMenu(iTopWebPage $oPage, $aExtraParams) { + self::LoadAdditionalMenus(); // Sort the root menu based on the rank usort(self::$aRootMenus, array('ApplicationMenu', 'CompareOnRank')); $iAccordion = 0; diff --git a/core/expression.class.inc.php b/core/expression.class.inc.php index c6f2da177..270535736 100644 --- a/core/expression.class.inc.php +++ b/core/expression.class.inc.php @@ -847,15 +847,20 @@ class FunctionExpression extends Expression $oFormatExpr = $this->m_aArgs[1]; if ($oFormatExpr->Render() == "'%w'") { - static $aWeekDayToString = array( - 0 => 'Sunday', - 1 => 'Monday', - 2 => 'Tuesday', - 3 => 'Wednesday', - 4 => 'Thursday', - 5 => 'Friday', - 6 => 'Saturday', - ); + static $aWeekDayToString = null; + if (is_null($aWeekDayToString)) + { + // Init the correspondance table + $aWeekDayToString = array( + 0 => Dict::S('DayOfWeek-Sunday'), + 1 => Dict::S('DayOfWeek-Monday'), + 2 => Dict::S('DayOfWeek-Tuesday'), + 3 => Dict::S('DayOfWeek-Wednesday'), + 4 => Dict::S('DayOfWeek-Thursday'), + 5 => Dict::S('DayOfWeek-Friday'), + 6 => Dict::S('DayOfWeek-Saturday') + ); + } if (isset($aWeekDayToString[(int)$sValue])) { $sRes = $aWeekDayToString[(int)$sValue]; diff --git a/core/oql/oqlexception.class.inc.php b/core/oql/oqlexception.class.inc.php index dc913f86b..8eb3235c8 100644 --- a/core/oql/oqlexception.class.inc.php +++ b/core/oql/oqlexception.class.inc.php @@ -50,6 +50,12 @@ class OQLException extends CoreException parent::__construct($sMessage, 0); } + public function GetUserFriendlyDescription() + { + // Todo - translate all errors! + return $this->getMessage(); + } + public function getHtmlDesc($sHighlightHtmlBegin = '', $sHighlightHtmlEnd = '') { $sRet = htmlentities($this->m_MyIssue.", found '".$this->m_sUnexpected."' in: ", ENT_QUOTES, 'UTF-8'); diff --git a/core/oql/oqlinterpreter.class.inc.php b/core/oql/oqlinterpreter.class.inc.php index 5656f3bea..94a187a8e 100644 --- a/core/oql/oqlinterpreter.class.inc.php +++ b/core/oql/oqlinterpreter.class.inc.php @@ -37,6 +37,21 @@ class UnknownClassOqlException extends OqlNormalizeException { parent::__construct('Unknown class', $sInput, $oName, $aExpecting); } + + public function GetUserFriendlyDescription() + { + $sWrongClass = $this->GetWrongWord(); + $sSuggest = self::FindClosestString($sWrongClass, $this->GetSuggestions()); + + if ($sSuggest != '') + { + return Dict::Format('UI:OQL:UnknownClassAndFix', $sWrongClass, $sSuggest); + } + else + { + return Dict::Format('UI:OQL:UnknownClassNoFix', $sWrongClass); + } + } } class OqlInterpreterException extends OQLException diff --git a/css/light-grey.css b/css/light-grey.css index 0c64b524f..fc46bc73c 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -1178,6 +1178,19 @@ table.prop_table { td.prop_value { text-align: left; } +tr.itop-property-field-modified td { + background: #fbb; +} +tr.itop-property-field-modified td.prop_value.hover { + background: #fbb; +} +td.prop_value textarea, td.prop_value input[type=text]{ + width: 98%; +} +td.prop_icon { + width: 20px; +} + .dashlet { text-align:left; } @@ -1193,6 +1206,15 @@ td.prop_value { .dashlet-content .display_block { text-align:left; } +.prop_apply .ui-icon-alert { + display: none; +} +.prop_apply .ui-state-error .ui-icon-alert { + display: block; +} +.ui-state-error .ui-icon-circle-check { + display: none; +} .summary-details { float: right; margin-top: 5px; diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index 292d5369e..6aaeb32c9 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -353,6 +353,7 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Button:Search' => ' Search ', 'UI:Button:Query' => ' Query ', 'UI:Button:Ok' => 'Ok', + 'UI:Button:Save' => 'Save', 'UI:Button:Cancel' => 'Cancel', 'UI:Button:Apply' => 'Apply', 'UI:Button:Back' => ' << Back ', @@ -979,5 +980,87 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Button:MoveUp' => 'Move Up', 'UI:Button:MoveDown' => 'Move Down', + 'UI:OQL:UnknownClassAndFix' => 'Unknown class "%1$s". You may try "%2$s" instead.', + 'UI:OQL:UnknownClassNoFix' => 'Unknown class "%1$s"', + + 'UI:Dashboard:Edit' => 'Edit This Page...', + 'UI:Dashboard:Revert' => 'Revert To Original Version...', + 'UI:Dashboard:RevertConfirm' => 'Every changes made to the original version will be lost. Please confirm that you want to do this.', + + 'UI:DashletCreation:Title' => 'Create a new Dashlet', + 'UI:DashletCreation:Dashboard' => 'Dashboard', + 'UI:DashletCreation:DashletType' => 'Dashlet Type', + 'UI:DashletCreation:EditNow' => 'Edit the Dashboard', + + 'UI:DashboardEdit:Title' => 'Dashboard Editor', + 'UI:DashboardEdit:DashboardTitle' => 'Title', + 'UI:DashboardEdit:Layout' => 'Layout', + 'UI:DashboardEdit:Properties' => 'Dashboard Properties', + 'UI:DashboardEdit:Dashlets' => 'Available Dashlets', + 'UI:DashboardEdit:DashletProperties' => 'Dashlet Properties', + + 'UI:Form:Property' => 'Property', + 'UI:Form:Value' => 'Value', + + 'UI:DashletPlainText:Label' => 'Text', + 'UI:DashletPlainText:Description' => 'Plain text (no formatting)', + 'UI:DashletPlainText:Prop-Text' => 'Text', + 'UI:DashletPlainText:Prop-Text:Default' => 'Please enter some text here...', + + 'UI:DashletObjectList:Label' => 'Object list', + 'UI:DashletObjectList:Description' => 'Object list dashlet', + 'UI:DashletObjectList:Prop-Title' => 'Title', + 'UI:DashletObjectList:Prop-Query' => 'Query', + 'UI:DashletObjectList:Prop-Menu' => 'Menu', + + 'UI:DashletGroupBy:Prop-Title' => 'Title', + 'UI:DashletGroupBy:Prop-Query' => 'Query', + 'UI:DashletGroupBy:Prop-Style' => 'Style', + 'UI:DashletGroupBy:Prop-GroupBy' => 'Group by...', + 'UI:DashletGroupBy:Prop-GroupBy:Hour' => 'Hour of %1$s (0-23)', + 'UI:DashletGroupBy:Prop-GroupBy:Month' => 'Month of %1$s (1 - 12)', + 'UI:DashletGroupBy:Prop-GroupBy:DayOfWeek' => 'Day of week for %1$s', + 'UI:DashletGroupBy:Prop-GroupBy:DayOfMonth' => 'Day of month for %1$s', + 'UI:DashletGroupBy:Prop-GroupBy:Select-Hour' => '%1$s (hour)', + 'UI:DashletGroupBy:Prop-GroupBy:Select-Month' => '%1$s (month)', + 'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$s (day of week)', + 'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth' => '%1$s (day of month)', + 'UI:DashletGroupBy:MissingGroupBy' => 'Please select the field on which the objects will be grouped together', + + 'UI:DashletGroupByPie:Label' => 'Pie Chart', + 'UI:DashletGroupByPie:Description' => 'Pie Chart', + 'UI:DashletGroupByBars:Label' => 'Bar Chart', + 'UI:DashletGroupByBars:Description' => 'Bar Chart', + 'UI:DashletGroupByTable:Label' => 'Group By (table)', + 'UI:DashletGroupByTable:Description' => 'List (Grouped by a field)', + + 'UI:DashletHeaderStatic:Label' => 'Header', + 'UI:DashletHeaderStatic:Description' => 'Displays an horizontal separator', + 'UI:DashletHeaderStatic:Prop-Title' => 'Title', + 'UI:DashletHeaderStatic:Prop-Title:Default' => 'Contacts', + 'UI:DashletHeaderStatic:Prop-Icon' => 'Icon', + + 'UI:DashletHeaderDynamic:Label' => 'Header with statistics', + 'UI:DashletHeaderDynamic:Description' => 'Header with stats (grouped by...)', + 'UI:DashletHeaderDynamic:Prop-Title' => 'Title', + 'UI:DashletHeaderDynamic:Prop-Title:Default' => 'Contacts', + 'UI:DashletHeaderDynamic:Prop-Icon' => 'Icon', + 'UI:DashletHeaderDynamic:Prop-Subtitle' => 'Subtitle', + 'UI:DashletHeaderDynamic:Prop-Subtitle:Default' => 'Contacts', + 'UI:DashletHeaderDynamic:Prop-Query' => 'Query', + 'UI:DashletHeaderDynamic:Prop-GroupBy' => 'Group by', + 'UI:DashletHeaderDynamic:Prop-Values' => 'Values', + + 'UI:DashletBadge:Label' => 'Badge', + 'UI:DashletBadge:Description' => 'Object Icon with new/search', + 'UI:DashletBadge:Prop-Class' => 'Class', + + 'DayOfWeek-Sunday' => 'Sunday', + 'DayOfWeek-Monday' => 'Monday', + 'DayOfWeek-Tuesday' => 'Tuesday', + 'DayOfWeek-Wednesday' => 'Wednesday', + 'DayOfWeek-Thursday' => 'Thursday', + 'DayOfWeek-Friday' => 'Friday', + 'DayOfWeek-Saturday' => 'Saturday', )); ?> diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index fa6c57fd4..2d65bcd50 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -235,6 +235,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Button:GlobalSearch' => 'Rechercher', 'UI:Button:Search' => 'Rechercher', 'UI:Button:Query' => ' Lancer la requête ', + 'UI:Button:Save' => 'Sauver', 'UI:Button:Ok' => 'Ok', 'UI:Button:Cancel' => 'Annuler', 'UI:Button:Apply' => 'Appliquer', @@ -823,5 +824,87 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:Button:MoveUp' => 'Monter', 'UI:Button:MoveDown' => 'Descendre', + 'UI:OQL:UnknownClassAndFix' => 'La classe "%1$s" est inconnue. Essayez plutôt "%2$s".', + 'UI:OQL:UnknownClassNoFix' => 'La classe "%1$s" est inconnue', + + 'UI:Dashboard:Edit' => 'Editer cette page...', + 'UI:Dashboard:Revert' => 'Revenir à la version d\'origine...', + 'UI:Dashboard:RevertConfirm' => 'Toutes modifications apportées à la version d\'origine seront perdues. Veuillez confirmer l\'opération.', + + 'UI:DashletCreation:Title' => 'Créer un Indicateur', + 'UI:DashletCreation:Dashboard' => 'Tableau de bord', + 'UI:DashletCreation:DashletType' => 'Type d\'Indicateur', + 'UI:DashletCreation:EditNow' => 'Modifier le tableau de bord', + + 'UI:DashboardEdit:Title' => 'Editeur de tableau de bord', + 'UI:DashboardEdit:DashboardTitle' => 'Titre', + 'UI:DashboardEdit:Layout' => 'Mise en page', + 'UI:DashboardEdit:Properties' => 'Propriétés du tableau de bord', + 'UI:DashboardEdit:Dashlets' => 'Indicateurs', + 'UI:DashboardEdit:DashletProperties' => 'Propriétés de l\'Indicateur', + + 'UI:Form:Property' => 'Propriété', + 'UI:Form:Value' => 'Valeur', + + 'UI:DashletPlainText:Label' => 'Texte', + 'UI:DashletPlainText:Description' => 'Text pur (pas de mise en forme)', + 'UI:DashletPlainText:Prop-Text' => 'Texte', + 'UI:DashletPlainText:Prop-Text:Default' => 'Veuillez saisir votre texte ici...', + + 'UI:DashletObjectList:Label' => 'Liste d\'objets', + 'UI:DashletObjectList:Description' => 'Liste d\'objets', + 'UI:DashletObjectList:Prop-Title' => 'Titre', + 'UI:DashletObjectList:Prop-Query' => 'Requête OQL', + 'UI:DashletObjectList:Prop-Menu' => 'Menu', + + 'UI:DashletGroupBy:Prop-Title' => 'Titre', + 'UI:DashletGroupBy:Prop-Query' => 'Requête OQL', + 'UI:DashletGroupBy:Prop-Style' => 'Style', + 'UI:DashletGroupBy:Prop-GroupBy' => 'Grouper par', + 'UI:DashletGroupBy:Prop-GroupBy:Hour' => 'Heure de %1$s (0-23)', + 'UI:DashletGroupBy:Prop-GroupBy:Month' => 'Mois de %1$s (1 - 12)', + 'UI:DashletGroupBy:Prop-GroupBy:DayOfWeek' => 'Jour de la semaine pour %1$s', + 'UI:DashletGroupBy:Prop-GroupBy:DayOfMonth' => 'Jour du mois pour %1$s', + 'UI:DashletGroupBy:Prop-GroupBy:Select-Hour' => '%1$s (heure)', + 'UI:DashletGroupBy:Prop-GroupBy:Select-Month' => '%1$s (mois)', + 'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$s (jour de la semaine)', + 'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth' => '%1$s (jour du mois)', + 'UI:DashletGroupBy:MissingGroupBy' => 'Veuillez sélectionner le champ sur lequel les objets seront groupés', + + 'UI:DashletGroupByPie:Label' => 'Secteurs', + 'UI:DashletGroupByPie:Description' => 'Graphique à secteur', + 'UI:DashletGroupByBars:Label' => 'Barres', + 'UI:DashletGroupByBars:Description' => 'Graphique en Barres', + 'UI:DashletGroupByTable:Label' => 'Table', + 'UI:DashletGroupByTable:Description' => 'Table', + + 'UI:DashletHeaderStatic:Label' => 'En-tête', + 'UI:DashletHeaderStatic:Description' => 'En-tête présenté comme une barre horizontale', + 'UI:DashletHeaderStatic:Prop-Title' => 'Titre', + 'UI:DashletHeaderStatic:Prop-Title:Default' => 'Contacts', + 'UI:DashletHeaderStatic:Prop-Icon' => 'Icône', + + 'UI:DashletHeaderDynamic:Label' => 'En-tête dynamique', + 'UI:DashletHeaderDynamic:Description' => 'En-tête avec statistiques (regroupements)', + 'UI:DashletHeaderDynamic:Prop-Title' => 'Titre', + 'UI:DashletHeaderDynamic:Prop-Title:Default' => 'Contacts', + 'UI:DashletHeaderDynamic:Prop-Icon' => 'Icône', + 'UI:DashletHeaderDynamic:Prop-Subtitle' => 'Sous-titre', + 'UI:DashletHeaderDynamic:Prop-Subtitle:Default' => 'Contacts', + 'UI:DashletHeaderDynamic:Prop-Query' => 'Requête OQL', + 'UI:DashletHeaderDynamic:Prop-GroupBy' => 'Grouper par', + 'UI:DashletHeaderDynamic:Prop-Values' => 'Valeurs', + + 'UI:DashletBadge:Label' => 'Badge', + 'UI:DashletBadge:Description' => 'Icône représentant une classe d\'objets, ainsi que des liens pour créer/rechercher', + 'UI:DashletBadge:Prop-Class' => 'Classe', + + 'DayOfWeek-Sunday' => 'Dimanche', + 'DayOfWeek-Monday' => 'Lundi', + 'DayOfWeek-Tuesday' => 'Mardi', + 'DayOfWeek-Wednesday' => 'Mercredi', + 'DayOfWeek-Thursday' => 'Jeudi', + 'DayOfWeek-Friday' => 'Vendredi', + 'DayOfWeek-Saturday' => 'Samedi', )); ?> diff --git a/js/property_field.js b/js/property_field.js index 2f9d07864..d11615045 100644 --- a/js/property_field.js +++ b/js/property_field.js @@ -39,11 +39,15 @@ $(function() { if (this.bModified) { - this.element.find(".prop_icon span.ui-icon").css({visibility: ''}); + this.element.addClass("itop-property-field-modified"); + this.element.find(".prop_icon span.ui-icon-circle-check").css({visibility: ''}); + this.element.find(".prop_icon span.ui-icon-circle-close").css({visibility: ''}); } else { - this.element.find(".prop_icon span.ui-icon").css({visibility: 'hidden'}); + this.element.removeClass("itop-property-field-modified"); + this.element.find(".prop_icon span.ui-icon-circle-check").css({visibility: 'hidden'}); + this.element.find(".prop_icon span.ui-icon-circle-close").css({visibility: 'hidden'}); } }, @@ -52,6 +56,7 @@ $(function() _destroy: function() { this.element.removeClass( "itop-property-field" ); + this.element.removeClass("itop-property-field-modified"); }, // _setOptions is called with a hash of all options that are changing @@ -211,13 +216,13 @@ function ValidateWithPattern(sFieldId, bMandatory, sPattern, sFormId) } if (!bValid) { - $('#v_'+sFieldId).html(''); + $('#v_'+sFieldId).addClass('ui-state-error'); if (oFormValidation[sFormId] == undefined) oFormValidation[sFormId] = []; oFormValidation[sFormId].push(sFieldId); } else { - $('#v_'+sFieldId).html(''); + $('#v_'+sFieldId).removeClass('ui-state-error'); } } diff --git a/pages/UI.php b/pages/UI.php index e4e8c98a7..d1e2e2f42 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -2159,6 +2159,7 @@ EOF /////////////////////////////////////////////////////////////////////////////////////////// default: // Menu node rendering (templates) + ApplicationMenu::LoadAdditionalMenus(); $oMenuNode = ApplicationMenu::GetMenuNode(ApplicationMenu::GetMenuIndexById(ApplicationMenu::GetActiveNodeId())); if (is_object($oMenuNode)) { diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 651f07835..7239cb6c5 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -672,18 +672,7 @@ try case 'dashboard_editor': $sId = utils::ReadParam('id', '', false, 'raw_data'); - - // Before searching for the menus make sure that all of them exist - // Build menus from module handlers - // - foreach(get_declared_classes() as $sPHPClass) - { - if (is_subclass_of($sPHPClass, 'ModuleHandlerAPI')) - { - $aCallSpec = array($sPHPClass, 'OnMenuCreation'); - call_user_func($aCallSpec); - } - } + ApplicationMenu::LoadAdditionalMenus(); $idx = ApplicationMenu::GetMenuIndexById($sId); $oMenu = ApplicationMenu::GetMenuNode($idx); $oMenu->RenderEditor($oPage); @@ -808,7 +797,7 @@ try break; case 'add_dashlet': - $oForm = RuntimeDashboard::GetDashletCreationForm(''); + $oForm = RuntimeDashboard::GetDashletCreationForm(); $aValues = $oForm->ReadParams(); $sDashletClass = $aValues['dashlet_class']; @@ -819,6 +808,7 @@ try $oDashlet = new $sDashletClass(0); $oDashlet->FromParams($aValues); + ApplicationMenu::LoadAdditionalMenus(); $index = ApplicationMenu::GetMenuIndexById($sMenuId); $oMenu = ApplicationMenu::GetMenuNode($index); $oMenu->AddDashlet($oDashlet);