diff --git a/application/applicationextension.inc.php b/application/applicationextension.inc.php index 209f1424c..37921b0c7 100644 --- a/application/applicationextension.inc.php +++ b/application/applicationextension.inc.php @@ -1,5 +1,5 @@ +/** + * New extension to add menu items in the "popup" menus inside iTop. Provides a greater flexibility than + * iApplicationUIExtension::EnumAllowedActions. + * + * To add some menus into iTop, declare a class that implements this interface, it will be called automatically + * by the application, as long as the class definition is included somewhere in the code + */ +interface iPopupMenuExtension +{ + // Possible types of menu into which new items can be added + const MENU_OBJLIST_ACTIONS = 1; // $param is a DBObjectSet containing the list of objects + const MENU_OBJLIST_TOOLKIT = 2; // $param is a DBObjectSet containing the list of objects + const MENU_OBJDETAILS_ACTIONS = 3; // $param is a DBObject instance: the object currently displayed + const MENU_DASHBOARD_ACTIONS = 4; // $param is a Dashboard instance: the dashboard currently displayed + const MENU_USER_ACTIONS = 5; // $param is a null ?? + + /** + * Get the list of items to be added to a menu. The items will be inserted in the menu in the order of the returned array + * @param int $iMenuId The identifier of the type of menu, as listed by the constants MENU_xxx above + * @param mixed $param Depends on $iMenuId, see the constants defined above + * @return Array An array of ApplicationPopupMenuItem or an empty array if no action is to be added to the menu + */ + public static function EnumItems($iMenuId, $param); +} + +/** + * Each menu items is defined by an instance of an object derived from the class + * ApplicationPopupMenu below + * + */ +abstract class ApplicationPopupMenuItem +{ + protected $sUID; + protected $sLabel; + + public function __construct($sUID, $sLabel) + { + $this->sUID = $sUID; + $this->sLabel = $sLabel; + } + + public function GetUID() + { + return $this->sUID; + } + + public function GetLabel() + { + return $this->sLabel; + } + + /** + * Returns the components to create a popup menu item in HTML + * @return Hash A hash array: array('label' => , 'url' => , 'target' => , 'onclick' => ) + */ + abstract public function GetMenuItem(); + + public function GetLinkedScripts() + { + return array(); + } +} + +/** + * Class for adding an item into a popup menu that browses to the given URL + */ +class URLPopupMenuItem extends ApplicationPopupMenuItem +{ + protected $sURL; + protected $sTarget; + + /** + * Class for adding an item that browses to the given URL + * @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough + * @param string $sLabel The display label of the menu (must be localized) + * @param string $sURL If the menu is an hyperlink, provide the absolute hyperlink here + * @param string $sTarget In case the menu is an hyperlink and a specific target is needed (_blank for example), pass it here + */ + public function __construct($sUID, $sLabel, $sURL, $sTarget = '_top') + { + parent::__construct($sUID, $sLabel); + $this->sURL = $sURL; + $this->sTarget = $sTarget; + } + + public function GetMenuItem() + { + return array ('label' => $this->GetLabel(), 'url' => $this->sURL, 'target' => $this->sTarget); + } +} + +/** + * Class for adding an item into a popup menu that triggers some Javascript code + */ +class JSPopupMenuItem extends ApplicationPopupMenuItem +{ + protected $sJSCode; + protected $aIncludeJSFiles; + + /** + * Class for adding an item that triggers some Javascript code + * @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough + * @param string $sLabel The display label of the menu (must be localized) + * @param string $sJSCode In case the menu consists in executing some havascript code inside the page, pass it here. If supplied $sURL ans $sTarget will be ignored + * @param array $aIncludeJSFiles An array of file URLs to be included (once) to provide some JS libraries for the page. + */ + public function __construct($sUID, $sLabel, $sJSCode, $aIncludeJSFiles = array()) + { + parent::__construct($sUID, $sLabel); + $this->sJSCode = $sJSCode; + $this->aIncludeJSFiles = $aIncludeJSFiles; + } + + public function GetMenuItem() + { + return array ('label' => $this->GetLabel(), 'onclick' => $this->sJSCode, 'url' => '#'); + } + + public function GetLinkedScripts() + { + return $this->aIncludeJSFiles; + } +} + +/** + * Class for adding a separator (horizontal line, not selectable) the output + * will automatically reduce several consecutive separators to just one + */ +class SeparatorPopupMenuItem extends ApplicationPopupMenuItem +{ + /** + * Class for inserting a separator into a popup menu + */ + public function __construct() + { + parent::__construct('', ''); + } + + public function GetMenuItem() + { + return array ('label' => '', 'url' => ''); + } +} diff --git a/application/dashboard.class.inc.php b/application/dashboard.class.inc.php index a97843c51..0b92ff214 100644 --- a/application/dashboard.class.inc.php +++ b/application/dashboard.class.inc.php @@ -40,8 +40,11 @@ abstract class Dashboard public function FromXml($sXml) { + $this->aCells = array(); // reset the content of the dashboard + set_error_handler(array('Dashboard', 'ErrorHandler')); $oDoc = new DOMDocument(); $oDoc->loadXML($sXml); + restore_error_handler(); $this->oDOMNode = $oDoc->getElementsByTagName('dashboard')->item(0); $oLayoutNode = $this->oDOMNode->getElementsByTagName('layout')->item(0); @@ -68,6 +71,21 @@ abstract class Dashboard } } + /** + * Error handler to turn XML loading warnings into exceptions + */ + public static function ErrorHandler($errno, $errstr, $errfile, $errline) + { + if ($errno == E_WARNING && (substr_count($errstr,"DOMDocument::loadXML()")>0)) + { + throw new DOMException($errstr); + } + else + { + return false; + } + } + public function ToXml() { $oDoc = new DOMDocument(); @@ -365,13 +383,21 @@ class RuntimeDashboard extends Dashboard if (!$bEditMode) { $sEditMenu = ""; + utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_DASHBOARD_ACTIONS, $this, $aActions); + $sEditMenu .= $oPage->RenderPopupMenuItems($aActions); + + $sEditMenu = addslashes($sEditMenu); //$sEditBtn = addslashes('
'); $oPage->add_ready_script( diff --git a/application/datatable.class.inc.php b/application/datatable.class.inc.php index ba4f0d0e1..44bd7e3c8 100644 --- a/application/datatable.class.inc.php +++ b/application/datatable.class.inc.php @@ -21,10 +21,11 @@ * @author Denis Flaven * @license http://www.opensource.org/licenses/gpl-3.0.html GPL */ + class DataTable { protected $iListId; // Unique ID inside the web page - protected $sTableId; // identifier for sqve the settings (combined with the class aliases) + protected $sTableId; // identifier for saving the settings (combined with the class aliases) protected $oSet; // The set of objects to display protected $aClassAliases; // The aliases (alias => class) inside the set protected $iNbObjects; // Total number of objects inthe set @@ -250,8 +251,16 @@ EOF; protected function GetToolkitMenu(WebPage $oPage, $aExtraParams) { $sMenuTitle = Dict::S('UI:ConfigureThisList'); - $sHtml = '
'; - //$oPage->add_ready_script("$('#tk_{$this->iListId} > ul').popupmenu();"); + $sHtml = '
"; + foreach(array_reverse($aFavoriteActions) as $aAction) + { + $sHtml .= "
{$aAction['label']}
"; + } + + return $sHtml; + } } ?> \ No newline at end of file diff --git a/datamodel/itop-attachments/main.attachments.php b/datamodel/itop-attachments/main.attachments.php index dd94643de..7b79969ed 100644 --- a/datamodel/itop-attachments/main.attachments.php +++ b/datamodel/itop-attachments/main.attachments.php @@ -356,7 +356,6 @@ EOF $oPage->add('
'); $sMaxUpload = $this->GetMaxUpload(); $oPage->p(Dict::S('Attachments:AddAttachment').' '.$sMaxUpload); - //$oPage->p(''); $oPage->p(''); $oPage->p(''); $oPage->add(''); diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index a5d70bf00..2221f7cb2 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -988,6 +988,11 @@ When associated with a trigger, each action is given an "order" number, specifyi '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:ExportDashBoard' => 'Export to a file', + 'UI:ImportDashBoard' => 'Import from a file...', + 'UI:ImportDashboardTitle' => 'Import From a File', + 'UI:ImportDashboardText' => 'Select a dashboard file to import:', + 'UI:DashletCreation:Title' => 'Create a new Dashlet', 'UI:DashletCreation:Dashboard' => 'Dashboard', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index d69dafb2e..0ee6b2230 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -832,6 +832,10 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé '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:ExportDashBoard' => 'Exporter dans un fichier', + 'UI:ImportDashBoard' => 'Importer depuis un fichier...', + 'UI:ImportDashboardTitle' => 'Importation depuis un fichier', + 'UI:ImportDashboardText' => 'Choisissez un fichier de définition de tableau de bord :', 'UI:DashletCreation:Title' => 'Créer un Indicateur', 'UI:DashletCreation:Dashboard' => 'Tableau de bord', diff --git a/js/dashboard.js b/js/dashboard.js index a8581c12a..cda3c16a8 100644 --- a/js/dashboard.js +++ b/js/dashboard.js @@ -219,4 +219,124 @@ $(function() }); } }); -}); \ No newline at end of file +}); + +function UploadDashboard(oOptions) +{ + var sFileId = 'dashboard_upload_file'; + var oDlg = $('

'+oOptions.text+'

'); + $('body').append(oDlg); + oOptions.file_id = sFileId; + + oDlg.dashboard_upload_dlg(oOptions); +} + + +//jQuery UI style "widget" for managing a "import dashboard" dialog (file upload) +$(function() +{ + // the widget definition, where "itop" is the namespace, + // "dashboard-upload-dlg" the widget name + $.widget( "itop.dashboard_upload_dlg", + { + // default options + options: + { + dashboard_id: '', + file_id: '', + text: 'Select a dashboard file to import', + title: 'Dahsboard Import', + close_btn: 'Close', + submit_to: GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?operation=import_dashboard' + }, + + // the constructor + _create: function() + { + var me = this; + + var oButtons = {}; + oButtons[this.options.close_btn] = function() { + me.element.dialog('close'); + //me.onClose(); + }; + $('#'+this.options.file_id).bind('change', function() { me._doUpload(); } ); + this.element + .addClass('itop-dashboard_upload_dlg') + .dialog({ + modal: true, + width: 500, + height: 'auto', + title: this.options.title, + close: function() { me._onClose(); }, + buttons: oButtons + }); + }, + + // called when created, and later when changing options + _refresh: function() + { + }, + // events bound via _bind are removed automatically + // revert other modifications here + destroy: function() + { + this.element + .removeClass('itop-dashboard_upload_dlg'); + + // call the original destroy method since we overwrote it + $.Widget.prototype.destroy.call( this ); + }, + // _setOptions is called with a hash of all options that are changing + _setOptions: function() + { + // in 1.9 would use _superApply + $.Widget.prototype._setOptions.apply( this, arguments ); + this._refresh(); + }, + // _setOption is called for each individual option that is changing + _setOption: function( key, value ) + { + // in 1.9 would use _super + $.Widget.prototype._setOption.call( this, key, value ); + }, + _onClose: function() + { + this.element.remove(); + }, + _doUpload: function() + { + var me = this; + $.ajaxFileUpload + ( + { + url: me.options.submit_to+'&id='+me.options.dashboard_id, + secureuri:false, + fileElementId: me.options.file_id, + dataType: 'json', + success: function (data, status) + { + if(typeof(data.error) != 'undefined') + { + if(data.error != '') + { + alert(data.error); + me.element.dialog('close'); + } + else + { + me.element.dialog('close'); + location.reload(); + } + } + }, + error: function (data, status, e) + { + alert(e); + me.element.dialog('close'); + } + } + ) + } + }); +}); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 382239a0c..504529c14 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -820,6 +820,56 @@ try } break; + case 'export_dashboard': + $sMenuId = utils::ReadParam('id', ''); + ApplicationMenu::LoadAdditionalMenus(); + $index = ApplicationMenu::GetMenuIndexById($sMenuId); + $oMenu = ApplicationMenu::GetMenuNode($index); + if ($oMenu instanceof DashboardMenuNode) + { + $oDashboard = $oMenu->GetDashboard(); + $sPreviousContent = ob_get_clean(); + if (trim($sPreviousContent) != '') + { + IssueLog::Error("Output already started before downloading file:\nContent was:'$sPreviousContent'\n"); + } + $oPage->SetContentType('text/xml'); + $oPage->SetContentDisposition('attachment', $oMenu->GetLabel().'.xml'); + $oPage->add($oDashboard->ToXml()); + } + break; + + case 'import_dashboard': + $sMenuId = utils::ReadParam('id', ''); + ApplicationMenu::LoadAdditionalMenus(); + $index = ApplicationMenu::GetMenuIndexById($sMenuId); + $oMenu = ApplicationMenu::GetMenuNode($index); + $aResult = array('error' => ''); + try + { + if ($oMenu instanceof DashboardMenuNode) + { + $oDoc = utils::ReadPostedDocument('dashboard_upload_file'); + $oDashboard = $oMenu->GetDashboard(); + $oDashboard->FromXml($oDoc->GetData()); + $oDashboard->Save(); + } + else + { + $aResult['error'] = 'Dashboard id="'.$sMenuId.'" not found.'; + } + } + catch(DOMException $e) + { + $aResult = array('error' => Dict::S('UI:Error:InvalidDashboardFile')); + } + catch(Exception $e) + { + $aResult = array('error' => $e->getMessage()); + } + $oPage->add(json_encode($aResult)); + break; + default: $oPage->p("Invalid query."); }