Przeglądaj źródła

New feature: shortcuts to a search result. The feature is not automatically available with upgrade of custom versions -requires a ShortcutContainerMenuNode.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@2431 a333f486-631f-4898-b8df-5754b55c2be0
romainq 12 lat temu
rodzic
commit
8118327ddf

+ 26 - 0
application/ajaxwebpage.class.inc.php

@@ -36,6 +36,7 @@ class ajax_page extends WebPage
 	protected $m_sCurrentTab;
 	protected $m_sCurrentTab;
 	protected $m_sCurrentTabContainer;
 	protected $m_sCurrentTabContainer;
 	protected $m_aTabs;
 	protected $m_aTabs;
+	private $m_sMenu; // If set, then the menu will be updated
 	
 	
     /**
     /**
      * constructor for the web page
      * constructor for the web page
@@ -52,6 +53,7 @@ class ajax_page extends WebPage
 		$this->m_aTabs = array();
 		$this->m_aTabs = array();
 		$this->sContentType = 'text/html';
 		$this->sContentType = 'text/html';
 		$this->sContentDisposition = 'inline';
 		$this->sContentDisposition = 'inline';
+		$this->m_sMenu = "";
     }	
     }	
 
 
 	public function AddTabContainer($sTabContainer, $sPrefix = '')
 	public function AddTabContainer($sTabContainer, $sPrefix = '')
@@ -93,6 +95,11 @@ class ajax_page extends WebPage
 		return $this->m_sCurrentTab;
 		return $this->m_sCurrentTab;
 	}
 	}
 	
 	
+	public function AddToMenu($sHtml)
+	{
+		$this->m_sMenu .= $sHtml;
+	}
+
     /**
     /**
      * Echoes the content of the whole page
      * Echoes the content of the whole page
      * @return void
      * @return void
@@ -221,6 +228,24 @@ EOF
         {
         {
         	echo $this->s_content;
         	echo $this->s_content;
         }
         }
+        if (!empty($this->m_sMenu))
+        {
+           $uid = time();
+           echo "<div id=\"accordion_temp_$uid\">\n";
+           echo "<div id=\"accordion\">\n";
+           echo "<!-- Beginning of the accordion menu -->\n";
+           echo self::FilterXSS($this->m_sMenu);
+           echo "<!-- End of the accordion menu-->\n";
+           echo "</div>\n";
+           echo "</div>\n";
+
+	        echo "<script type=\"text/javascript\">\n";
+	        echo "$('#inner_menu').html($('#accordion_temp_$uid').html());\n";
+	        echo "$('#accordion_temp_$uid').remove();\n";
+	        echo "$('#accordion').accordion({ header: 'h3', navigation: true, autoHeight: false, collapsible: false, icons: false });\n";
+	        echo "\n</script>\n";
+        }
+
         //echo $this->s_deferred_content;
         //echo $this->s_deferred_content;
         if (count($this->a_scripts) > 0)
         if (count($this->a_scripts) > 0)
         {
         {
@@ -240,6 +265,7 @@ EOF
 	        echo $this->m_sReadyScript; // Ready Scripts are output as simple scripts
 	        echo $this->m_sReadyScript; // Ready Scripts are output as simple scripts
 	        echo "\n</script>\n";
 	        echo "\n</script>\n";
         }
         }
+        
 		if (trim($s_captured_output) != "")
 		if (trim($s_captured_output) != "")
         {
         {
         	echo self::FilterXSS($s_captured_output);
         	echo self::FilterXSS($s_captured_output);

+ 1 - 0
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:EMail', Dict::S('UI:Menu:EMail'), "mailto:?body=".urlencode($sUrl)),
 				new URLPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), $sUrl."&format=csv"),
 				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:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL')"),
+				new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sContext')"),
 			);
 			);
 			break;
 			break;
 			
 			

+ 137 - 19
application/menunode.class.inc.php

@@ -80,6 +80,15 @@ class ApplicationMenu
 					call_user_func($aCallSpec);
 					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;
 			self::$bAdditionalMenusLoaded = true;
 		}
 		}
 	}
 	}
@@ -131,7 +140,7 @@ class ApplicationMenu
 			//       they were not used to display the menus (redundant or unused)
 			//       they were not used to display the menus (redundant or unused)
 			//
 			//
 			$aBacktrace = debug_backtrace();
 			$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);
 			self::$aMenusIndex[$index] = array('node' => $oMenuNode, 'children' => array(), 'parent' => $sParentId, 'rank' => $fRank, 'source_file' => $sFile);
 		}
 		}
 		else
 		else
@@ -154,7 +163,7 @@ class ApplicationMenu
 	/**
 	/**
 	 * Entry point to display the whole menu into the web page, used by iTopWebPage
 	 * 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();
 		self::LoadAdditionalMenus();
 		// Sort the root menu based on the rank
 		// 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
 	 * 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'];
 		return self::$aMenusIndex[$index]['children'];
 	}
 	}
-	
+
 	/**
 	/**
 	 * Helper function to get the ID of a menu based on its name
 	 * 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)
 	 * @param string $sTitle Title of the menu (as passed when creating the menu)
@@ -407,6 +416,16 @@ abstract class MenuNode
 		return $this->index;
 		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)
 	public function GetHyperlink($aExtraParams)
 	{
 	{
 		$aExtraParams['c[menu]'] = $this->GetMenuId();
 		$aExtraParams['c[menu]'] = $this->GetMenuId();
@@ -617,31 +636,38 @@ class OQLMenuNode extends MenuNode
 	
 	
 	public function RenderContent(WebPage $oPage, $aExtraParams = array())
 	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
+		);
+	}
+
+	public static function RenderOQLSearch($sOql, $sTitle, $sUsageId, $bSearchPane, $bSearchOpen, WebPage $oPage, $aExtraParams = array())
+	{
+		$oSearch = DBObjectSearch::FromOQL($sOql);
+		$sIcon = MetaModel::GetClassIcon($oSearch->GetClass());
 
 
-		if ($this->bSearch)
+		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 = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
 			$oBlock->Display($oPage, 0);
 			$oBlock->Display($oPage, 0);
 		}
 		}
 		
 		
-		$oPage->add("<p class=\"page-header\">$sIcon ".Dict::S($this->sPageTitle)."</p>");
+		$oPage->add("<p class=\"page-header\">$sIcon ".Dict::S($sTitle)."</p>");
 		
 		
-		$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 = 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
  * 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');
+	}
+}
+

+ 257 - 0
application/shortcut.class.inc.php

@@ -0,0 +1,257 @@
+<?php
+// Copyright (C) 2010-2012 Combodo SARL
+//
+//   This file is part of iTop.
+//
+//   iTop is free software; you can redistribute it and/or modify	
+//   it under the terms of the GNU Affero General Public License as published by
+//   the Free Software Foundation, either version 3 of the License, or
+//   (at your option) any later version.
+//
+//   iTop is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU Affero General Public License for more details.
+//
+//   You should have received a copy of the GNU Affero General Public License
+//   along with iTop. If not, see <http://www.gnu.org/licenses/>
+
+
+/**
+ * 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('<div id="shortcut_rename_dlg">');
+
+		$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('</div>');
+		
+		$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(
+<<<EOF
+function ShortcutRenameOK() 
+{
+	var oForm = $(this).find('form');
+	var sFormId = oForm.attr('id');
+	var oParams = null;
+	var aErrors = ValidateForm(sFormId, false);
+	if (aErrors.length == 0)
+	{
+		oParams = ReadFormParams(sFormId);
+	}
+	oParams.operation = 'shortcut_rename_go';
+	oParams.id = $iShortcut;
+	var me = $(this);
+	$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function(data) {
+		me.dialog( "close" );
+		me.remove();
+		$('body').append(data);
+	});
+}
+
+$('#shortcut_rename_dlg form').bind('submit', function() { return false; });
+
+$('#shortcut_rename_dlg').dialog({
+	width: 400,
+	modal: true,
+	title: '$sDialogTitle',
+	buttons: [
+	{ text: "$sOkButtonLabel", click: ShortcutRenameOK},
+	{ text: "$sCancelButtonLabel", click: function() {
+		$(this).dialog( "close" ); $(this).remove();
+	} },
+	],
+	close: function() { $(this).remove(); }
+});
+EOF
+		);
+	}
+}
+
+class ShortcutOQL extends Shortcut
+{
+	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_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('<div id="shortcut_creation_dlg">');
+
+		$oForm = self::GetCreationForm($sOQL);
+
+		$oForm->Render($oPage);
+		$oPage->add('</div>');
+		
+		$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(
+<<<EOF
+
+function ShortcutCreationOK() 
+{
+	var oForm = $('#shortcut_creation_dlg form');
+	var sFormId = oForm.attr('id');
+	var oParams = null;
+	var aErrors = ValidateForm(sFormId, false);
+	if (aErrors.length == 0)
+	{
+		oParams = ReadFormParams(sFormId);
+	}
+	oParams.operation = 'shortcut_list_create';
+	var me = $('#shortcut_creation_dlg');
+	$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?$sContext', oParams, function(data) {
+		me.dialog( "close" );
+		me.remove();
+		$('body').append(data);
+	});
+}
+
+$('#shortcut_creation_dlg form').bind('submit', function() { ShortcutCreationOK(); return false; });
+
+$('#shortcut_creation_dlg').dialog({
+	width: 400,
+	modal: true,
+	title: '$sDialogTitle',
+	buttons: [
+	{ text: "$sOkButtonLabel", click: ShortcutCreationOK },
+	{ text: "$sCancelButtonLabel", click: function() {
+		$(this).dialog( "close" ); $(this).remove();
+	} },
+	],
+	close: function() { $(this).remove(); }
+});
+EOF
+		);
+	}
+}
+
+?>

+ 22 - 0
application/utils.inc.php

@@ -819,5 +819,27 @@ class utils
 		$sUrl = self::GetAbsoluteUrlAppRoot().'env-'.self::GetCurrentEnvironment().'/';
 		$sUrl = self::GetAbsoluteUrlAppRoot().'env-'.self::GetCurrentEnvironment().'/';
 		return $sUrl;
 		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;
+		}
+	}	 	
 }
 }
 ?>
 ?>

+ 3 - 1
datamodels/1.x/itop-welcome-itil/main.itop-welcome-itil.php

@@ -45,7 +45,9 @@ class ItopWelcome extends ModuleHandlerAPI
 	public static function OnMenuCreation()
 	public static function OnMenuCreation()
 	{
 	{
 		$oWelcomeMenu = new MenuGroup('WelcomeMenu', 10 /* fRank */);
 		$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);
 		$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 */);
 		new WebPageMenuNode('CSVImportMenu', utils::GetAbsoluteUrlAppRoot().'pages/csvimport.php', $oToolsMenu->GetIndex(), 1 /* fRank */);

+ 5 - 1
datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml

@@ -5,7 +5,7 @@
       <rank>10</rank>
       <rank>10</rank>
     </menu>
     </menu>
     <menu id="WelcomeMenuPage" xsi:type="DashboardMenuNode" _delta="define">
     <menu id="WelcomeMenuPage" xsi:type="DashboardMenuNode" _delta="define">
-      <rank>1</rank>
+      <rank>10</rank>
       <parent>WelcomeMenu</parent>
       <parent>WelcomeMenu</parent>
       <definition>
       <definition>
         <layout>DashboardLayoutOneCol</layout>
         <layout>DashboardLayoutOneCol</layout>
@@ -52,5 +52,9 @@
         </cells>
         </cells>
       </definition>
       </definition>
     </menu>
     </menu>
+    <menu id="MyShortcuts" xsi:type="ShortcutContainerMenuNode" _delta="define">
+      <rank>20</rank>
+      <parent>WelcomeMenu</parent>
+    </menu>
   </menus>
   </menus>
 </itop_design>
 </itop_design>

+ 15 - 0
dictionaries/dictionary.itop.ui.php

@@ -375,6 +375,7 @@ Dict::Add('EN US', 'English', 'English', array(
 	'UI:Button:FilterList' => ' Filter... ',
 	'UI:Button:FilterList' => ' Filter... ',
 	'UI:Button:Create' => ' Create ',
 	'UI:Button:Create' => ' Create ',
 	'UI:Button:Delete' => ' Delete ! ',
 	'UI:Button:Delete' => ' Delete ! ',
+	'UI:Button:Rename' => ' Rename... ',
 	'UI:Button:ChangePassword' => ' Change Password ',
 	'UI:Button:ChangePassword' => ' Change Password ',
 	'UI:Button:ResetPassword' => ' Reset 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-Thursday' => 'Thursday',
 	'DayOfWeek-Friday' => 'Friday',
 	'DayOfWeek-Friday' => 'Friday',
 	'DayOfWeek-Saturday' => 'Saturday',
 	'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',
 ));
 ));
 ?>
 ?>

+ 15 - 0
dictionaries/fr.dictionary.itop.ui.php

@@ -256,6 +256,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
 	'UI:Button:FilterList' => ' Filtrer... ',
 	'UI:Button:FilterList' => ' Filtrer... ',
 	'UI:Button:Create' => ' Créer ',
 	'UI:Button:Create' => ' Créer ',
 	'UI:Button:Delete' => ' Supprimer ! ',
 	'UI:Button:Delete' => ' Supprimer ! ',
+	'UI:Button:Rename' => ' Renommer... ',
 	'UI:Button:ChangePassword' => ' Changer ! ',
 	'UI:Button:ChangePassword' => ' Changer ! ',
 	'UI:Button:ResetPassword' => ' Ràz du mot de passe ',
 	'UI:Button:ResetPassword' => ' Ràz du mot de passe ',
 	'UI:SearchToggle' => 'Recherche',
 	'UI:SearchToggle' => 'Recherche',
@@ -964,5 +965,19 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
 	'DayOfWeek-Thursday' => 'Jeudi',
 	'DayOfWeek-Thursday' => 'Jeudi',
 	'DayOfWeek-Friday' => 'Vendredi',
 	'DayOfWeek-Friday' => 'Vendredi',
 	'DayOfWeek-Saturday' => 'Samedi',
 	'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',
 ));
 ));
 ?>
 ?>

+ 22 - 0
js/datatable.js

@@ -255,6 +255,28 @@ $(function()
 			var oDlgOpen = $('#datatable_dlg_'+sListId+' :visible');
 			var oDlgOpen = $('#datatable_dlg_'+sListId+' :visible');
 			
 			
 			return (oDlgOpen.length > 0);
 			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;
 		}
 		}
 	});	
 	});	
 });
 });

+ 8 - 0
js/utils.js

@@ -318,4 +318,12 @@ function DashletCreationDlg(sOQL)
 		$('body').append(data);
 		$('body').append(data);
 	});
 	});
 	return false;
 	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;
 }
 }

+ 63 - 0
pages/ajax.render.php

@@ -861,6 +861,69 @@ EOF
 		}
 		}
 		break;
 		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':
 		case 'export_dashboard':
 		$sMenuId = utils::ReadParam('id', '', false, 'raw_data');
 		$sMenuId = utils::ReadParam('id', '', false, 'raw_data');
 		ApplicationMenu::LoadAdditionalMenus();
 		ApplicationMenu::LoadAdditionalMenus();

+ 95 - 1
pages/preferences.php

@@ -144,7 +144,6 @@ EOF
 		var pager = $('#user_prefs form .pager');
 		var pager = $('#user_prefs form .pager');
 		$(':input[name=selectionMode]', pager).val('negative');
 		$(':input[name=selectionMode]', pager).val('negative');
 		$('#user_prefs table.listResults').trigger('load_selection');
 		$('#user_prefs table.listResults').trigger('load_selection');
-		
 	}
 	}
 	else
 	else
 	{
 	{
@@ -186,7 +185,102 @@ EOF
 EOF
 EOF
 );
 );
 	}
 	}
+
+	//////////////////////////////////////////////////////////////////////////
+	//
+	// Shortcuts
+	//
+	//////////////////////////////////////////////////////////////////////////
+
+	$oP->add('<fieldset><legend>'.Dict::S('Menu:MyShortcuts').'</legend>');
+	//$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('<p>');
+
+	$oSet = new DBObjectSet($oBMSearch);
+	if ($oSet->Count() > 0)
+	{
+		$sButtons = '<img src="../images/tv-item-last.gif">';
+		$sButtons .= '&nbsp;';
+		$sButtons .= '<button id="shortcut_btn_rename">'.Dict::S('UI:Button:Rename').'</button>';
+		$sButtons .= '&nbsp;';
+		$sButtons .= '<button id="shortcut_btn_delete">'.Dict::S('UI:Button:Delete').'</button>';
+
+		// Selection count updated by the pager, and used to enable buttons
+		$oP->add('<input type="hidden" id="shortcut_selection_count"/>');
+		$oP->add('</fieldset>');
+	
+		$sConfirmDelete = addslashes(Dict::S('UI:ShortcutDelete:Confirm'));
 	
 	
+		$oP->add_ready_script(
+<<<EOF
+function OnShortcutBtnRename()
+{
+	var oParams = $('#datatable_shortcut_list').datatable('GetMultipleSelectionParams');
+	oParams.operation = 'shortcut_rename_dlg';
+
+	$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function(data){
+		$('body').append(data);
+	});
+	return false;
+}
+
+function OnShortcutBtnDelete()
+{
+	if (confirm('$sConfirmDelete'))
+	{
+		var oParams = $('#datatable_shortcut_list').datatable('GetMultipleSelectionParams');
+		oParams.operation = 'shortcut_delete_go';
+
+		$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function(data){
+			$('body').append(data);
+		});
+	}
+	return false;
+}
+
+function OnSelectionCountChange()
+{
+	var iCountSelected = $("#shortcut_selection_count").val();
+	if (iCountSelected == 0)
+	{
+		$('#shortcut_btn_rename').attr('disabled', 'disabled');
+		$('#shortcut_btn_delete').attr('disabled', 'disabled');
+	}
+	else if (iCountSelected == 1)
+	{
+		$('#shortcut_btn_rename').removeAttr('disabled');
+		$('#shortcut_btn_delete').removeAttr('disabled');
+	}
+	else
+	{
+		$('#shortcut_btn_rename').attr('disabled', 'disabled');
+		$('#shortcut_btn_delete').removeAttr('disabled');
+	}
+}
+
+var oUpperCheckBox = $('#datatable_shortcut_list .checkAll').first();
+oUpperCheckBox.parent().width(oUpperCheckBox.width() + 2);
+
+$('#datatable_shortcut_list').append('<tr><td colspan="2">&nbsp;&nbsp;&nbsp;$sButtons</td></tr>');
+$('#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('</div>');
 	$oP->add('</div>');
 	$oP->add_ready_script("$('#fav_page_length').bind('keyup change', function(){ ValidateOtherSettings(); })");
 	$oP->add_ready_script("$('#fav_page_length').bind('keyup change', function(){ ValidateOtherSettings(); })");
 }
 }

+ 8 - 5
setup/compiler.class.inc.php

@@ -178,7 +178,7 @@ EOF;
 					{
 					{
 						$sCompiledCode .= $this->CompileMenu($oMenuNode, $sTargetDir, $sRelativeDir, $oP);
 						$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());
 						throw new Exception("Failed to process menu '$sMenuId', from '$sModuleRootDir': ".$e->getMessage());
 					}
 					}
@@ -1018,6 +1018,10 @@ EOF;
 			$sNewMenu = "new TemplateMenuNode('$sMenuId', $sTemplateSpec, $sParentSpec, $fRank);";
 			$sNewMenu = "new TemplateMenuNode('$sMenuId', $sTemplateSpec, $sParentSpec, $fRank);";
 			break;
 			break;
 
 
+		case 'ShortcutContainerMenuNode':
+			$sNewMenu = "new ShortcutContainerMenuNode('$sMenuId', $sParentSpec, $fRank);";
+			break;
+
 		case 'OQLMenuNode':
 		case 'OQLMenuNode':
 			$sOQL = self::QuoteForPHP($oMenu->GetChildText('oql'));
 			$sOQL = self::QuoteForPHP($oMenu->GetChildText('oql'));
 			$bSearch = ($oMenu->GetChildText('do_search') == '1') ? 'true' : 'false';
 			$bSearch = ($oMenu->GetChildText('do_search') == '1') ? 'true' : 'false';
@@ -1043,17 +1047,16 @@ EOF;
 				$sEnableStimulus = $oMenu->GetChildText('enable_stimulus');
 				$sEnableStimulus = $oMenu->GetChildText('enable_stimulus');
 				if (strlen($sEnableStimulus) > 0)
 				if (strlen($sEnableStimulus) > 0)
 				{
 				{
-					$sNewMenu = "new MenuGroup('$sMenuId', $fRank, '$sEnableClass', $sEnableAction, $sEnablePermission, '$sEnableStimulus');";
+					$sNewMenu = "new $sMenuClass('$sMenuId', $fRank, '$sEnableClass', $sEnableAction, $sEnablePermission, '$sEnableStimulus');";
 				}
 				}
 				else
 				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
 			else
 			{
 			{
-				$sNewMenu = "new MenuGroup('$sMenuId', $fRank);";
+				$sNewMenu = "new $sMenuClass('$sMenuId', $fRank);";
 			}
 			}
 		}
 		}