From 7792b54d2617ddae4834fb465a1e66ebc69e126c Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 16 Nov 2012 11:21:00 +0000 Subject: [PATCH] New feature: shortcuts to a search result. The feature is not automatically available with upgrade of custom versions -requires a ShortcutContainerMenuNode. SVN:trunk[2431] --- application/ajaxwebpage.class.inc.php | 26 ++ application/displayblock.class.inc.php | 1 + application/menunode.class.inc.php | 156 +++++++++-- application/shortcut.class.inc.php | 257 ++++++++++++++++++ application/utils.inc.php | 22 ++ .../main.itop-welcome-itil.php | 4 +- .../datamodel.itop-welcome-itil.xml | 6 +- dictionaries/dictionary.itop.ui.php | 15 + dictionaries/fr.dictionary.itop.ui.php | 15 + js/datatable.js | 22 ++ js/utils.js | 8 + pages/ajax.render.php | 63 +++++ pages/preferences.php | 96 ++++++- setup/compiler.class.inc.php | 13 +- 14 files changed, 677 insertions(+), 27 deletions(-) create mode 100644 application/shortcut.class.inc.php diff --git a/application/ajaxwebpage.class.inc.php b/application/ajaxwebpage.class.inc.php index 8679747ae..d6d2a3783 100644 --- a/application/ajaxwebpage.class.inc.php +++ b/application/ajaxwebpage.class.inc.php @@ -36,6 +36,7 @@ class ajax_page extends WebPage protected $m_sCurrentTab; protected $m_sCurrentTabContainer; protected $m_aTabs; + private $m_sMenu; // If set, then the menu will be updated /** * constructor for the web page @@ -52,6 +53,7 @@ class ajax_page extends WebPage $this->m_aTabs = array(); $this->sContentType = 'text/html'; $this->sContentDisposition = 'inline'; + $this->m_sMenu = ""; } public function AddTabContainer($sTabContainer, $sPrefix = '') @@ -93,6 +95,11 @@ class ajax_page extends WebPage return $this->m_sCurrentTab; } + public function AddToMenu($sHtml) + { + $this->m_sMenu .= $sHtml; + } + /** * Echoes the content of the whole page * @return void @@ -221,6 +228,24 @@ EOF { echo $this->s_content; } + if (!empty($this->m_sMenu)) + { + $uid = time(); + echo "
\n"; + echo "
\n"; + echo "\n"; + echo self::FilterXSS($this->m_sMenu); + echo "\n"; + echo "
\n"; + echo "
\n"; + + echo "\n"; + } + //echo $this->s_deferred_content; if (count($this->a_scripts) > 0) { @@ -240,6 +265,7 @@ EOF echo $this->m_sReadyScript; // Ready Scripts are output as simple scripts echo "\n\n"; } + if (trim($s_captured_output) != "") { echo self::FilterXSS($s_captured_output); diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 10df7c912..5b430845b 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -1614,6 +1614,7 @@ class ExtraMenus implements iPopupMenuExtension new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?body=".urlencode($sUrl)), new URLPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), $sUrl."&format=csv"), new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL')"), + new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sContext')"), ); break; diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index 2f2ecee85..024279d8a 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -80,6 +80,15 @@ class ApplicationMenu call_user_func($aCallSpec); } } + + // Build menus from the menus themselves (e.g. the ShortcutContainerMenuNode will do that) + // + foreach(self::$aRootMenus as $aMenu) + { + $oMenuNode = self::GetMenuNode($aMenu['index']); + $oMenuNode->PopulateChildMenus(); + } + self::$bAdditionalMenusLoaded = true; } } @@ -131,7 +140,7 @@ class ApplicationMenu // they were not used to display the menus (redundant or unused) // $aBacktrace = debug_backtrace(); - $sFile = $aBacktrace[2]["file"]; + $sFile = isset($aBacktrace[2]["file"]) ? $aBacktrace[2]["file"] : $aBacktrace[1]["file"]; self::$aMenusIndex[$index] = array('node' => $oMenuNode, 'children' => array(), 'parent' => $sParentId, 'rank' => $fRank, 'source_file' => $sFile); } else @@ -154,7 +163,7 @@ class ApplicationMenu /** * Entry point to display the whole menu into the web page, used by iTopWebPage */ - static public function DisplayMenu(iTopWebPage $oPage, $aExtraParams) + static public function DisplayMenu($oPage, $aExtraParams) { self::LoadAdditionalMenus(); // Sort the root menu based on the rank @@ -253,11 +262,11 @@ class ApplicationMenu /** * Helper function to get the list of child(ren) of a menu */ - static protected function GetChildren($index) + static public function GetChildren($index) { return self::$aMenusIndex[$index]['children']; } - + /** * Helper function to get the ID of a menu based on its name * @param string $sTitle Title of the menu (as passed when creating the menu) @@ -407,6 +416,16 @@ abstract class MenuNode return $this->index; } + public function PopulateChildMenus() + { + foreach (ApplicationMenu::GetChildren($this->GetIndex()) as $aMenu) + { + $index = $aMenu['index']; + $oMenu = ApplicationMenu::GetMenuNode($index); + $oMenu->PopulateChildMenus(); + } + } + public function GetHyperlink($aExtraParams) { $aExtraParams['c[menu]'] = $this->GetMenuId(); @@ -617,31 +636,38 @@ class OQLMenuNode extends MenuNode public function RenderContent(WebPage $oPage, $aExtraParams = array()) { - $aExtraParams = array_merge($aExtraParams, $this->m_aParams); - try - { - $oSearch = DBObjectSearch::FromOQL($this->sOQL); - $sIcon = MetaModel::GetClassIcon($oSearch->GetClass()); - } - catch(Exception $e) - { - $sIcon = ''; - } + OQLMenuNode::RenderOQLSearch + ( + $this->sOQL, + Dict::S($this->sPageTitle), + 'Menu_'.$this->GetMenuId(), + $this->bSearch, // Search pane + true, // Search open + $oPage, + $aExtraParams + ); + } - if ($this->bSearch) + public static function RenderOQLSearch($sOql, $sTitle, $sUsageId, $bSearchPane, $bSearchOpen, WebPage $oPage, $aExtraParams = array()) + { + $oSearch = DBObjectSearch::FromOQL($sOql); + $sIcon = MetaModel::GetClassIcon($oSearch->GetClass()); + + if ($bSearchPane) { - $aParams = array_merge(array('open' => true, 'table_id' => 'Menu_'.$this->GetMenuId()), $aExtraParams); + $aParams = array_merge(array('open' => $bSearchOpen, 'table_id' => $sUsageId), $aExtraParams); $oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams); $oBlock->Display($oPage, 0); } - $oPage->add("

$sIcon ".Dict::S($this->sPageTitle)."

"); + $oPage->add("

$sIcon ".Dict::S($sTitle)."

"); - $aParams = array_merge(array('table_id' => 'Menu_'.$this->GetMenuId()), $aExtraParams); + $aParams = array_merge(array('table_id' => $sUsageId), $aExtraParams); $oBlock = new DisplayBlock($oSearch, 'list', false /* Asynchronous */, $aParams); - $oBlock->Display($oPage, 1); + $oBlock->Display($oPage, $sUsageId); } } + /** * This class defines a menu item that displays a search form for the given class of objects */ @@ -893,3 +919,95 @@ class DashboardMenuNode extends MenuNode } +/** + * A shortcut container is the preferred destination of newly created shortcuts + */ +class ShortcutContainerMenuNode extends MenuNode +{ + public function GetHyperlink($aExtraParams) + { + return ''; + } + + public function RenderContent(WebPage $oPage, $aExtraParams = array()) + { + } + + public function PopulateChildMenus() + { + // Load user shortcuts in DB + // + $oBMSearch = new DBObjectSearch('Shortcut'); + $oBMSearch->AddCondition('user_id', UserRights::GetUserId(), '='); + $oBMSet = new DBObjectSet($oBMSearch, array('friendlyname' => true)); // ascending on friendlyname + $fRank = 1; + while ($oShortcut = $oBMSet->Fetch()) + { + $sName = $this->GetMenuId().'_'.$oShortcut->GetKey(); + $oShortcutMenu = new ShortcutMenuNode($sName, $oShortcut, $this->GetIndex(), $fRank++); + } + + // Complete the tree + // + parent::PopulateChildMenus(); + } +} + + +require_once(APPROOT.'application/shortcut.class.inc.php'); +/** + * This class defines a menu item which content is a shortcut. + */ +class ShortcutMenuNode extends MenuNode +{ + protected $oShortcut; + + /** + * 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 object $oShortcut Shortcut object + * @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, $oShortcut, $iParentIndex, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null) + { + parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus); + $this->oShortcut = $oShortcut; + $this->aReflectionProperties['shortcut'] = $oShortcut->GetKey(); + } + + public function GetHyperlink($aExtraParams) + { + $sContext = $this->oShortcut->Get('context'); + $aContext = unserialize($sContext); + if (isset($aContext['menu'])) + { + unset($aContext['menu']); + } + foreach ($aContext as $sArgName => $sArgValue) + { + $aExtraParams[$sArgName] = $sArgValue; + } + return parent::GetHyperlink($aExtraParams); + } + + public function RenderContent(WebPage $oPage, $aExtraParams = array()) + { + $this->oShortcut->RenderContent($oPage, $aExtraParams); + } + + public function GetTitle() + { + return $this->oShortcut->Get('name'); + } + + public function GetLabel() + { + return $this->oShortcut->Get('name'); + } +} + diff --git a/application/shortcut.class.inc.php b/application/shortcut.class.inc.php new file mode 100644 index 000000000..1fdb5d9a1 --- /dev/null +++ b/application/shortcut.class.inc.php @@ -0,0 +1,257 @@ + + + +/** + * Persistent class Shortcut and derived + * Shortcuts of any kind + * + * @copyright Copyright (C) 2010-2012 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + +abstract class Shortcut extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb,view_in_gui,application", + "key_type" => "autoincrement", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_shortcut", + "db_key_field" => "id", + "db_finalclass_field" => "realclass", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("targetclass"=>"User", "allowed_values"=>null, "sql"=>"user_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("context", array("allowed_values"=>null, "sql"=>"context", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'context')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name')); // Attributes to be displayed for a list + // Search criteria +// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form +// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } + + abstract public function RenderContent(WebPage $oPage, $aExtraParams = array()); + + protected function OnInsert() + { + $this->Set('user_id', UserRights::GetUserId()); + } + + public function StartRenameDialog($oPage) + { + $oPage->add('
'); + + $oForm = new DesignerForm(); + $sDefault = $this->Get('name'); + $oField = new DesignerTextField('name', Dict::S('Class:Shortcut/Attribute:name'), $sDefault); + $oField->SetMandatory(true); + $oForm->AddField($oField); + $oForm->Render($oPage); + $oPage->add('
'); + + $sDialogTitle = Dict::S('UI:ShortcutRenameDlg:Title'); + $sOkButtonLabel = Dict::S('UI:Button:Ok'); + $sCancelButtonLabel = Dict::S('UI:Button:Cancel'); + $iShortcut = $this->GetKey(); + + $oPage->add_ready_script( +<< "core/cmdb,view_in_gui,application", + "key_type" => "autoincrement", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_shortcut_oql", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeOQL("oql", array("allowed_values"=>null, "sql"=>"oql", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'context', 'oql')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name')); // Attributes to be displayed for a list + // Search criteria +// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form +// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } + + public function RenderContent(WebPage $oPage, $aExtraParams = array()) + { + $oPage->set_title($this->Get('name')); + + $bSearchPane = true; + $bSearchOpen = false; + try + { + OQLMenuNode::RenderOQLSearch($this->Get('oql'), $this->Get('name'), 'shortcut_'.$this->GetKey(), $bSearchPane, $bSearchOpen, $oPage, $aExtraParams); + } + catch (Exception $e) + { + throw new Exception("The OQL shortcut '".$this->Get('name')."' (id: ".$this->GetKey().") could not be displayed: ".$e->getMessage()); + } + + } + + public static function GetCreationForm($sOQL = null) + { + $oForm = new DesignerForm(); + + // Find a unique default name + // -> The class of the query + an index if necessary + if ($sOQL == null) + { + $sDefault = ''; + } + else + { + $oBMSearch = new DBObjectSearch('Shortcut'); + $oBMSearch->AddCondition('user_id', UserRights::GetUserId(), '='); + $oBMSet = new DBObjectSet($oBMSearch); + $aNames = $oBMSet->GetColumnAsArray('name'); + $oSearch = DBObjectSearch::FromOQL($sOQL); + $sDefault = utils::MakeUniqueName($oSearch->GetClass(), $aNames); + } + + $oField = new DesignerTextField('name', Dict::S('Class:Shortcut/Attribute:name'), $sDefault); + $oField->SetMandatory(true); + $oForm->AddField($oField); + + //$oField = new DesignerLongTextField('oql', Dict::S('Class:Shortcut/Attribute:oql'), $sOQL); + //$oField->SetMandatory(); + $oField = new DesignerHiddenField('oql', '', $sOQL); + $oForm->AddField($oField); + + return $oForm; + } + + public static function GetCreationDlgFromOQL($oPage, $sOQL) + { + $oPage->add('
'); + + $oForm = self::GetCreationForm($sOQL); + + $oForm->Render($oPage); + $oPage->add('
'); + + $sDialogTitle = Dict::S('UI:ShortcutListDlg:Title'); + $sOkButtonLabel = Dict::S('UI:Button:Ok'); + $sCancelButtonLabel = Dict::S('UI:Button:Cancel'); + + $oAppContext = new ApplicationContext(); + $sContext = $oAppContext->GetForLink(); + + $oPage->add_ready_script( +<< diff --git a/application/utils.inc.php b/application/utils.inc.php index a26c25ce7..05c91f489 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -819,5 +819,27 @@ class utils $sUrl = self::GetAbsoluteUrlAppRoot().'env-'.self::GetCurrentEnvironment().'/'; return $sUrl; } + + /** + * Returns a name unique amongst the given list + * @param string $sProposed The default value + * @param array $aExisting An array of existing values (strings) + */ + static public function MakeUniqueName($sProposed, $aExisting) + { + if (in_array($sProposed, $aExisting)) + { + $i = 1; + while (in_array($sProposed.$i, $aExisting) && ($i < 50)) + { + $i++; + } + return $sProposed.$i; + } + else + { + return $sProposed; + } + } } ?> \ No newline at end of file diff --git a/datamodels/1.x/itop-welcome-itil/main.itop-welcome-itil.php b/datamodels/1.x/itop-welcome-itil/main.itop-welcome-itil.php index 7b9a6b037..7aaabbc8b 100644 --- a/datamodels/1.x/itop-welcome-itil/main.itop-welcome-itil.php +++ b/datamodels/1.x/itop-welcome-itil/main.itop-welcome-itil.php @@ -45,7 +45,9 @@ class ItopWelcome extends ModuleHandlerAPI public static function OnMenuCreation() { $oWelcomeMenu = new MenuGroup('WelcomeMenu', 10 /* fRank */); - new DashboardMenuNode('WelcomeMenuPage', dirname(__FILE__).'/welcome_menu.xml', $oWelcomeMenu->GetIndex() /* oParent */, 1 /* fRank */); + new DashboardMenuNode('WelcomeMenuPage', dirname(__FILE__).'/welcome_menu.xml', $oWelcomeMenu->GetIndex() /* oParent */, 10 /* fRank */); + + new ShortcutContainerMenuNode('MyShortcuts', $oWelcomeMenu->GetIndex(), 20 /* fRank */); $oToolsMenu = new MenuGroup('DataAdministration', 70 /* fRank */, 'Organization', UR_ACTION_MODIFY, UR_ALLOWED_YES|UR_ALLOWED_DEPENDS); new WebPageMenuNode('CSVImportMenu', utils::GetAbsoluteUrlAppRoot().'pages/csvimport.php', $oToolsMenu->GetIndex(), 1 /* fRank */); diff --git a/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml b/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml index f15d86b7d..b33411b5c 100644 --- a/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml +++ b/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml @@ -5,7 +5,7 @@ 10 - 1 + 10 WelcomeMenu DashboardLayoutOneCol @@ -52,5 +52,9 @@ + + 20 + WelcomeMenu + diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index 93c1005f1..c16abb6d4 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -375,6 +375,7 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Button:FilterList' => ' Filter... ', 'UI:Button:Create' => ' Create ', 'UI:Button:Delete' => ' Delete ! ', + 'UI:Button:Rename' => ' Rename... ', 'UI:Button:ChangePassword' => ' Change Password ', 'UI:Button:ResetPassword' => ' Reset Password ', @@ -1122,5 +1123,19 @@ When associated with a trigger, each action is given an "order" number, specifyi 'DayOfWeek-Thursday' => 'Thursday', 'DayOfWeek-Friday' => 'Friday', 'DayOfWeek-Saturday' => 'Saturday', + + 'UI:Menu:ShortcutList' => 'Create a Shortcut...', + 'UI:ShortcutRenameDlg:Title' => 'Rename the shortcut', + 'UI:ShortcutListDlg:Title' => 'Create a shortcut for the list', + 'UI:ShortcutDelete:Confirm' => 'Please confirm that wou wish to delete the shortcut(s).', + 'Menu:MyShortcuts' => 'My Shortcuts', + 'Class:Shortcut' => 'Shortcut', + 'Class:Shortcut+' => '', + 'Class:Shortcut/Attribute:name' => 'Name', + 'Class:Shortcut/Attribute:name+' => 'Label used in the menu and page title', + 'Class:ShortcutOQL' => 'Search result shortcut', + 'Class:ShortcutOQL+' => '', + 'Class:ShortcutOQL/Attribute:oql' => 'Query', + 'Class:ShortcutOQL/Attribute:oql+' => 'OQL defining the list of objects to search for', )); ?> diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index c894597a2..ad24cbde4 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -256,6 +256,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Button:FilterList' => ' Filtrer... ', 'UI:Button:Create' => ' Créer ', 'UI:Button:Delete' => ' Supprimer ! ', + 'UI:Button:Rename' => ' Renommer... ', 'UI:Button:ChangePassword' => ' Changer ! ', 'UI:Button:ResetPassword' => ' Ràz du mot de passe ', 'UI:SearchToggle' => 'Recherche', @@ -964,5 +965,19 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'DayOfWeek-Thursday' => 'Jeudi', 'DayOfWeek-Friday' => 'Vendredi', 'DayOfWeek-Saturday' => 'Samedi', + + 'UI:Menu:ShortcutList' => 'Créer un Raccourci...', + 'UI:ShortcutListDlg:Title' => 'Créer un raccourci pour la liste', + 'UI:ShortcutRenameDlg:Title' => 'Renommer le raccourci', + 'UI:ShortcutDelete:Confirm' => 'Veuillez confirmer la suppression du ou des raccourci(s)', + 'Menu:MyShortcuts' => 'Mes raccourcis', + 'Class:Shortcut' => 'Raccourci', + 'Class:Shortcut+' => '', + 'Class:Shortcut/Attribute:name' => 'Nom', + 'Class:Shortcut/Attribute:name+' => 'Label utilisé dans le menu et comme titre de la page', + 'Class:ShortcutOQL' => 'Raccourci vers une liste d\'objets', + 'Class:ShortcutOQL+' => '', + 'Class:ShortcutOQL/Attribute:oql' => 'Requête', + 'Class:ShortcutOQL/Attribute:oql+' => 'Requête de définition de l\'ensemble des objets', )); ?> diff --git a/js/datatable.js b/js/datatable.js index 8232d91cc..1933e2899 100644 --- a/js/datatable.js +++ b/js/datatable.js @@ -255,6 +255,28 @@ $(function() var oDlgOpen = $('#datatable_dlg_'+sListId+' :visible'); return (oDlgOpen.length > 0); + }, + GetMultipleSelectionParams: function() + { + var oRes = {}; + + oRes.selectionMode = ''; + if (this.element.find(':input[name=selectionMode]').length > 0) + { + oRes.selectionMode = this.element.find(':input[name=selectionMode]').val(); + } + + oRes.selectObject = []; + this.element.find(':input[name^=selectObject]').each(function() { + oRes.selectObject.push($(this).val()); + }); + + oRes.storedSelection = []; + this.element.find(':input[name^=storedSelection]').each(function() { + oRes.storedSelection.push($(this).val()); + }); + + return oRes; } }); }); \ No newline at end of file diff --git a/js/utils.js b/js/utils.js index 0cd9e72c7..acb46832e 100644 --- a/js/utils.js +++ b/js/utils.js @@ -318,4 +318,12 @@ function DashletCreationDlg(sOQL) $('body').append(data); }); return false; +} + +function ShortcutListDlg(sOQL, sContext) +{ + $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?'+sContext, {operation: 'shortcut_list_dlg', oql: sOQL}, function(data){ + $('body').append(data); + }); + return false; } \ No newline at end of file diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 596296a25..031fd1a5d 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -861,6 +861,69 @@ EOF } break; + case 'shortcut_list_dlg': + $sOQL = utils::ReadParam('oql', '', false, 'raw_data'); + ShortcutOQL::GetCreationDlgFromOQL($oPage, $sOQL); + break; + + case 'shortcut_list_create': + $oForm = ShortcutOQL::GetCreationForm(); + $aValues = $oForm->ReadParams(); + + $oAppContext = new ApplicationContext(); + $aContext = $oAppContext->GetAsHash(); + $sContext = serialize($aContext); + + $oShortcut = MetaModel::NewObject("ShortcutOQL"); + $oShortcut->Set('user_id', UserRights::GetUserId()); + $oShortcut->Set("context", $sContext); + $oShortcut->Set("name", $aValues['name']); + $oShortcut->Set("oql", $aValues['oql']); + $iId = $oShortcut->DBInsertNoReload(); + + // Add the menu node in the right place + // + // Mmmm... already done because the newly created menu is read from the DB + // as soon as we invoke DisplayMenu + + // Refresh the menu pane + $aExtraParams = array(); + ApplicationMenu::DisplayMenu($oPage, $aExtraParams); + break; + + case 'shortcut_rename_dlg': + $oSearch = new DBObjectSearch('Shortcut'); + $aShortcuts = utils::ReadMultipleSelection($oSearch); + $iShortcut = $aShortcuts[0]; + $oShortcut = MetaModel::GetObject('Shortcut', $iShortcut); + $oShortcut->StartRenameDialog($oPage); + break; + + case 'shortcut_rename_go': + $iShortcut = utils::ReadParam('id', 0); + $oShortcut = MetaModel::GetObject('Shortcut', $iShortcut); + + $sName = utils::ReadParam('attr_name', '', false, 'raw_data'); + if (strlen($sName) > 0) + { + $oShortcut->Set('name', $sName); + $oShortcut->DBUpdate(); + $oPage->add_ready_script('window.location.reload();'); + } + + break; + + case 'shortcut_delete_go': + $oSearch = new DBObjectSearch('Shortcut'); + $aShortcuts = utils::ReadMultipleSelection($oSearch); + foreach ($aShortcuts as $iShortcut) + { + $oShortcut = MetaModel::GetObject('Shortcut', $iShortcut); + $oShortcut->DBDelete(); + $oPage->add_ready_script('window.location.reload();'); + } + break; + case 'export_dashboard': $sMenuId = utils::ReadParam('id', '', false, 'raw_data'); ApplicationMenu::LoadAdditionalMenus(); diff --git a/pages/preferences.php b/pages/preferences.php index 714809b2f..946c61b24 100644 --- a/pages/preferences.php +++ b/pages/preferences.php @@ -144,7 +144,6 @@ EOF var pager = $('#user_prefs form .pager'); $(':input[name=selectionMode]', pager).val('negative'); $('#user_prefs table.listResults').trigger('load_selection'); - } else { @@ -186,7 +185,102 @@ EOF EOF ); } + + ////////////////////////////////////////////////////////////////////////// + // + // Shortcuts + // + ////////////////////////////////////////////////////////////////////////// + + $oP->add('
'.Dict::S('Menu:MyShortcuts').''); + //$oP->p(Dict::S('UI:Menu:MyShortcuts+')); + $oBMSearch = new DBObjectSearch('Shortcut'); + $oBMSearch->AddCondition('user_id', UserRights::GetUserId(), '='); + + //$aExtraParams = array('menu' => false, 'toolkit_menu' => false, 'display_limit' => false, 'localize_values' => $bLocalize, 'zlist' => 'details'); + $aExtraParams = array(); + $oBlock = new DisplayBlock($oBMSearch, 'list', false, $aExtraParams); + $oBlock->Display($oP, 'shortcut_list', array('view_link' => false, 'menu' => false, 'toolkit_menu' => false, 'selection_mode' => true, 'selection_type' => 'multiple', 'cssCount'=> '#shortcut_selection_count', 'table_id' => 'user_prefs_shortcuts')); + $oP->add('

'); + + $oSet = new DBObjectSet($oBMSearch); + if ($oSet->Count() > 0) + { + $sButtons = ''; + $sButtons .= ' '; + $sButtons .= ''; + $sButtons .= ' '; + $sButtons .= ''; + + // Selection count updated by the pager, and used to enable buttons + $oP->add(''); + $oP->add('

'); + $sConfirmDelete = addslashes(Dict::S('UI:ShortcutDelete:Confirm')); + + $oP->add_ready_script( +<<   $sButtons'); +$('#shortcut_selection_count').bind('change', OnSelectionCountChange); +$('#shortcut_btn_rename').bind('click', OnShortcutBtnRename); +$('#shortcut_btn_delete').bind('click', OnShortcutBtnDelete); +OnSelectionCountChange(); +EOF + ); + } // if count > 0 + + ////////////////////////////////////////////////////////////////////////// + // + // Footer + // $oP->add(''); $oP->add_ready_script("$('#fav_page_length').bind('keyup change', function(){ ValidateOtherSettings(); })"); } diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 4e3393373..58db08c99 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -178,7 +178,7 @@ EOF; { $sCompiledCode .= $this->CompileMenu($oMenuNode, $sTargetDir, $sRelativeDir, $oP); } - catch (ssDOMFormatException $e) + catch (DOMFormatException $e) { throw new Exception("Failed to process menu '$sMenuId', from '$sModuleRootDir': ".$e->getMessage()); } @@ -1018,6 +1018,10 @@ EOF; $sNewMenu = "new TemplateMenuNode('$sMenuId', $sTemplateSpec, $sParentSpec, $fRank);"; break; + case 'ShortcutContainerMenuNode': + $sNewMenu = "new ShortcutContainerMenuNode('$sMenuId', $sParentSpec, $fRank);"; + break; + case 'OQLMenuNode': $sOQL = self::QuoteForPHP($oMenu->GetChildText('oql')); $bSearch = ($oMenu->GetChildText('do_search') == '1') ? 'true' : 'false'; @@ -1043,17 +1047,16 @@ EOF; $sEnableStimulus = $oMenu->GetChildText('enable_stimulus'); if (strlen($sEnableStimulus) > 0) { - $sNewMenu = "new MenuGroup('$sMenuId', $fRank, '$sEnableClass', $sEnableAction, $sEnablePermission, '$sEnableStimulus');"; + $sNewMenu = "new $sMenuClass('$sMenuId', $fRank, '$sEnableClass', $sEnableAction, $sEnablePermission, '$sEnableStimulus');"; } else { - $sNewMenu = "new MenuGroup('$sMenuId', $fRank, '$sEnableClass', $sEnableAction, $sEnablePermission);"; + $sNewMenu = "new $sMenuClass('$sMenuId', $fRank, '$sEnableClass', $sEnableAction, $sEnablePermission);"; } - //$sNewMenu = "new MenuGroup('$sMenuId', $fRank, '$sEnableClass', UR_ACTION_MODIFY, UR_ALLOWED_YES|UR_ALLOWED_DEPENDS);"; } else { - $sNewMenu = "new MenuGroup('$sMenuId', $fRank);"; + $sNewMenu = "new $sMenuClass('$sMenuId', $fRank);"; } }