فهرست منبع

#423 Fixed issues with application root URL = f(mode CLI, modules, web server techno, etc.)

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@1304 a333f486-631f-4898-b8df-5754b55c2be0
romainq 14 سال پیش
والد
کامیت
8e56399e7f

+ 104 - 0
application/applicationcontext.class.inc.php

@@ -24,6 +24,43 @@
  */
 
 require_once(APPROOT."/application/utils.inc.php");
+
+/**
+ * Interface for directing end-users to the relevant application
+ */ 
+interface iDBObjectURLMaker
+{
+	public static function MakeObjectURL($sClass, $iId);
+}
+
+/**
+ * Direct end-users to the standard iTop application: UI.php
+ */ 
+class iTopStandardURLMaker implements iDBObjectURLMaker
+{
+	public static function MakeObjectURL($sClass, $iId)
+	{
+		$sPage = DBObject::ComputeStandardUIPage($sClass);
+		$sAbsoluteUrl = utils::GetAbsoluteUrlAppRoot();
+		$sUrl = "{$sAbsoluteUrl}pages/$sPage?operation=details&class=$sClass&id=$iId";
+		return $sUrl;
+	}
+}
+
+/**
+ * Direct end-users to the standard Portal application
+ */ 
+class PortalURLMaker implements iDBObjectURLMaker
+{
+	public static function MakeObjectURL($sClass, $iId)
+	{
+		$sAbsoluteUrl = utils::GetAbsoluteUrlAppRoot();
+		$sUrl = "{$sAbsoluteUrl}portal/index.php?operation=details&class=$sClass&id=$iId";
+		return $sUrl;
+	}
+}
+
+
 /**
  * Helper class to store and manipulate the parameters that make the application's context
  *
@@ -166,5 +203,72 @@ class ApplicationContext
 			unset($this->aValues[$sParamName]);
 		}
 	}
+
+	static $m_sUrlMakerClass = null;
+
+	/**
+	 * Set the current application url provider
+	 * @param sClass string Class implementing iDBObjectURLMaker	 
+	 * @return void
+	 */
+	public static function SetUrlMakerClass($sClass = 'iTopStandardURLMaker')
+	{
+		$sPrevious = self::GetUrlMakerClass();
+
+		self::$m_sUrlMakerClass = $sClass;
+		$_SESSION['UrlMakerClass'] = $sClass;
+
+		return $sPrevious;
+	}
+
+	/**
+	 * Get the current application url provider
+	 * @return string the name of the class
+	 */
+	public static function GetUrlMakerClass()
+	{
+		if (is_null(self::$m_sUrlMakerClass))
+		{
+			if (isset($_SESSION['UrlMakerClass']))
+			{
+				self::$m_sUrlMakerClass = $_SESSION['UrlMakerClass'];
+			}
+			else
+			{
+				self::$m_sUrlMakerClass = 'iTopStandardURLMaker';
+			}
+		}
+		return self::$m_sUrlMakerClass;
+	}
+
+	/**
+	 * Get the current application url provider
+	 * @return string the name of the class
+	 */
+   public static function MakeObjectUrl($sObjClass, $sObjKey, $sUrlMakerClass = null, $bWithNavigationContext = true)
+   {
+   	$oAppContext = new ApplicationContext();
+
+      if (is_null($sUrlMakerClass))
+      {
+			$sUrlMakerClass = self::GetUrlMakerClass();
+		}
+		$sUrl = call_user_func(array($sUrlMakerClass, 'MakeObjectUrl'), $sObjClass, $sObjKey);
+		if (strlen($sUrl) > 0)
+		{
+			if ($bWithNavigationContext)
+			{
+				return $sUrl."&".$oAppContext->GetForLink();
+			}
+			else
+			{
+				return $sUrl;
+			}
+		}
+		else
+		{
+			return '';
+		}	
+	}
 }
 ?>

+ 6 - 5
application/displayblock.class.inc.php

@@ -999,8 +999,9 @@ class MenuBlock extends DisplayBlock
 		$sClass = $this->m_oFilter->GetClass();
 		$oSet = new CMDBObjectSet($this->m_oFilter);
 		$sFilter = $this->m_oFilter->serialize();
+		$sFilterDesc = $this->m_oFilter->ToOql();
 		$aActions = array();
-		$sUIPage = cmdbAbstractObject::ComputeUIPage($sClass);
+		$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass);
 		// 1:n links, populate the target object as a default value when creating a new linked object
 		if (isset($aExtraParams['target_attr']))
 		{
@@ -1032,7 +1033,6 @@ class MenuBlock extends DisplayBlock
 			// Just one object in the set, possible actions are "new / clone / modify and delete"
 			if (!isset($aExtraParams['link_attr']))
 			{
-				$sUrl = utils::GetAbsoluteUrl(false);
 				if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:Modify'), 'url' => "../pages/$sUIPage?operation=modify&class=$sClass&id=$id&$sContext#"); }
 				if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext{$sDefault}"); }
 				if ($bIsDeleteAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:Delete'), 'url' => "../pages/$sUIPage?operation=delete&class=$sClass&id=$id&$sContext"); }
@@ -1068,7 +1068,8 @@ class MenuBlock extends DisplayBlock
 				}
 				$this->AddMenuSeparator($aActions);
 				// Static menus: Email this page & CSV Export
-				$aActions[] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=".$oObj->GetName()."&body=".urlencode("$sUrl?operation=details&class=$sClass&id=$id&$sContext"));
+				$sUrl = ApplicationContext::MakeObjectUrl($sClass, $id);
+				$aActions[] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=".$oObj->GetName()."&body=".urlencode($sUrl));
 				$aActions[] = array ('label' => Dict::S('UI:Menu:CSVExport'), 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext");
 			}
 			else
@@ -1112,7 +1113,6 @@ class MenuBlock extends DisplayBlock
 			else
 			{
 				// many objects in the set, possible actions are: new / modify all / delete all
-				$sUrl = utils::GetAbsoluteUrl();
 				if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext{$sDefault}"); }
 				if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:ModifyAll'), 'url' => "../pages/$sUIPage?operation=select_for_modify_all&class=$sClass&filter=$sFilter&sContext"); }
 				if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:BulkDelete'), 'url' => "../pages/$sUIPage?operation=select_for_deletion&filter=$sFilter&$sContext"); }
@@ -1157,7 +1157,8 @@ class MenuBlock extends DisplayBlock
 					}
 				}
 				$this->AddMenuSeparator($aActions);
-				$aActions[] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("$sUrl?operation=search&filter=$sFilter&$sContext"));
+				$sUrl = utils::GetAbsoluteUrlAppRoot();
+				$aActions[] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=$sFilterDesc&body=".urlencode("{$sUrl}pages/$sUIPage?operation=search&filter=$sFilter&$sContext"));
 				$aActions[] = array ('label' => Dict::S('UI:Menu:CSVExport'), 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext");
 			}
 			$this->AddMenuSeparator($aActions);

+ 8 - 5
application/itopwebpage.class.inc.php

@@ -39,11 +39,14 @@ class iTopWebPage extends NiceWebPage
 	private $m_sMessage;
 	private $m_sInitScript;
 	
-    public function __construct($sTitle)
-    {
-        parent::__construct($sTitle);
-        $this->m_sCurrentTabContainer = '';
-        $this->m_sCurrentTab = '';
+	public function __construct($sTitle)
+	{
+		parent::__construct($sTitle);
+
+		ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker');
+
+		$this->m_sCurrentTabContainer = '';
+		$this->m_sCurrentTab = '';
 		$this->m_aTabs = array();
 		$this->m_sMenu = "";
 		$this->m_sMessage = '';

+ 2 - 4
application/loginwebpage.class.inc.php

@@ -223,10 +223,8 @@ EOF
 	{
 		if (self::SecureConnectionRequired() && !self::IsConnectionSecure())
 		{
-			// Non secured URL... redirect to a secured one
-			$sUrl = Utils::GetAbsoluteUrl(true /* query string */, true /* force HTTPS */);
-			header("Location: $sUrl");			
-			exit;
+			// Non secured URL... request for a secure connection
+			throw new Exception('Secure connection required!');			
 		}
 
 		$aAllowedLoginTypes = MetaModel::GetConfig()->GetAllowedLoginTypes();

+ 45 - 16
application/utils.inc.php

@@ -359,7 +359,7 @@ class utils
 	{
 		// Build an absolute URL to this page on this server/port
 		$sServerName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
-		if (MetaModel::GetConfig()->GetSecureConnectionRequired() || MetaModel::GetConfig()->GetHttpsHyperlinks())
+		if (MetaModel::GetConfig()->GetSecureConnectionRequired())
 		{
 			// If a secure connection is required, or if the URL is requested to start with HTTPS
 			// then any URL must start with https !
@@ -414,18 +414,6 @@ class utils
 	}
 	
     /**
-     * Returns the absolute URL PATH of the current page
-     * @param $bForceHTTPS bool True to force HTTPS, false otherwise
-     * @return string The absolute URL to the current page
-     */                   
-	static public function GetAbsoluteUrlPath($bForceHTTPS = false)
-	{
-		$sAbsoluteUrl = self::GetAbsoluteUrl(false, $bForceHTTPS); // False => Don't get the query string
-		$sAbsoluteUrl = substr($sAbsoluteUrl, 0, 1+strrpos($sAbsoluteUrl, '/')); // remove the current page, keep just the path, up to the last /
-		return $sAbsoluteUrl;
-	}
-
-    /**
      * Returns the absolute URL to the server's root path
      * @param $sCurrentRelativePath string NO MORE USED, kept for backward compatibility only !
      * @param $bForceHTTPS bool True to force HTTPS, false otherwise
@@ -433,7 +421,48 @@ class utils
      */                   
 	static public function GetAbsoluteUrlAppRoot($sCurrentRelativePathUNUSED = '', $bForceHTTPS = false)
 	{
-		$sAbsoluteUrl = self::GetAbsoluteUrl(false, $bForceHTTPS); // False => Don't get the query string
+		return MetaModel::GetConfig()->Get('app_root_url');
+	}
+
+	static public function GetDefaultUrlAppRoot()
+	{
+		// Build an absolute URL to this page on this server/port
+		$sServerName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
+		$sProtocol = (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!="off")) ? 'https' : 'http';
+		$iPort = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
+		if ($sProtocol == 'http')
+		{
+			$sPort = ($iPort == 80) ? '' : ':'.$iPort;
+		}
+		else
+		{
+			$sPort = ($iPort == 443) ? '' : ':'.$iPort;
+		}
+		// $_SERVER['REQUEST_URI'] is empty when running on IIS
+		// Let's use Ivan Tcholakov's fix (found on www.dokeos.com)
+		if (!empty($_SERVER['REQUEST_URI']))
+		{
+			$sPath = $_SERVER['REQUEST_URI'];
+		}
+		else
+		{
+			$sPath = $_SERVER['SCRIPT_NAME'];
+			if (!empty($_SERVER['QUERY_STRING']))
+			{
+				$sPath .= '?'.$_SERVER['QUERY_STRING'];
+			}
+			$_SERVER['REQUEST_URI'] = $sPath;
+		}
+		$sPath = $_SERVER['REQUEST_URI'];
+
+		// remove all the parameters from the query string
+		$iQuestionMarkPos = strpos($sPath, '?');
+		if ($iQuestionMarkPos !== false)
+		{
+			$sPath = substr($sPath, 0, $iQuestionMarkPos);
+		}
+		$sAbsoluteUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}";
+
 		$sCurrentScript = realpath($_SERVER['SCRIPT_FILENAME']);
 		$sCurrentScript = str_replace('\\', '/', $sCurrentScript); // canonical path
 		$sAppRoot = str_replace('\\', '/', APPROOT); // canonical path
@@ -442,13 +471,13 @@ class utils
 		$sAppRootPos = strpos($sAbsoluteUrl, $sCurrentRelativePath);
 		if ($sAppRootPos !== false)
 		{
-			$sAbsoluteUrl = substr($sAbsoluteUrl, 0, $sAppRootPos); // remove the current page and path
+			$sAppRootUrl = substr($sAbsoluteUrl, 0, $sAppRootPos); // remove the current page and path
 		}
 		else
 		{
 			throw new Exception("Failed to determine application root path $sAbsoluteUrl ($sCurrentRelativePath) APPROOT:'$sAppRoot'");
 		}
-		return $sAbsoluteUrl;
+		return $sAppRootUrl;
 	}
 
 	/**

+ 27 - 19
core/action.class.inc.php

@@ -274,25 +274,33 @@ class ActionEmail extends ActionNotification
 
 	protected function _DoExecute($oTrigger, $aContextArgs, &$oLog)
 	{
-		$this->m_iRecipients = 0;
-		$this->m_aMailErrors = array();
-		$bRes = false; // until we do succeed in sending the email
-
-		// Determine recicipients
-		//
-		$sTo = $this->FindRecipients('to', $aContextArgs);
-		$sCC = $this->FindRecipients('cc', $aContextArgs);
-		$sBCC = $this->FindRecipients('bcc', $aContextArgs);
-
-		$sFrom = $this->Get('from');
-		$sReplyTo = $this->Get('reply_to');
-
-		$sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs);
-		$sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs);
-		
-		$oObj = $aContextArgs['this->object()'];
-		$sServerIP = $_SERVER['SERVER_ADDR']; //gethostbyname(gethostname());
-		$sReference = '<iTop/'.get_class($oObj).'/'.$oObj->GetKey().'@'.$sServerIP.'>';
+		$sPreviousUrlMaker = ApplicationContext::SetUrlMakerClass();
+		try
+		{
+			$this->m_iRecipients = 0;
+			$this->m_aMailErrors = array();
+			$bRes = false; // until we do succeed in sending the email
+	
+			// Determine recicipients
+			//
+			$sTo = $this->FindRecipients('to', $aContextArgs);
+			$sCC = $this->FindRecipients('cc', $aContextArgs);
+			$sBCC = $this->FindRecipients('bcc', $aContextArgs);
+	
+			$sFrom = $this->Get('from');
+			$sReplyTo = $this->Get('reply_to');
+	
+			$sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs);
+			$sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs);
+			
+			$oObj = $aContextArgs['this->object()'];
+			$sReference = '<iTop/'.get_class($oObj).'/'.$oObj->GetKey().'>';
+		}
+		catch(Exception $e)
+		{
+  			ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
+  			throw $e;
+  		}
 
 		if (!is_null($oLog))
 		{

+ 20 - 22
core/config.class.inc.php

@@ -58,7 +58,6 @@ define ('DEFAULT_MAX_DISPLAY_LIMIT', 15);
 define ('DEFAULT_STANDARD_RELOAD_INTERVAL', 5*60);
 define ('DEFAULT_FAST_RELOAD_INTERVAL', 1*60);
 define ('DEFAULT_SECURE_CONNECTION_REQUIRED', false);
-define ('DEFAULT_HTTPS_HYPERLINKS', false);
 define ('DEFAULT_ALLOWED_LOGIN_TYPES', 'form|basic|external');
 define ('DEFAULT_EXT_AUTH_VARIABLE', '$_SERVER[\'REMOTE_USER\']');
 define ('DEFAULT_ENCRYPTION_KEY', '@iT0pEncr1pti0n!'); // We'll use a random value, later...
@@ -85,6 +84,14 @@ class Config
 	// New way to store the settings !
 	//
 	protected $m_aSettings = array(
+		'app_root_url' => array(
+			'type' => 'string',
+			'description' => 'Root URL used for navigating within the application, or from an email to the application',
+			'default' => '',
+			'value' => '',
+			'source_of_value' => '',
+			'show_in_conf_sample' => true,
+		),
 		'skip_check_to_write' => array(
 			'type' => 'bool',
 			'description' => 'Disable data format and integrity checks to boost up data load (insert or update)',
@@ -452,13 +459,6 @@ class Config
 	protected $m_bSecureConnectionRequired;
 
 	/**
-	 * @var boolean Forces iTop to output hyperlinks starting with https:// even
-	 *              if the current page is not using https. This can be useful when
-	 *              the application runs behind a SSL gateway
-	 */	 	
-	protected $m_bHttpsHyperlinks;
-
-	/**
 	 * @var string Langage code, default if the user language is undefined
 	 */	 	
 	protected $m_sDefaultLanguage;
@@ -559,7 +559,6 @@ class Config
 		$this->m_iStandardReloadInterval = DEFAULT_STANDARD_RELOAD_INTERVAL;
 		$this->m_iFastReloadInterval = DEFAULT_FAST_RELOAD_INTERVAL;
 		$this->m_bSecureConnectionRequired = DEFAULT_SECURE_CONNECTION_REQUIRED;
-		$this->m_bHttpsHyperlinks = DEFAULT_HTTPS_HYPERLINKS;
 		$this->m_sDefaultLanguage = 'EN US';
 		$this->m_sAllowedLoginTypes = DEFAULT_ALLOWED_LOGIN_TYPES;
 		$this->m_sExtAuthVariable = DEFAULT_EXT_AUTH_VARIABLE;
@@ -573,6 +572,18 @@ class Config
 			$this->Load($sConfigFile);
 			$this->Verify();
 		}
+
+      // Application root url: set a default value, then normalize it
+      $sAppRootUrl = trim($this->Get('app_root_url'));
+		if (strlen($sAppRootUrl) == 0)
+      {
+			$sAppRootUrl = utils::GetDefaultUrlAppRoot();
+		}
+		if (substr($sAppRootUrl, -1, 1) != '/')
+		{
+			$sAppRootUrl .= '/';
+		}
+		$this->Set('app_root_url', $sAppRootUrl);
 	}
 
 	protected function CheckFile($sPurpose, $sFileName)
@@ -681,7 +692,6 @@ class Config
 		$this->m_iStandardReloadInterval = isset($MySettings['standard_reload_interval']) ? trim($MySettings['standard_reload_interval']) : DEFAULT_STANDARD_RELOAD_INTERVAL;
 		$this->m_iFastReloadInterval = isset($MySettings['fast_reload_interval']) ? trim($MySettings['fast_reload_interval']) : DEFAULT_FAST_RELOAD_INTERVAL;
 		$this->m_bSecureConnectionRequired = isset($MySettings['secure_connection_required']) ? (bool) trim($MySettings['secure_connection_required']) : DEFAULT_SECURE_CONNECTION_REQUIRED;
-		$this->m_bHttpsHyperlinks = isset($MySettings['https_hyperlinks']) ? (bool) trim($MySettings['https_hyperlinks']) : DEFAULT_HTTPS_HYPERLINKS;
 
 		$this->m_aModuleSettings = isset($MyModuleSettings) ?  $MyModuleSettings : array();
 
@@ -857,11 +867,6 @@ class Config
 		return $this->m_bSecureConnectionRequired;
 	}
 
-	public function GetHttpsHyperlinks()
-	{
-		return $this->m_bHttpsHyperlinks;
-	}
-
 	public function GetDefaultLanguage()
 	{
 		return $this->m_sDefaultLanguage;
@@ -967,11 +972,6 @@ class Config
 		$this->m_bSecureConnectionRequired = $bSecureConnectionRequired;
 	}
 
-	public function SetHttpsHyperlinks($bHttpsHyperlinks)
-	{
-		$this->m_bHttpsHyperlinks = $bHttpsHyperlinks;
-	}
-
 	public function SetDefaultLanguage($sLanguageCode)
 	{
 		$this->m_sDefaultLanguage = $sLanguageCode;
@@ -1037,7 +1037,6 @@ class Config
 		$aSettings['standard_reload_interval'] = $this->m_iStandardReloadInterval;
 		$aSettings['fast_reload_interval'] = $this->m_iFastReloadInterval;
 		$aSettings['secure_connection_required'] = $this->m_bSecureConnectionRequired;
-		$aSettings['https_hyperlinks'] = $this->m_bHttpsHyperlinks;
 		$aSettings['default_language'] = $this->m_sDefaultLanguage;
 		$aSettings['allowed_login_types'] = $this->m_sAllowedLoginTypes;
 		$aSettings['encryption_key'] = $this->m_sEncryptionKey;
@@ -1132,7 +1131,6 @@ class Config
 			fwrite($hFile, "\t'standard_reload_interval' => {$this->m_iStandardReloadInterval},\n");
 			fwrite($hFile, "\t'fast_reload_interval' => {$this->m_iFastReloadInterval},\n");
 			fwrite($hFile, "\t'secure_connection_required' => ".($this->m_bSecureConnectionRequired ? 'true' : 'false').",\n");
-			fwrite($hFile, "\t'https_hyperlinks' => ".($this->m_bHttpsHyperlinks ? 'true' : 'false').",\n");
 			fwrite($hFile, "\t'default_language' => '{$this->m_sDefaultLanguage}',\n");
 			fwrite($hFile, "\t'allowed_login_types' => '{$this->m_sAllowedLoginTypes}',\n");
 			fwrite($hFile, "\t'encryption_key' => '{$this->m_sEncryptionKey}',\n");

+ 15 - 23
core/dbobject.class.php

@@ -528,14 +528,10 @@ abstract class DBObject
 		return $oAtt->GetAsCSV($this->GetOriginal($sAttCode), $sSeparator, $sTextQualifier, $this);
 	}
 
-	protected static function MakeHyperLink($sObjClass, $sObjKey, $sLabel = '')
+	protected static function MakeHyperLink($sObjClass, $sObjKey, $sLabel = '', $sUrlMakerClass = null, $bWithNavigationContext = true)
 	{
 		if ($sObjKey <= 0) return '<em>'.Dict::S('UI:UndefinedObject').'</em>'; // Objects built in memory have negative IDs
 
-		$oAppContext = new ApplicationContext();	
-		$sPage = self::ComputeUIPage($sObjClass);
-		$sAbsoluteUrl = utils::GetAbsoluteUrlPath();
-
 		// Safety net
 		//
 		if (empty($sLabel))
@@ -547,25 +543,23 @@ abstract class DBObject
 			//$sLabel = MetaModel::GetName($sObjClass)." #$sObjKey";
 		}
 		$sHint = MetaModel::GetName($sObjClass)."::$sObjKey";
-		return "<a href=\"{$sAbsoluteUrl}{$sPage}?operation=details&class=$sObjClass&id=$sObjKey&".$oAppContext->GetForLink()."\" title=\"$sHint\">$sLabel</a>";
+		$sUrl = ApplicationContext::MakeObjectUrl($sObjClass, $sObjKey, $sUrlMakerClass, $bWithNavigationContext);
+		if (strlen($sUrl) > 0)
+		{
+			return "<a href=\"$sUrl\" title=\"$sHint\">$sLabel</a>";
+		}
+		else
+		{
+			return $sLabel;
+		}
 	}
 
-	public function GetHyperlink()
+	public function GetHyperlink($sUrlMakerClass = null, $bWithNavigationContext = true)
 	{
-		if ($this->IsNew()) return '<em>'.Dict::S('UI:UndefinedObject').'</em>'; // Objects built in memory have negative IDs
-
-		$oAppContext = new ApplicationContext();	
-		$sPage = $this->GetUIPage();
-		$sAbsoluteUrl = utils::GetAbsoluteUrlPath();
-		$sObjClass = get_class($this);
-		$sObjKey = $this->GetKey();
-
-		$sLabel = $this->GetName();
-		$sHint = MetaModel::GetName($sObjClass)."::$sObjKey";
-		return "<a href=\"{$sAbsoluteUrl}{$sPage}?operation=details&class=$sObjClass&id=$sObjKey&".$oAppContext->GetForLink()."\" title=\"$sHint\">$sLabel</a>";
+		return self::MakeHyperLink(get_class($this), $this->GetKey(), $this->GetName(), $sUrlMakerClass, $bWithNavigationContext);
 	}
 	
-	public static function ComputeUIPage($sClass)
+	public static function ComputeStandardUIPage($sClass)
 	{
 		static $aUIPagesCache = array(); // Cache to store the php page used to display each class of object
 		if (!isset($aUIPagesCache[$sClass]))
@@ -1464,10 +1458,8 @@ abstract class DBObject
 			$aScalarArgs[$sArgName] = $this->GetKey();
 			$aScalarArgs[$sArgName.'->id'] = $this->GetKey();
 			$aScalarArgs[$sArgName.'->object()'] = $this;
-			$aScalarArgs[$sArgName.'->hyperlink()'] = $this->GetHyperlink();
-			// #@# Prototype for a user portal - to be dehardcoded later
-			$sToPortal = utils::GetAbsoluteUrlPath().'../portal/index.php?operation=details&id='.$this->GetKey();
-			$aScalarArgs[$sArgName.'->hyperlink(portal)'] = '<a href="'.$sToPortal.'">'.$this->GetName().'</a>';
+			$aScalarArgs[$sArgName.'->hyperlink()'] = $this->GetHyperlink('iTopStandardURLMaker', false);
+			$aScalarArgs[$sArgName.'->hyperlink(portal)'] = $this->GetHyperlink('PortalURLMaker', false);
 			$aScalarArgs[$sArgName.'->name()'] = $this->GetName();
 		
 			$sClass = get_class($this);

+ 1 - 1
pages/logoff.php

@@ -28,7 +28,7 @@ require_once(APPROOT.'/application/loginwebpage.class.inc.php');
 session_name(MetaModel::GetConfig()->Get('session_name'));
 session_start();
 $bPortal = utils::ReadParam('portal', false);
-$sUrl = utils::GetAbsoluteUrlAppRoot('pages/logoff.php');
+$sUrl = utils::GetAbsoluteUrlAppRoot();
 if ($bPortal)
 {
 	$sUrl .= 'portal/';

+ 2 - 4
pages/xml.navigator.php

@@ -97,10 +97,8 @@ function GetRelatedObjectsAsXml(DBObject $oObj, $sRelationName, &$oLinks, &$oXml
 
 function BuildIconPath($sIconPath)
 {
-	$sFullURL = utils::GetAbsoluteURL(false, false);
-	$iLastSlashPos = strrpos($sFullURL, '/');
-	$sFullURLPath = substr($sFullURL, 0, 1 + $iLastSlashPos);
-	return $sFullURLPath.$sIconPath;
+	$sFullURL = utils::GetAbsoluteUrlAppRoot(false, false);
+	return $sFullURL.'pages/'.$sIconPath;
 }
 
 require_once(APPROOT.'/application/startup.inc.php');

+ 2 - 2
synchro/synchrodatasource.class.inc.php

@@ -817,8 +817,8 @@ EOF
 			if ($iErrors > 0)
 			{
 				$sIssuesOQL = "SELECT SynchroReplica WHERE sync_source_id=".$this->GetKey()." AND status_last_error!=''";
-				$sAbsoluteUrl = utils::GetAbsoluteUrlPath();
-				$sIssuesURL = "$sAbsoluteUrl../synchro/replica.php?operation=oql&datasource=".$this->GetKey()."&oql=".urlencode($sIssuesOQL);
+				$sAbsoluteUrl = utils::GetAbsoluteUrlAppRoot();
+				$sIssuesURL = "{$sAbsoluteUrl}synchro/replica.php?operation=oql&datasource=".$this->GetKey()."&oql=".urlencode($sIssuesOQL);
 				$sSeeIssues = "<p></p>";
 
 				$sStatistics = "<h1>Statistics</h1>\n";

+ 4 - 2
webservices/export.php

@@ -35,6 +35,8 @@ require_once(APPROOT.'/application/startup.inc.php');
 require_once(APPROOT.'/application/loginwebpage.class.inc.php');
 LoginWebPage::DoLogin(); // Check user rights and prompt if needed
 
+ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker');
+
 $sOperation = utils::ReadParam('operation', 'menu');
 $oAppContext = new ApplicationContext();
 $iActiveNodeId = utils::ReadParam('menu', -1);
@@ -63,8 +65,8 @@ if (!empty($sExpression))
 				// The HTML output is made for pages located in the /pages/ folder
 				// since this page is in a different folder, let's adjust the HTML 'base' attribute
 				// to make the relative hyperlinks in the page work
-				$sUrl = utils::GetAbsoluteUrlAppRoot('/webservices/');
-				$oP->set_base($sUrl.'/pages/');
+				$sUrl = utils::GetAbsoluteUrlAppRoot();
+				$oP->set_base($sUrl.'pages/');
 
 				$oResultBlock = new DisplayBlock($oFilter, 'list', false, array('menu' => false, 'display_limit' => false, 'zlist' => 'details'));
 				$oResultBlock->Display($oP, 'expresult');