Modularization of the portal. The entry points for portals is now defined in XML, and thus can be altered by an extension.

SVN:trunk[3509]
This commit is contained in:
Denis Flaven
2015-03-23 16:02:44 +00:00
parent fa0d408664
commit 4919ca88ec
13 changed files with 338 additions and 44 deletions

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design>
<portals>
<portal id="legacy_portal" _delta="define">
<url>portal/index.php</url>
<rank>1.0</rank>
<handler/>
<allow>
</allow>
<deny/>
</portal>
<portal id="backoffice" _delta="define">
<url>pages/UI.php</url>
<rank>2.0</rank>
<handler/>
<allow/>
<deny>
<profile id="Portal user"/>
</deny>
</portal>
</portals>
</itop_design>

View File

@@ -25,6 +25,7 @@
*/
require_once(APPROOT."/application/nicewebpage.class.inc.php");
require_once(APPROOT.'/application/portaldispatcher.class.inc.php');
/**
* Web page used for displaying the login form
*/
@@ -428,6 +429,7 @@ EOF
// Unset all of the session variables.
unset($_SESSION['auth_user']);
unset($_SESSION['login_mode']);
unset($_SESSION['profile_list']);
// If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!
}
@@ -654,12 +656,22 @@ EOF
/**
* Overridable: depending on the user, head toward a dedicated portal
* @param bool $bIsAllowedToPortalUsers Whether or not the current page is considered as part of the portal
* @param string|null $sRequestedPortalId
* @param int $iOnExit How to complete the call: redirect or return a code
*/
protected static function ChangeLocation($bIsAllowedToPortalUsers, $iOnExit = self::EXIT_PROMPT)
protected static function ChangeLocation($sRequestedPortalId = null, $iOnExit = self::EXIT_PROMPT)
{
if ( (!$bIsAllowedToPortalUsers) && (UserRights::IsPortalUser()))
$fStart = microtime(true);
$ret = call_user_func(array(self::$sHandlerClass, 'Dispatch'), $sRequestedPortalId);
if ($ret === true)
{
return self::EXIT_CODE_OK;
}
else if($ret === false)
{
throw new Exception('Nowhere to go??');
}
else
{
if ($iOnExit == self::EXIT_RETURN)
{
@@ -668,16 +680,11 @@ EOF
else
{
// No rights to be here, redirect to the portal
header('Location: '.utils::GetAbsoluteUrlAppRoot().'portal/index.php');
header('Location: '.$ret);
}
}
else
{
return self::EXIT_CODE_OK;
}
}
/**
* Check if the user is already authentified, if yes, then performs some additional validations:
* - if $bMustBeAdmin is true, then the user must be an administrator, otherwise an error is displayed
@@ -688,9 +695,56 @@ EOF
*/
static function DoLogin($bMustBeAdmin = false, $bIsAllowedToPortalUsers = false, $iOnExit = self::EXIT_PROMPT)
{
$sMessage = ''; // In case we need to return a message to the calling web page
$sRequestedPortalId = $bIsAllowedToPortalUsers ? 'legacy_portal' : 'backoffice';
return self::DoLoginEx($sRequestedPortalId, $bMustBeAdmin, $iOnExit);
}
/**
* Check if the user is already authentified, if yes, then performs some additional validations to redirect towards the desired "portal"
* @param string|null $sRequestedPortalId The requested "portal" interface, null for any
* @param bool $bMustBeAdmin Whether or not the user must be an admin to access the current page
* @param int iOnExit What action to take if the user is not logged on (one of the class constants EXIT_...)
*/
static function DoLoginEx($sRequestedPortalId = null, $bMustBeAdmin = false, $iOnExit = self::EXIT_PROMPT)
{
$operation = utils::ReadParam('loginop', '');
$sMessage = self::HandleOperations($operation); // May exit directly
$iRet = self::Login($iOnExit);
if ($iRet == self::EXIT_CODE_OK)
{
if ($bMustBeAdmin && !UserRights::IsAdministrator())
{
if ($iOnExit == self::EXIT_RETURN)
{
return self::EXIT_CODE_MUSTBEADMIN;
}
else
{
require_once(APPROOT.'/setup/setuppage.class.inc.php');
$oP = new SetupPage(Dict::S('UI:PageTitle:FatalError'));
$oP->add("<h1>".Dict::S('UI:Login:Error:AccessAdmin')."</h1>\n");
$oP->p("<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/logoff.php\">".Dict::S('UI:LogOffMenu')."</a>");
$oP->output();
exit;
}
}
$iRet = call_user_func(array(self::$sHandlerClass, 'ChangeLocation'), $sRequestedPortalId, $iOnExit);
}
if ($iOnExit == self::EXIT_RETURN)
{
return $iRet;
}
else
{
return $sMessage;
}
}
protected static function HandleOperations($operation)
{
$sMessage = ''; // most of the operations never return, but some can return a message to be displayed
if ($operation == 'logoff')
{
if (isset($_SESSION['login_mode']))
@@ -714,7 +768,7 @@ EOF
$oPage->DisplayLoginForm( $sLoginMode, false /* not a failed attempt */);
$oPage->output();
exit;
}
}
else if ($operation == 'forgot_pwd')
{
$oPage = self::NewLoginWebPage();
@@ -767,36 +821,54 @@ EOF
}
$sMessage = Dict::S('UI:Login:PasswordChanged');
}
return $sMessage;
}
protected static function Dispatch($sRequestedPortalId)
{
if ($sRequestedPortalId === null) return true; // allowed to any portal => return true
$iRet = self::Login($iOnExit);
if ($iRet == self::EXIT_CODE_OK)
$aPortalsConf = PortalDispatcherData::GetData();
$aDispatchers = array();
foreach($aPortalsConf as $sPortalId => $aConf)
{
if ($bMustBeAdmin && !UserRights::IsAdministrator())
$sHandlerClass = $aConf['handler'];
$aDispatchers[$sPortalId] = new $sHandlerClass($sPortalId);
}
if (array_key_exists($sRequestedPortalId, $aDispatchers) && $aDispatchers[$sRequestedPortalId]->IsUserAllowed())
{
return true;
}
foreach($aDispatchers as $sPortalId => $oDispatcher)
{
if ($oDispatcher->IsUserAllowed()) return $oDispatcher->GetUrl();
}
return false; // nothing matched !!
}
public static function GetAllowedPortals()
{
$aAllowedPortals = array();
$aPortalsConf = PortalDispatcherData::GetData();
$aDispatchers = array();
foreach($aPortalsConf as $sPortalId => $aConf)
{
$sHandlerClass = $aConf['handler'];
$aDispatchers[$sPortalId] = new $sHandlerClass($sPortalId);
}
foreach($aDispatchers as $sPortalId => $oDispatcher)
{
if ($oDispatcher->IsUserAllowed())
{
if ($iOnExit == self::EXIT_RETURN)
{
return self::EXIT_CODE_MUSTBEADMIN;
}
else
{
require_once(APPROOT.'/setup/setuppage.class.inc.php');
$oP = new SetupPage(Dict::S('UI:PageTitle:FatalError'));
$oP->add("<h1>".Dict::S('UI:Login:Error:AccessAdmin')."</h1>\n");
$oP->p("<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/logoff.php\">".Dict::S('UI:LogOffMenu')."</a>");
$oP->output();
exit;
}
$aAllowedPortals[] = array(
'id' => $sPortalId,
'label' => $oDispatcher->GetLabel(),
'url' => $oDispatcher->GetUrl(),
);
}
$iRet = call_user_func(array(self::$sHandlerClass, 'ChangeLocation'), $bIsAllowedToPortalUsers, $iOnExit);
}
if ($iOnExit == self::EXIT_RETURN)
{
return $iRet;
}
else
{
return $sMessage;
}
}
return $aAllowedPortals;
}
} // End of class

View File

@@ -0,0 +1,63 @@
<?php
class PortalDispatcher
{
protected $sPortalid;
protected $aData;
public function __construct($sPortalId)
{
$this->sPortalid = $sPortalId;
$this->aData = PortalDispatcherData::GetData($sPortalId);
}
public function IsUserAllowed()
{
if (array_key_exists('profile_list', $_SESSION))
{
$aProfiles = $_SESSION['profile_list'];
}
else
{
$oUser = UserRights::GetUserObject();
$oSet = $oUser->Get('profile_list');
while(($oLnkUserProfile = $oSet->Fetch()) !== null)
{
$aProfiles[] = $oLnkUserProfile->Get('profileid_friendlyname');
}
$_SESSION['profile_list'] = $aProfiles;
}
foreach($this->aData['deny'] as $sDeniedProfile)
{
// If one denied profile is present, it's enough => return false
if (in_array($sDeniedProfile, $aProfiles))
{
return false;
}
}
foreach($this->aData['allow'] as $sAllowProfile)
{
// if one required profile is missing, it's enough => return false
if (!in_array($sAllowProfile, $aProfiles))
{
return false;
}
}
return true;
}
public function GetURL()
{
return utils::GetAbsoluteUrlAppRoot().$this->aData['url'];
}
public function GetLabel()
{
return Dict::S('portal:'.$this->sPortalid);
}
public function GetRank()
{
return $this->aData['rank'];
}
}

View File

@@ -1730,7 +1730,7 @@ class Config
$this->SetDBName($sDBName);
$this->SetDBSubname($aParamValues['db_prefix']);
}
if (!is_null($sModulesDir))
{
if (isset($aParamValues['selected_modules']))
@@ -1746,6 +1746,10 @@ class Config
$oEmptyConfig = new Config('dummy_file', false); // Do NOT load any config file, just set the default values
$aAddOns = $oEmptyConfig->GetAddOns();
$aAppModules = $oEmptyConfig->GetAppModules();
if (file_exists(APPROOT.$sModulesDir.'/core/main.php'))
{
$aAppModules[] = $sModulesDir.'/core/main.php';
}
$aDataModels = $oEmptyConfig->GetDataModels();
$aWebServiceCategories = $oEmptyConfig->GetWebServiceCategories();
$aDictionaries = $oEmptyConfig->GetDictionaries();

3
core/datamodel.core.xml Normal file
View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design>
</itop_design>

View File

@@ -35,7 +35,7 @@ try
// require_once(APPROOT.'/application/user.preferences.class.inc.php');
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
LoginWebPage::DoLogin(false /* bMustBeAdmin */, true /* IsAllowedToPortalUsers */); // Check user rights and prompt if needed
LoginWebPage::DoLoginEx(null /* any portal */, false);
$oPage = new ajax_page("");
$oPage->no_cache();

View File

@@ -1224,5 +1224,7 @@ When associated with a trigger, each action is given an "order" number, specifyi
'ExcelExport:AutoDownload' => 'Start the download automatically when the export is ready',
'ExcelExport:PreparingExport' => 'Preparing the export...',
'ExcelExport:Statistics' => 'Statistics',
'portal:legacy_portal' => 'End-User Portal',
'portal:backoffice' => 'iTop Back-Office User Interface',
));
?>

View File

@@ -1065,5 +1065,7 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'ExcelExport:AutoDownload' => 'Téléchargement automatique dès que le fichier est prêt',
'ExcelExport:PreparingExport' => 'Préparation de l\'export...',
'ExcelExport:Statistics' => 'Statistiques',
'portal:legacy_portal' => 'Portail Utilisateurs',
'portal:backoffice' => 'Console iTop',
));
?>

View File

@@ -40,7 +40,7 @@ try
require_once(APPROOT.'/application/user.preferences.class.inc.php');
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
LoginWebPage::DoLogin(false /* bMustBeAdmin */, true /* IsAllowedToPortalUsers */); // Check user rights and prompt if needed
LoginWebPage::DoLoginEx(null /* any portal */, false);
$oPage = new ajax_page("");
$oPage->no_cache();

View File

@@ -465,6 +465,20 @@ class ApplicationInstaller
}
$oFactory = new ModelFactory($aDirsToScan);
$sDeltaFile = APPROOT.'core/datamodel.core.xml';
if (file_exists($sDeltaFile))
{
$oCoreModule = new MFCoreModule('core', 'Core Module', $sDeltaFile);
$oFactory->LoadModule($oCoreModule);
}
$sDeltaFile = APPROOT.'application/datamodel.application.xml';
if (file_exists($sDeltaFile))
{
$oApplicationModule = new MFCoreModule('application', 'Application Module', $sDeltaFile);
$oFactory->LoadModule($oApplicationModule);
}
$aModules = $oFactory->FindModules();
foreach($aModules as $foo => $oModule)

View File

@@ -32,12 +32,14 @@ class MFCompiler
protected $aRootClasses;
protected $aLog;
protected $sMainPHPCode; // Code that goes into core/main.php
public function __construct($oModelFactory)
{
$this->oFactory = $oModelFactory;
$this->aLog = array();
$this->sMainPHPCode = '<'.'?'."php\n";
}
protected function Log($sText)
@@ -377,7 +379,16 @@ EOF;
//
$oBrandingNode = $this->oFactory->GetNodes('branding')->item(0);
$this->CompileBranding($oBrandingNode, $sTempTargetDir, $sFinalTargetDir);
// Compile the portals
$oPortalsNode = $this->oFactory->GetNodes('/itop_design/portals')->item(0);
$this->CompilePortals($oPortalsNode, $sTempTargetDir, $sFinalTargetDir);
// Write core/main.php
SetupUtils::builddir($sTempTargetDir.'/core');
$sPHPFile = $sTempTargetDir.'/core/main.php';
file_put_contents($sPHPFile, $this->sMainPHPCode);
} // DoCompile()
/**
@@ -1929,6 +1940,60 @@ EOF;
}
}
}
protected function CompilePortals($oPortalsNode, $sTempTargetDir, $sFinalTargetDir)
{
if ($oPortalsNode)
{
// Create some static PHP data in <env-xxx>/core/main.php
$oPortals = $oPortalsNode->GetNodes('portal');
$aPortalsConfig = array();
foreach($oPortals as $oPortal)
{
$sPortalId = $oPortal->getAttribute('id');
$aPortalsConfig[$sPortalId] = array();
$aPortalsConfig[$sPortalId]['rank'] = (float)$oPortal->GetChildText('rank', 0);
$aPortalsConfig[$sPortalId]['handler'] = $oPortal->GetChildText('handler', 'PortalDispatcher');
$aPortalsConfig[$sPortalId]['url'] = $oPortal->GetChildText('url', 'portal/index.php');
$oAllow = $oPortal->GetOptionalElement('allow');
$aPortalsConfig[$sPortalId]['allow'] = array();
if ($oAllow)
{
foreach($oAllow->GetNodes('profile') as $oProfile)
{
$aPortalsConfig[$sPortalId]['allow'][] = $oProfile->getAttribute('id');
}
}
$oDeny = $oPortal->GetOptionalElement('deny');
$aPortalsConfig[$sPortalId]['deny'] = array();
if ($oDeny)
{
foreach($oDeny->GetNodes('profile') as $oProfile)
{
$aPortalsConfig[$sPortalId]['deny'][] = $oProfile->getAttribute('id');
}
}
}
uasort($aPortalsConfig, array(get_class($this), 'SortOnRank'));
$this->sMainPHPCode .= "class PortalDispatcherData\n";
$this->sMainPHPCode .= "{\n";
$this->sMainPHPCode .= "\tprotected static \$aData = ".str_replace("\n", "\n\t", var_export($aPortalsConfig, true)).";\n\n";
$this->sMainPHPCode .= "\tpublic static function GetData(\$sPortalId = null)\n";
$this->sMainPHPCode .= "\t{\n";
$this->sMainPHPCode .= "\t\tif (\$sPortalId === null) return self::\$aData;\n";
$this->sMainPHPCode .= "\t\tif (!array_key_exists(\$sPortalId, self::\$aData)) return array();\n";
$this->sMainPHPCode .= "\t\treturn self::\$aData[\$sPortalId];\n";
$this->sMainPHPCode .= "\t}\n";
$this->sMainPHPCode .= "}\n";
}
}
public static function SortOnRank($aConf1, $aConf2)
{
return ($aConf1['rank'] < $aConf2['rank']) ? -1 : 1;
}
}
?>

View File

@@ -171,6 +171,40 @@ class MFDeltaModule extends MFModule
}
}
/**
* MFDeltaModule: an optional module, made of a single file
* @package ModelFactory
*/
class MFCoreModule extends MFModule
{
public function __construct($sName, $sLabel, $sDeltaFile)
{
$this->sId = $sName;
$this->sName = $sName;
$this->sVersion = '1.0';
$this->sRootDir = '';
$this->sLabel = $sLabel;
$this->aDataModels = array($sDeltaFile);
}
public function GetRootDir()
{
return '';
}
public function GetModuleDir()
{
return '';
}
public function GetDictionaryFiles()
{
return array();
}
}
/**
* ModelFactory: the class that manages the in-memory representation of the XML MetaModel
* @package ModelFactory

View File

@@ -335,6 +335,19 @@ class RunTimeEnvironment
// Do load the required modules
//
$oFactory = new ModelFactory($aDirsToCompile);
$sDeltaFile = APPROOT.'core/datamodel.core.xml';
if (file_exists($sDeltaFile))
{
$oCoreModule = new MFCoreModule('core', 'Core Module', $sDeltaFile);
$aRet[] = $oCoreModule;
}
$sDeltaFile = APPROOT.'application/datamodel.application.xml';
if (file_exists($sDeltaFile))
{
$oApplicationModule = new MFCoreModule('application', 'Application Module', $sDeltaFile);
$aRet[] = $oApplicationModule;
}
$aModules = $oFactory->FindModules();
foreach($aModules as $foo => $oModule)
{