Forráskód Böngészése

- Cosmetics: aligning the style in the different pages, make the iTop logo clickable, etc...
- Handle persistent user preferences: for now only the menu status (open/closed) and its width. In the future the status of lists (open/closed, which columns to show...) can be considered as well.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@487 a333f486-631f-4898-b8df-5754b55c2be0

dflaven 15 éve
szülő
commit
df3ba5bedd

+ 83 - 29
application/itopwebpage.class.inc.php

@@ -26,6 +26,7 @@
 require_once("../application/nicewebpage.class.inc.php");
 require_once("../application/usercontext.class.inc.php");
 require_once("../application/applicationcontext.class.inc.php");
+require_once("../application/user.preferences.class.inc.php");
 /**
  * Web page with some associated CSS and scripts (jquery) for a fancier display
  */
@@ -95,15 +96,44 @@ class iTopWebPage extends NiceWebPage
 
 	$(document).ready(function () {
 		// Layout
+		paneSize = GetUserPreference('menu_size', 300)
 		myLayout = $('body').layout({
-			west : { minSize: 200, size: 300 /* TO DO: read from a cookie ?*/, spacing_open: 16, spacing_close: 16, slideTrigger_open: "mouseover", hideTogglerOnSlide: true }
+			west : 	{
+						minSize: 200, size: paneSize, spacing_open: 16, spacing_close: 16, slideTrigger_open: "mouseover", hideTogglerOnSlide: true,
+					 	onclose_end: function(name, elt, state, options, layout)
+					 	{
+					 			if (state.isSliding == false)
+					 			{
+					 				SetUserPreference('menu_pane', 'closed', true);
+					 			}
+					 	},
+					 	onresize_end: function(name, elt, state, options, layout)
+					 	{
+					 			if (state.isSliding == false)
+					 			{
+					 				SetUserPreference('menu_size', state.size, true);
+					 			}
+					 	},
+					 				
+					 	onopen_end: function(name, elt, state, options, layout)
+					 	{
+					 		if (state.isSliding == false)
+					 		{
+					 			SetUserPreference('menu_pane', 'open', true);
+					 		}
+					 	}
+					}
 		});
 		myLayout.addPinBtn( "#tPinMenu", "west" );
 		//myLayout.open( "west" );
 		$('.ui-layout-resizer-west').html('<img src="../images/splitter-top-corner.png"/>');
+		if (GetUserPreference('menu_pane', 'open') == 'closed')
+		{
+			myLayout.close('west');
+		}
 
 		// Accordion Menu
-		$("#accordion").accordion({ header: "h3", navigation: true, autoHeight: false });
+		$("#accordion").accordion({ header: "h3", navigation: true, autoHeight: false, collapsible: false });
  	});
 		
 	//$("div[id^=tabbedContent] > ul").tabs( 1, { fxFade: true, fxSpeed: 'fast' } ); // tabs
@@ -128,11 +158,13 @@ class iTopWebPage extends NiceWebPage
 	catch(err)
 	{
 		// Do something with the error !
+		alert(err);
 	}
 	
 	//$('.display_block').draggable(); // make the blocks draggable
 EOF
 );
+		$sUserPrefs = appUserPreferences::GetAsJSON();
 		$this->add_script("
 		// For automplete
 		function findValue(li) {
@@ -173,6 +205,8 @@ EOF
 				$('#rawOutput').dialog( {autoOpen: true, modal:false});
 			}
 		}
+		
+		var oUserPreferences = $sUserPrefs;
 		");
 		
 		// Add the standard menus
@@ -223,36 +257,56 @@ EOF
 
 	public function GetSiloSelectionForm()
 	{
-		$sHtml = '<div id="SiloSelection">';
-		$sHtml .= '<form style="display:inline" action="'.$_SERVER['PHP_SELF'].'"><select style="width:150px;font-size:x-small" name="org_id" title="Pick an organization" onChange="this.form.submit();">';
 		// List of visible Organizations
+		$iCount = 0;
 		$oContext = new UserContext();
-		$oSearchFilter = $oContext->NewFilter("bizOrganization");
-		$oSet = new CMDBObjectSet($oSearchFilter);
-		$sSelected = ($this->m_currentOrganization == '') ? ' selected' : '';
-		$sHtml .= '<option value="">'.Dict::S('UI:AllOrganizations').'</option>';
-		if ($oSet->Count() > 0)
-		while($oOrg = $oSet->Fetch())
+		if (MetaModel::IsValidClass('bizOrganization'))
 		{
-			if ($this->m_currentOrganization == $oOrg->GetKey())
-			{
-				$oCurrentOrganization = $oOrg;
-				$sSelected = " selected";
-		
-			}
-			else
+			$oSearchFilter = $oContext->NewFilter('Organization');
+			$oSet = new CMDBObjectSet($oSearchFilter);
+			$iCount = $oSet->Count();
+		}
+		switch($iCount)
+		{
+			case 0:
+			// No such dimension/silo => nothing to select
+			$sHtml = '<div id="SiloSelection"><!-- nothing to select --></div>';
+			break;
+			
+			case 1:
+			// Only one possible choice... no selection, but display the value
+			$oOrg = $oSet->Fetch();
+			$sHtml = '<div id="SiloSelection">'.$oOrg->GetName().'</div>';
+			$sHtml .= '';
+			break;
+			
+			default:
+			$sHtml = '<div id="SiloSelection">';
+			$sHtml .= '<form style="display:inline" action="'.$_SERVER['PHP_SELF'].'"><select style="width:150px;font-size:x-small" name="org_id" title="Pick an organization" onChange="this.form.submit();">';
+			$sSelected = ($this->m_currentOrganization == '') ? ' selected' : '';
+			$sHtml .= '<option value=""'.$sSelected.'>'.Dict::S('UI:AllOrganizations').'</option>';
+			while($oOrg = $oSet->Fetch())
 			{
-				$sSelected = "";
+				if ($this->m_currentOrganization == $oOrg->GetKey())
+				{
+					$oCurrentOrganization = $oOrg;
+					$sSelected = " selected";
+			
+				}
+				else
+				{
+					$sSelected = "";
+				}
+				$sHtml .= '<option value="'.$oOrg->GetKey().'"'.$sSelected.'>'.$oOrg->GetName().'</option>';
 			}
-			$sHtml .= '<option value="'.$oOrg->GetKey().'"'.$sSelected.'>'.$oOrg->Get('name').'</option>';
+			$sHtml .= '</select>';
+			// Add other dimensions/context information to this form
+			$oAppContext = new ApplicationContext();
+			$oAppContext->Reset('org_id'); // Org id is handled above and we want to be able to change it here !
+			$sHtml .= $oAppContext->GetForForm();		
+			$sHtml .= '</form>';
+			$sHtml .= '</div>';
 		}
-		$sHtml .= '</select>';
-		// Add other dimensions/context information to this form
-		$oAppContext = new ApplicationContext();
-		$oAppContext->Reset('org_id'); // Org id is handled above and we want to be able to change it here !
-		$sHtml .= $oAppContext->GetForForm();		
-		$sHtml .= '</form>';
-		$sHtml .= '</div>';
 		return $sHtml;		
 	}
 	
@@ -341,12 +395,12 @@ EOF
 		if (ITOP_REVISION == '$WCREV$')
 		{
 			// This is NOT a version built using the buil system, just display the main version
-			$sVersionString = "iTop Version ".ITOP_VERSION;
+			$sVersionString = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION);
 		}
 		else
 		{
 			// This is a build made from SVN, let display the full information
-			$sVersionString = "iTop Version ".ITOP_VERSION." revision ".ITOP_REVISION.", built on: ".ITOP_BUILD_DATE;
+			$sVersionString = Dict::Format('UI:iTopVersion:Long', ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE);
 		}
 
 		// Render the text of the global search form
@@ -413,7 +467,7 @@ EOF
 		echo '<div id="left-pane" class="ui-layout-west">';
 		echo '<!-- Beginning of the left pane -->';
 		echo '	<div id="header-logo">';
-		echo '	<div id="top-left"></div><div id="logo"><img src="../images/itop-logo.png" style="margin-top:16px; margin-right:40px;"/></div>';
+		echo '	<div id="top-left"></div><div id="logo"><a href="http://www.combodo.com/itop"><img src="../images/itop-logo.png" title="'.$sVersionString.'" style="border:0; margin-top:16px; margin-right:40px;"/></a></div>';
 		echo '	</div>';
 		echo '	<div class="header-menu">';
 		echo '		<div class="icon ui-state-default ui-corner-all"><span id="tPinMenu" class="ui-icon ui-icon-pin-w">pin</span></div>';

+ 32 - 7
application/loginwebpage.class.inc.php

@@ -32,33 +32,56 @@ class LoginWebPage extends NiceWebPage
     public function __construct()
     {
         parent::__construct("iTop Login");
-        $this->add_style("
+        $this->add_style(<<<EOF
 body {
-	background-color: #eee;
+	background: #eee;
 	margin: 0;
 	padding: 0;
 }
+#login-logo {
+	margin-top: 150px;
+	width: 250px;
+	padding-left: 20px;
+	padding-right: 20px;
+	padding-top: 10px;
+	padding-bottom: 10px;
+	margin-left: auto;
+	margin-right: auto;
+	background: #f6f6f1;
+	height: 54px;
+	border-top: 1px solid #000;
+	border-left: 1px solid #000;
+	border-right: 1px solid #000;
+	border-bottom: 0;
+	text-align: center;
+}
+#login-logo img {
+	border: 0;
+}
 #login {
-	width: 230px;
+	width: 250px;
 	margin-left: auto;
 	margin-right: auto;
-	margin-top: 150px;
 	padding: 20px;
 	background-color: #fff;
-	border: 1px solid #000;
+	border-bottom: 1px solid #000;
+	border-left: 1px solid #000;
+	border-right: 1px solid #000;
+	border-top: 0;
 }
 .center {
 	text-align: center;
 }
 
 h1 {
-	color: #83b217;
+	color: #1C94C4;
 	font-size: 16pt;
 }
 .v-spacer {
 	padding-top: 1em;
 }
-		");
+EOF
+);
 	}
 	
 	public function DisplayLoginForm($bFailedLogin = false)
@@ -66,6 +89,8 @@ h1 {
 		$sAuthUser = utils::ReadParam('auth_user', '');
 		$sAuthPwd = utils::ReadParam('suggest_pwd', '');
 
+		$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION);
+		$this->add("<div id=\"login-logo\"><a href=\"http://www.combodo.com/itop\"><img title=\"$sVersionShort\" src=\"../images/itop-logo.png\"></a></div>\n");
 		$this->add("<div id=\"login\">\n");
 		$this->add("<h1>".Dict::S('UI:Login:Welcome')."</h1>\n");
 		if ($bFailedLogin)

+ 1 - 1
application/menunode.class.inc.php

@@ -276,7 +276,7 @@ abstract class MenuNode
 	public function GetHyperlink($aExtraParams)
 	{
 		$aExtraParams['menu'] = $this->GetIndex();
-		return $this->AddParams('../pages/UI.php?menu=', $aExtraParams);
+		return $this->AddParams('../pages/UI.php', $aExtraParams);
 	}
 	
 	public abstract function RenderContent(WebPage $oPage, $aExtraParams = array());

+ 162 - 0
application/user.preferences.class.inc.php

@@ -0,0 +1,162 @@
+<?php
+// Copyright (C) 2010 Combodo SARL
+//
+//   This program is free software; you can redistribute it and/or modify
+//   it under the terms of the GNU General Public License as published by
+//   the Free Software Foundation; version 3 of the License.
+//
+//   This program 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 General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; if not, write to the Free Software
+//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+/**
+ * Store and retrieve user's preferences (i.e persistent per user settings)
+ *
+ * @author      Erwan Taloc <erwan.taloc@combodo.com>
+ * @author      Romain Quetiez <romain.quetiez@combodo.com>
+ * @author      Denis Flaven <denis.flaven@combodo.com>
+ * @license     http://www.opensource.org/licenses/gpl-3.0.html LGPL
+ */
+require_once('../core/dbobject.class.php');
+require_once('../core/userrights.class.inc.php');
+
+/**
+ * This class is used to store, in a persistent manner, user related settings (preferences)
+ * For each user, one record in the database will be created, making their preferences permanent and storing a list
+ * of properties (pairs of name/value strings)
+ * This overcomes some limitations of cookies: limited number of cookies, maximum size, depends on the browser, etc..
+ * This class is used in conjunction with the GetUserPreferences/SetUserPreferences javascript functions (utils.js)
+ */
+class appUserPreferences extends DBObject
+{
+	static $oUserPrefs = null; // Local cache
+	
+	/**
+	 * Get the value of the given property/preference
+	 * If not set, the default value will be returned
+	 * @param string $sCode Code/Name of the property to set
+	 * @param string $sDefaultValue The default value
+	 * @return string The value of the property for the current user
+	 */
+	static function GetPref($sCode, $sDefaultValue)
+	{
+		if (self::$oUserPrefs == null)
+		{
+			self::Load();
+		}
+		$aPrefs = self::$oUserPrefs->Get('preferences');
+		if (isset($aPrefs[$sCode]))
+		{
+			return $aPrefs[$sCode];
+		}
+		else
+		{
+			return $sDefaultValue;
+		}
+	}
+	
+	/**
+	 * Set the value for a given preference, and stores it into the database
+	 * @param string $sCode Code/Name of the property/preference to set
+	 * @param string $sValue Value to set
+	 */
+	static function SetPref($sCode, $sValue)
+	{
+		if (self::$oUserPrefs == null)
+		{
+			self::Load();
+		}
+		$aPrefs = self::$oUserPrefs->Get('preferences');
+		$aPrefs[$sCode] = $sValue;
+		self::$oUserPrefs->Set('preferences', $aPrefs);
+		self::Save();
+	}
+	
+	/**
+	 * Call this function to get all the preferences for the user, packed as a JSON object
+	 * @return string JSON representation of the preferences
+	 */
+	static function GetAsJSON()
+	{
+		if (self::$oUserPrefs == null)
+		{
+			self::Load();
+		}
+		$aPrefs = self::$oUserPrefs->Get('preferences');
+		return json_encode($aPrefs);
+	}
+
+	/**
+	 * Call this function if the user has changed (like when doing a logoff...)
+	 */
+	static public function Reset()
+	{
+		self::$oUserPrefs = null;
+	}
+	/**
+	 * Call this function to ERASE all the preferences from the current user
+	 */
+	static public function ClearPreferences()
+	{
+		self::$oUserPrefs = null;
+	}
+	
+	static protected function Save()
+	{
+		if (self::$oUserPrefs != null)
+		{
+			if (self::$oUserPrefs->IsModified())
+			{
+				self::$oUserPrefs->DBUpdate();
+			}
+		}
+	}
+	
+	/**
+	 * Loads the preferences for the current user, creating the record in the database
+	 * if needed
+	 */
+	static protected function Load()
+	{
+		if (self::$oUserPrefs != null) return;
+		$oSearch = new DBObjectSearch('appUserPreferences');
+		$oSearch->AddCondition('userid', UserRights::GetUser());
+		$oSet = new DBObjectSet($oSearch);
+		$oObj = $oSet->Fetch();
+		if ($oObj == null)
+		{
+			// No prefs (yet) for this user, create the object
+			$oObj = new appUserPreferences();
+			$oObj->Set('userid', UserRights::GetUser());
+			$oObj->Set('preferences', array()); // Default preferences: an empty array
+			$oObj->DBInsert();
+		}
+		self::$oUserPrefs = $oObj;
+	}
+
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "gui",
+			"key_type" => "autoincrement",
+			"name_attcode" => "userid",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "priv_app_preferences",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+		);
+		
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"URP_Users", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributePropertySet("preferences", array("allowed_values"=>null, "sql"=>"preferences", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
+	}
+	
+}
+?>

+ 1 - 0
core/config.class.inc.php

@@ -468,6 +468,7 @@ class Config
 			fwrite($hFile, "\t'application' => array (\n");
 			fwrite($hFile, "\t\t'../application/transaction.class.inc.php',\n");
 			fwrite($hFile, "\t\t'../application/menunode.class.inc.php',\n");
+			fwrite($hFile, "\t\t'../application/user.preferences.class.inc.php',\n");
 			fwrite($hFile, "\t\t'../application/audit.rule.class.inc.php',\n");
 // Romain - That's dirty, because those 3 classes are in fact part of the core
 //          but I needed those classes to be derived from cmdbAbstractObject

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

@@ -875,6 +875,9 @@ When associated with a trigger, each action is given an "order" number, specifyi
 	'UI:UserAccountsMenu' => 'User Accounts',
 	'UI:UserAccountsMenu+' => 'User Accounts',
 	'UI:UserAccountsMenu:Title' => 'User Accounts',	
+
+	'UI:iTopVersion:Short' => 'iTop version %1$s',
+	'UI:iTopVersion:Long' => 'iTop version %1$s-%2$s built on %3$s',
 ));
 
 

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

@@ -874,6 +874,9 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
 	'UI:UserAccountsMenu+' => 'Comptes Utilisateur',
 	'UI:UserAccountsMenu:Title' => 'Comptes Utilisateur',	
 
+	'UI:iTopVersion:Short' => 'iTop version %1$s',
+	'UI:iTopVersion:Long' => 'iTop version %1$s-%2$s du %3$s',
+
 ));
 
 ?>

+ 40 - 1
js/utils.js

@@ -77,4 +77,43 @@ function ReloadSearchForm(divId, sClassName, sBaseClass)
 		   $('#'+divId).unblock();
 	   }
 	 );
-}
+}
+
+/**
+ * Stores - in a persistent way - user specific preferences
+ * depends on a global variable oUserPreferences created/filled by the iTopWebPage
+ * that acts as a local -write through- cache
+ */
+function SetUserPreference(sPreferenceCode, sPrefValue, bPersistent)
+{
+	sPreviousValue = undefined;
+	try
+	{
+		sPreviousValue = oUserPreferences[sPreferenceCode];
+	}
+	catch(err)
+	{
+		sPreviousValue = undefined;
+	}
+    oUserPreferences[sPreferenceCode] = sPrefValue;
+    if (bPersistent && (sPrefValue != sPreviousValue))
+    {
+    	ajax_request = $.post('ajax.render.php',
+    						  { operation: 'set_pref', code: sPreferenceCode, value: sPrefValue} ); // Make it persistent
+    }
+}
+
+/**
+ * Get user specific preferences
+ * depends on a global variable oUserPreferences created/filled by the iTopWebPage
+ * that acts as a local -write through- cache
+ */
+function GetUserPreference(sPreferenceCode, sDefaultValue)
+{
+	var value = sDefaultValue;
+	if ( oUserPreferences[sPreferenceCode] != undefined)
+	{
+		value = oUserPreferences[sPreferenceCode];
+	}
+	return value;
+}

+ 13 - 0
pages/ajax.render.php

@@ -30,6 +30,8 @@ require_once('../application/wizardhelper.class.inc.php');
 require_once('../application/ui.linkswidget.class.inc.php');
 
 require_once('../application/startup.inc.php');
+require_once('../application/user.preferences.class.inc.php');
+
 session_start();
 if (isset($_SESSION['auth_user']))
 {
@@ -330,6 +332,17 @@ switch($operation)
 	$sHtml = cmdbAbstractObject::GetSearchForm($oPage, $oSet, array('currentId' => $currentId, 'baseClass' => $sRootClass));
 	$oPage->add($sHtml);
 	break;
+	
+	case 'set_pref':
+	$sCode = utils::ReadParam('code', '', 'post');
+	$sValue = utils::ReadParam('value', '', 'post');
+	appUserPreferences::SetPref($sCode, $sValue);
+	break;
+
+	case 'erase_all_pref':
+	// Can be useful in case a user got some corrupted prefs...
+	appUserPreferences::ClearPreferences();
+	break;
 
 	default:
 	$oPage->p("Invalid query.");

+ 1 - 1
setup/setuppage.class.inc.php

@@ -55,7 +55,7 @@ body {
 }
 
 h1 {
-	color: #83b217;
+	color: #1C94C4;
 	font-size: 16pt;
 }
 h2 {