diff --git a/application/utils.inc.php b/application/utils.inc.php
index 5ed9f79ff..62acd4a4f 100644
--- a/application/utils.inc.php
+++ b/application/utils.inc.php
@@ -1053,6 +1053,15 @@ class utils
{
return APPROOT.'data/cache-'.MetaModel::GetEnvironment().'/';
}
+ /**
+ * Returns a path to a folder into which any module can store log
+ * @return string
+ * @since 2.7.0
+ */
+ public static function GetLogPath()
+ {
+ return APPROOT.'log/';
+ }
/**
* Merge standard menu items with plugin provided menus items
*/
diff --git a/datamodels/2.x/itop-portal-base/composer.json b/datamodels/2.x/itop-portal-base/composer.json
new file mode 100644
index 000000000..6a285560d
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/composer.json
@@ -0,0 +1,19 @@
+{
+ "require": {
+ "php": ">=5.6.0",
+ "symfony/symfony": "^3.4"
+ },
+ "config": {
+ "vendor-dir": "../../../../lib/composer-vendor"
+ },
+ "autoload": {
+ "psr-4": {
+ "Combodo\\iTop\\Portal\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Combodo\\iTop\\Portal\\Tests\\": "tests/"
+ }
+ }
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/index.php b/datamodels/2.x/itop-portal-base/index.php
index d9a1c8295..38979ec9e 100644
--- a/datamodels/2.x/itop-portal-base/index.php
+++ b/datamodels/2.x/itop-portal-base/index.php
@@ -44,8 +44,17 @@ if(!defined('PORTAL_ID'))
}
// Defining portal constants
- define('PORTAL_MODULE_ID', $sPortalId);
define('PORTAL_ID', $sPortalId);
}
-require_once APPROOT . '/env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/web/index.php';
+define('PORTAL_CACHE_PATH', utils::GetCachePath() . '/portals/' . PORTAL_ID . '/');
+
+// Constants to be used in templates and others
+define('COMBODO_CURRENT_ENVIRONMENT', utils::GetCurrentEnvironment());
+define('COMBODO_ABSOLUTE_URL', utils::GetAbsoluteUrlAppRoot());
+define('COMBODO_MODULES_ABSOLUTE_URL', utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment());
+define('COMBODO_PORTAL_BASE_ABSOLUTE_URL', utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/public/');
+define('COMBODO_PORTAL_BASE_ABSOLUTE_PATH', MODULESROOT . '/itop-portal-base/portal/public/');
+define('COMBODO_PORTAL_INSTANCE_ABSOLUTE_URL', utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/' . PORTAL_ID . '/');
+
+require_once APPROOT . '/env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/public/index.php';
diff --git a/datamodels/2.x/itop-portal-base/module.itop-portal-base.php b/datamodels/2.x/itop-portal-base/module.itop-portal-base.php
index db386574a..e00ee8240 100644
--- a/datamodels/2.x/itop-portal-base/module.itop-portal-base.php
+++ b/datamodels/2.x/itop-portal-base/module.itop-portal-base.php
@@ -13,11 +13,10 @@ SetupWebPage::AddModule(
'visible' => true,
// Components
'datamodel' => array(
- 'portal/src/controllers/abstractcontroller.class.inc.php',
- 'portal/src/controllers/brickcontroller.class.inc.php',
- 'portal/src/entities/abstractbrick.class.inc.php',
- 'portal/src/entities/portalbrick.class.inc.php',
- 'portal/src/routers/abstractrouter.class.inc.php',
+// 'portal/src/controllers/brickcontroller.class.inc.php',
+// 'portal/src/entities/abstractbrick.class.inc.php',
+// 'portal/src/entities/portalbrick.class.inc.php',
+// 'portal/src/routers/abstractrouter.class.inc.php',
),
'webservice' => array(
//'webservices.itop-portal-base.php',
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/controllers/abstractcontroller.class.inc.php b/datamodels/2.x/itop-portal-base/portal-silex/src/controllers/abstractcontroller.class.inc.php
deleted file mode 100644
index c65fc2668..000000000
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/controllers/abstractcontroller.class.inc.php
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-namespace Combodo\iTop\Portal\Controller;
-
-/**
- * Class AbstractController
- *
- * @package Combodo\iTop\Portal\Controller
- * @author Guillaume Lajarige
- * @since 2.3.0
- */
-abstract class AbstractController
-{
-
-}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/controllers/defaultcontroller.class.inc.php b/datamodels/2.x/itop-portal-base/portal-silex/src/controllers/defaultcontroller.class.inc.php
deleted file mode 100644
index 0e3eb2dab..000000000
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/controllers/defaultcontroller.class.inc.php
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-namespace Combodo\iTop\Portal\Controller;
-
-use Silex\Application;
-use Symfony\Component\HttpFoundation\Request;
-
-/**
- * Class DefaultController
- *
- * @package Combodo\iTop\Portal\Controller
- * @author Guillaume Lajarige
- * @since 2.3.0
- */
-class DefaultController
-{
- /**
- * @param \Symfony\Component\HttpFoundation\Request $oRequest
- * @param \Silex\Application $oApp
- *
- * @return \Symfony\Component\HttpFoundation\Response
- */
- public function homeAction(Request $oRequest, Application $oApp)
- {
- $aData = array();
-
- // Rendering tiles
- $aData['aTilesRendering'] = array();
- /** @var \Combodo\iTop\Portal\Brick\PortalBrick $oBrick */
- foreach($oApp['combodo.portal.instance.conf']['bricks'] as $oBrick)
- {
- // Doing it only for tile visible on home page to avoid unnecessary rendering
- if (($oBrick->GetVisibleHome() === true) && ($oBrick->GetTileControllerAction() !== null))
- {
- $aControllerActionParts = explode('::', $oBrick->GetTileControllerAction());
- if (count($aControllerActionParts) !== 2)
- {
- $oApp->abort(500, 'Tile controller action must be of form "\Namespace\ControllerClass::FunctionName" for brick "' . $oBrick->GetId() . '"');
- }
-
- $sControllerName = $aControllerActionParts[0];
- $sControllerAction = $aControllerActionParts[1];
-
- $oController = new $sControllerName($oRequest, $oApp, $oBrick->GetId());
- $aData['aTilesRendering'][$oBrick->GetId()] = $oController->$sControllerAction($oRequest, $oApp, $oBrick->GetId());
- }
- }
-
- // Home page template
- $template = $oApp['combodo.portal.instance.conf']['properties']['templates']['home'];
-
- return $oApp['twig']->render($template, $aData);
- }
-
-}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/helpers/scopevalidatorhelper.class.inc.php b/datamodels/2.x/itop-portal-base/portal-silex/src/helpers/scopevalidatorhelper.class.inc.php
deleted file mode 100644
index b0cdf9827..000000000
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/helpers/scopevalidatorhelper.class.inc.php
+++ /dev/null
@@ -1,618 +0,0 @@
-
-
-namespace Combodo\iTop\Portal\Helper;
-
-use DBSearch;
-use DBUnionSearch;
-use DOMFormatException;
-use DOMNodeList;
-use Exception;
-use MetaModel;
-use ProfilesConfig;
-use UserRights;
-use utils;
-
-/**
- * Class ScopeValidatorHelper
- *
- * Inside the portal this service is injected, get the instance using $oApp['scope_validator']
- *
- * @package Combodo\iTop\Portal\Helper
- */
-class ScopeValidatorHelper
-{
- const ENUM_MODE_READ = 'r';
- const ENUM_MODE_WRITE = 'w';
- const ENUM_TYPE_ALLOW = 'allow';
- const ENUM_TYPE_RESTRICT = 'restrict';
- const DEFAULT_GENERATED_CLASS = 'PortalScopesValues';
- const DEFAULT_IGNORE_SILOS = false;
-
- protected $sCachePath;
- protected $sFilename;
- protected $sInstancePrefix;
- protected $sGeneratedClass;
- protected $aProfilesMatrix;
-
- public static function EnumTypeValues()
- {
- return array(static::ENUM_TYPE_ALLOW, static::ENUM_TYPE_RESTRICT);
- }
-
- public function __construct($sFilename, $sCachePath = null)
- {
- $this->sFilename = $sFilename;
- $this->sCachePath = $sCachePath;
- $this->sInstancePrefix = '';
- $this->sGeneratedClass = static::DEFAULT_GENERATED_CLASS;
- $this->aProfilesMatrix = array();
- }
-
- /**
- * Returns the path where to cache the compiled scopes file
- *
- * @return string
- */
- public function GetCachePath()
- {
- return $this->sCachePath;
- }
-
- /**
- * Returns the name of the compiled scopes file
- *
- * @return string
- */
- public function GetFilename()
- {
- return $this->sFilename;
- }
-
- /**
- * Returns the instance prefix used for the generated scopes class name
- *
- * @return string
- */
- public function GetInstancePrefix()
- {
- return $this->sInstancePrefix;
- }
-
- /**
- * Returns the name of the generated scopes class
- *
- * @return string
- */
- public function GetGeneratedClass()
- {
- return $this->sGeneratedClass;
- }
-
- /**
- * Sets the scope validator instance prefix.
- *
- * This is used to create a unique scope values class in the cache directory (/data/cache-) as there can be several instance of the portal.
- *
- * @param string $sInstancePrefix
- * @return \Combodo\iTop\Portal\Helper\ScopeValidatorHelper
- */
- public function SetInstancePrefix($sInstancePrefix)
- {
- $sInstancePrefix = preg_replace('/[-_]/', ' ', $sInstancePrefix);
- $sInstancePrefix = ucwords($sInstancePrefix);
- $sInstancePrefix = str_replace(' ', '', $sInstancePrefix);
-
- $this->sInstancePrefix = $sInstancePrefix;
- $this->sGeneratedClass = $this->sInstancePrefix . static::DEFAULT_GENERATED_CLASS;
-
- return $this;
- }
-
- /**
- * Initializes the ScopeValidator by generating and caching the scopes compilation in the $this->sCachePath.$this->sFilename file.
- *
- * @param DOMNodeList $oNodes
- * @throws DOMFormatException
- * @throws Exception
- */
- public function Init(DOMNodeList $oNodes)
- {
- // Checking cache path
- if ($this->sCachePath === null)
- {
- $this->sCachePath = utils::GetCachePath();
- }
- // Building full pathname for file
- $sFilePath = $this->sCachePath . $this->sFilename;
-
- // Creating file if not existing
- // Note : This is a temporary cache system, it should soon evolve to a cache provider (fs, apc, memcache, ...)
- if (!file_exists($sFilePath))
- {
- // - Build php array from xml
- $aProfiles = array();
- // This will be used to know which classes have been set, so we can set the missing ones.
- $aProfileClasses = array();
- // Iterating over the class nodes
- foreach ($oNodes as $oClassNode)
- {
- // retrieving mandatory class id attribute
- $sClass = $oClassNode->getAttribute('id');
- if ($sClass === '')
- {
- throw new DOMFormatException('Class tag must have an id attribute.', null, null, $oClassNode);
- }
-
- // Iterating over scope nodes of the class
- $oScopesNode = $oClassNode->GetOptionalElement('scopes');
- if ($oScopesNode !== null)
- {
- foreach ($oScopesNode->GetNodes('./scope') as $oScopeNode)
- {
- // Retrieving mandatory scope id attribute
- $sScopeId = $oScopeNode->getAttribute('id');
- if ($sScopeId === '')
- {
- throw new DOMFormatException('Scope tag must have an id attribute.', null, null, $oScopeNode);
- }
-
- // Retrieving the type of query
- // Note : This has been disabled as we don't want deny rules for now
- // $oOqlViewTypeNode = $oClassNode->GetOptionalElement('oql_view_type');
- // $sOqlViewType = ($oOqlViewTypeNode !== null && ($oOqlViewTypeNode->GetText() === static::ENUM_TYPE_RESTRICT)) ? static::ENUM_TYPE_RESTRICT : static::ENUM_TYPE_ALLOW;
- $sOqlViewType = static::ENUM_TYPE_ALLOW;
- // Retrieving the view query
- $oOqlViewNode = $oScopeNode->GetUniqueElement('oql_view');
- $sOqlView = $oOqlViewNode->GetText();
- if ($sOqlView === null)
- {
- throw new DOMFormatException('Scope tag in class must have a not empty oql_view tag', null, null, $oScopeNode);
- }
- // Retrieving the edit query
- $oOqlEditNode = $oScopeNode->GetOptionalElement('oql_edit');
- $sOqlEdit = ( ($oOqlEditNode !== null) && ($oOqlEditNode->GetText() !== null) ) ? $oOqlEditNode->GetText() : null;
- // Retrieving ignore allowed org flag
- $oIgnoreSilosNode = $oScopeNode->GetOptionalElement('ignore_silos');
- $bIgnoreSilos = ( ($oIgnoreSilosNode !== null) && ($oIgnoreSilosNode->GetText() === 'true') ) ? true : static::DEFAULT_IGNORE_SILOS;
-
- // Retrieving profiles for the scope
- $oProfilesNode = $oScopeNode->GetOptionalElement('allowed_profiles');
- $aProfilesNames = array();
- // If no profile is specified, we consider that it's for ALL the profiles
- if (($oProfilesNode === null) || ($oProfilesNode->GetNodes('./allowed_profile')->length === 0))
- {
- foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
- {
- $aProfilesNames[] = $aValue['name'];
- }
- }
- else
- {
- foreach ($oProfilesNode->GetNodes('./allowed_profile') as $oProfileNode)
- {
- // Retrieving mandatory profile id attribute
- $sProfileId = $oProfileNode->getAttribute('id');
- if ($sProfileId === '')
- {
- throw new DOMFormatException('Scope tag must have an id attribute.', null, null, $oProfileNode);
- }
- $aProfilesNames[] = $sProfileId;
- }
- }
-
- //
- foreach ($aProfilesNames as $sProfileName)
- {
- // Scope profile id
- $iProfileId = $this->GetProfileIdFromProfileName($sProfileName);
-
- // Now that we have the queries infos, we are going to build the queries for that profile / class
- $sMatrixPrefix = $iProfileId . '_' . $sClass . '_';
- // - View query
- $oViewFilter = DBSearch::FromOQL($sOqlView);
- // ... We have to union the query if this profile has another scope for that class
- if (array_key_exists($sMatrixPrefix . static::ENUM_MODE_READ, $aProfiles) && array_key_exists($sOqlViewType, $aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ]))
- {
- $oExistingFilter = DBSearch::FromOQL($aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ][$sOqlViewType]);
- $aFilters = array($oExistingFilter, $oViewFilter);
- $oResFilter = new DBUnionSearch($aFilters);
-
- // Applying ignore_silos flag on result filter if necessary (As the union will remove it if it is not on all sub-queries)
- if ($aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ]['ignore_silos'] === true)
- {
- $bIgnoreSilos = true;
- }
- }
- else
- {
- $oResFilter = $oViewFilter;
- }
- $aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ] = array(
- $sOqlViewType => $oResFilter->ToOQL(),
- 'ignore_silos' => $bIgnoreSilos
- );
- // - Edit query
- if ($sOqlEdit !== null)
- {
- $oEditFilter = DBSearch::FromOQL($sOqlEdit);
- // - If the queries are the same, we don't make an intersect, we just reuse the view query
- if ($sOqlEdit === $sOqlView)
- {
- // Do not intersect, edit query is identical to view query
- }
- else
- {
- if (($oEditFilter->GetClass() === $oViewFilter->GetClass()) && $oEditFilter->IsAny())
- {
- $oEditFilter = $oViewFilter;
- // Do not intersect, edit query is identical to view query
- }
- else
- {
- // Intersect
- $oEditFilter = $oViewFilter->Intersect($oEditFilter);
- }
- }
-
- // ... We have to union the query if this profile has another scope for that class
- if (array_key_exists($sMatrixPrefix . static::ENUM_MODE_WRITE, $aProfiles) && array_key_exists($sOqlViewType, $aProfiles[$sMatrixPrefix . static::ENUM_MODE_WRITE]))
- {
- $oExistingFilter = DBSearch::FromOQL($aProfiles[$sMatrixPrefix . static::ENUM_MODE_WRITE][$sOqlViewType]);
- $aFilters = array($oExistingFilter, $oEditFilter);
- $oResFilter = new DBUnionSearch($aFilters);
- }
- else
- {
- $oResFilter = $oEditFilter;
- }
- $aProfiles[$sMatrixPrefix . static::ENUM_MODE_WRITE] = array(
- $sOqlViewType => $oResFilter->ToOQL(),
- 'ignore_silos' => $bIgnoreSilos
- );
- }
- }
- }
-
- $aProfileClasses[] = $sClass;
- }
- }
-
- // Filling the array with missing classes from MetaModel, so we can have an inheritance principle on the scope
- // For each class explicitly given in the scopes, we check if its child classes were also in the scope :
- // If not, we add them with the same OQL
- foreach ($aProfileClasses as $sProfileClass)
- {
- foreach (MetaModel::EnumChildClasses($sProfileClass) as $sChildClass)
- {
- // If the child class is not in the scope, we are going to try to add it
- if (!in_array($sChildClass, $aProfileClasses))
- {
- foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
- {
- $iProfileId = $iKey;
- foreach (array(static::ENUM_MODE_READ, static::ENUM_MODE_WRITE) as $sAction)
- {
- // If the current profile has scope for that class in that mode, we duplicate it
- if (isset($aProfiles[$iProfileId . '_' . $sProfileClass . '_' . $sAction]))
- {
- $aTmpProfile = $aProfiles[$iProfileId . '_' . $sProfileClass . '_' . $sAction];
- foreach ($aTmpProfile as $sType => $sOql)
- {
- // IF condition is just to skip the 'ignore_silos' flag
- if (in_array($sType, array(static::ENUM_TYPE_ALLOW, static::ENUM_TYPE_RESTRICT)))
- {
- $oTmpFilter = DBSearch::FromOQL($sOql);
- $oTmpFilter->ChangeClass($sChildClass);
-
- $aTmpProfile[$sType] = $oTmpFilter->ToOQL();
- }
- }
-
- $aProfiles[$iProfileId . '_' . $sChildClass . '_' . $sAction] = $aTmpProfile;
- }
- }
- }
- }
- }
- }
-
- // - Build php class
- $sPHP = $this->BuildPHPClass($aProfiles);
-
- // - Write file on disk
- // - Creating dir if necessary
- if (!is_dir($this->sCachePath))
- {
- mkdir($this->sCachePath, 0777, true);
- }
- // -- Then creating the file
- $ret = file_put_contents($sFilePath, $sPHP);
- if ($ret === false)
- {
- $iLen = strlen($sPHP);
- $fFree = @disk_free_space(dirname($sFilePath));
- $aErr = error_get_last();
- throw new Exception("Failed to write '$sFilePath'. Last error: '{$aErr['message']}', content to write: $iLen bytes, available free space on disk: $fFree.");
- }
- }
-
- if (!class_exists($this->sGeneratedClass))
- {
- require_once $this->sCachePath . $this->sFilename;
- }
- }
-
- /**
- * Returns the DBSearch for the $sProfile in $iAction for the class $sClass
- *
- * @param string $sProfile
- * @param string $sClass
- * @param integer $iAction
- *
- * @return \DBSearch
- *
- * @throws \Exception
- * @throws \CoreException
- * @throws \OQLException
- */
- public function GetScopeFilterForProfile($sProfile, $sClass, $iAction = null)
- {
- return $this->GetScopeFilterForProfiles(array($sProfile), $sClass, $iAction);
- }
-
- /**
- * Returns the DBSearch for the $aProfiles in $iAction for the class $sClass.
- * Profiles are a OR condition.
- *
- * @param array $aProfiles
- * @param string $sClass
- * @param integer $iAction
- *
- * @return \DBSearch
- *
- * @throws \Exception
- * @throws \CoreException
- * @throws \OQLException
- */
- public function GetScopeFilterForProfiles($aProfiles, $sClass, $iAction = null)
- {
- $oSearch = null;
- $aAllowSearches = array();
- $aRestrictSearches = array();
- $bIgnoreSilos = static::DEFAULT_IGNORE_SILOS;
-
- // Checking the default mode
- if ($iAction === null)
- {
- $iAction = UR_ACTION_READ;
- }
-
- // Iterating on profiles to retrieving the different OQLs parts
- foreach ($aProfiles as $sProfile)
- {
- // Retrieving matrix informtions
- $iProfileId = $this->GetProfileIdFromProfileName($sProfile);
- $sMode = ($iAction === UR_ACTION_READ) ? static::ENUM_MODE_READ : static::ENUM_MODE_WRITE;
-
- // Retrieving profile OQLs
- $sScopeValuesClass = $this->sGeneratedClass;
- $aProfileMatrix = $sScopeValuesClass::GetProfileScope($iProfileId, $sClass, $sMode);
- if ($aProfileMatrix !== null)
- {
- if (isset($aProfileMatrix['allow']) && $aProfileMatrix['allow'] !== null)
- {
- $aAllowSearches[] = DBSearch::FromOQL($aProfileMatrix['allow']);
- }
- if (isset($aProfileMatrix['restrict']) && $aProfileMatrix['restrict'] !== null)
- {
- $aRestrictSearches[] = DBSearch::FromOQL($aProfileMatrix['restrict']);
- }
- // If a profile should ignore allowed org, we set it for all its queries no matter the profile
- if (isset($aProfileMatrix['ignore_silos']) && $aProfileMatrix['ignore_silos'] === true)
- {
- $bIgnoreSilos = true;
- }
- }
- }
-
- // Building the real OQL from all the parts from the differents profiles
- for ($i = 0; $i < count($aAllowSearches); $i++)
- {
- foreach ($aRestrictSearches as $oRestrictSearch)
- {
- $aAllowSearches[$i] = $aAllowSearches[$i]->Intersect($oRestrictSearch);
- }
- }
- if (count($aAllowSearches) > 0)
- {
- $oSearch = new DBUnionSearch($aAllowSearches);
- $oSearch = $oSearch->RemoveDuplicateQueries();
- }
- if ($bIgnoreSilos === true)
- {
- $oSearch->AllowAllData();
- }
-
- return $oSearch;
- }
-
- /**
- * Add the scope query (view or edit depending on $sAction) for $sClass to the $oQuery.
- *
- * @param \DBSearch $oQuery
- * @param string $sClass
- * @param int $sAction
- *
- * @return bool true if scope exists, false if scope is null
- *
- * @throws \CoreException
- * @throws \OQLException
- */
- public function AddScopeToQuery(DBSearch &$oQuery, $sClass, $sAction = UR_ACTION_READ)
- {
- $oScopeQuery = $this->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sClass, $sAction);
- if ($oScopeQuery !== null)
- {
- $oQuery = $oQuery->Intersect($oScopeQuery);
- // - Allowing all data if necessary
- if ($oScopeQuery->IsAllDataAllowed())
- {
- $oQuery->AllowAllData();
- }
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Returns true if at least one of the $aProfiles has the ignore_silos flag set to true for the $sClass.
- *
- * @param array $aProfiles
- * @param string $sClass
- *
- * @return boolean
- *
- * @throws \Exception
- */
- public function IsAllDataAllowedForScope($aProfiles, $sClass)
- {
- $bIgnoreSilos = false;
-
- // Iterating on profiles to retrieving the different OQLs parts
- foreach ($aProfiles as $sProfile)
- {
- // Retrieving matrix informtions
- $iProfileId = $this->GetProfileIdFromProfileName($sProfile);
-
- // Retrieving profile OQLs
- $sScopeValuesClass = $this->sGeneratedClass;
- $aProfileMatrix = $sScopeValuesClass::GetProfileScope($iProfileId, $sClass, static::ENUM_MODE_READ);
- if ($aProfileMatrix !== null)
- {
- // If a profile should ignore allowed org, we set it for all its queries no matter the profile
- if (isset($aProfileMatrix['ignore_silos']) && $aProfileMatrix['ignore_silos'] === true)
- {
- $bIgnoreSilos = true;
- }
- }
- }
-
- return $bIgnoreSilos;
- }
-
- /**
- * Returns the profile id from a string being either a constant or its name.
- *
- * @param string $sProfile
- *
- * @return integer
- *
- * @throws \Exception
- */
- protected function GetProfileIdFromProfileName($sProfile)
- {
- $iProfileId = null;
-
- // We try to find the profile from its name in order to retrieve it's id
- // - If the regular UserRights addon is installed we check the profiles array
- if (class_exists('ProfilesConfig'))
- {
- if (defined($sProfile) && in_array($sProfile, ProfilesConfig::GetProfilesValues()))
- {
- $iProfileId = constant($sProfile);
- }
- else
- {
- foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
- {
- if ($aValue['name'] === $sProfile)
- {
- $iProfileId = $iKey;
- break;
- }
- }
- }
- }
- // - Else, we can't find the id from the name as we don't know the used UserRights addon. It has to be a constant
- else
- {
- throw new Exception('Scope validator : Unknown UserRights addon, scope\'s profile must be a constant');
- }
-
- // If profile was not found from its name or from a constant, we throw an exception
- if ($iProfileId === null)
- {
- throw new Exception('Scope validator : Could not find "' . $sProfile . '" in the profiles list');
- }
-
- return $iProfileId;
- }
-
- /**
- * Returns a string containing the generated PHP class for the compiled scopes
- *
- * @param array $aProfiles
- * @return string
- */
- protected function BuildPHPClass($aProfiles = array())
- {
- $sProfiles = var_export($aProfiles, true);
- $sClassName = $this->sGeneratedClass;
- $sPHP = <<
-
-namespace Combodo\iTop\Portal\Router;
-
-
-/**
- * Class DefaultRouter
- *
- * @package Combodo\iTop\Portal\Router
- * @author Guillaume Lajarige
- * @since 2.3.0
- */
-class DefaultRouter extends AbstractRouter
-{
- static $aRoutes = array(
- array('pattern' => '/',
- 'callback' => 'Combodo\\iTop\\Portal\\Controller\\DefaultController::homeAction',
- 'bind' => 'p_home'),
-// // Example route
-// array('pattern' => '/url-pattern',
-// 'hash' => 'string-to-be-append-to-the-pattern-after-a-#',
-// 'navigation_menu_attr' => array('id' => 'link_id', 'rel' => 'foo'),
-// 'callback' => 'Combodo\\iTop\\Portal\\Controller\\DefaultController::exampleAction',
-// 'bind' => 'p_example')
- );
-
-}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/routers/userprofilebrickrouter.class.inc.php b/datamodels/2.x/itop-portal-base/portal-silex/src/routers/userprofilebrickrouter.class.inc.php
deleted file mode 100644
index b809915fc..000000000
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/routers/userprofilebrickrouter.class.inc.php
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-namespace Combodo\iTop\Portal\Router;
-
-/**
- * Class UserProfileRouter
- *
- * @package Combodo\iTop\Portal\Router
- * @author Guillaume Lajarige
- * @since 2.3.0
- */
-class UserProfileRouter extends AbstractRouter
-{
- static $aRoutes = array(
- array('pattern' => '/user/{sBrickId}',
- 'callback' => 'Combodo\\iTop\\Portal\\Controller\\UserProfileBrickController::DisplayAction',
- 'bind' => 'p_user_profile_brick',
- 'values' => array(
- 'sBrickId' => null
- )
- )
- );
-
-}
diff --git a/datamodels/2.x/itop-portal-base/portal/.env b/datamodels/2.x/itop-portal-base/portal/.env
new file mode 100644
index 000000000..22257ab47
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/.env
@@ -0,0 +1,21 @@
+# In all environments, the following files are loaded if they exist,
+# the later taking precedence over the former:
+#
+# * .env contains default values for the environment variables needed by the app
+# * .env.local uncommitted file with local overrides
+# * .env.$APP_ENV committed environment-specific defaults
+# * .env.$APP_ENV.local uncommitted environment-specific overrides
+#
+# Real environment variables win over .env files.
+#
+# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
+#
+# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
+# https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration
+
+###> symfony/framework-bundle ###
+APP_ENV=dev
+APP_SECRET=40ef8b29be00df19cec62edf08f73808
+#TRUSTED_PROXIES=127.0.0.1,127.0.0.2
+#TRUSTED_HOSTS='^localhost|example\.com$'
+###< symfony/framework-bundle ###
diff --git a/datamodels/2.x/itop-portal-base/portal/.gitignore b/datamodels/2.x/itop-portal-base/portal/.gitignore
new file mode 100644
index 000000000..136901bf9
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/.gitignore
@@ -0,0 +1,9 @@
+
+###> symfony/framework-bundle ###
+/.env.local
+/.env.local.php
+/.env.*.local
+/public/bundles/
+/var/
+/vendor/
+###< symfony/framework-bundle ###
diff --git a/datamodels/2.x/itop-portal-base/portal/bin/console b/datamodels/2.x/itop-portal-base/portal/bin/console
new file mode 100644
index 000000000..19c2f6c3c
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/bin/console
@@ -0,0 +1,42 @@
+#!/usr/bin/env php
+getParameterOption(['--env', '-e'], null, true)) {
+ putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env);
+}
+
+if ($input->hasParameterOption('--no-debug', true)) {
+ putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0');
+}
+
+require dirname(__DIR__).'/config/bootstrap.php';
+
+if ($_SERVER['APP_DEBUG']) {
+ umask(0000);
+
+ if (class_exists(Debug::class)) {
+ Debug::enable();
+ }
+}
+
+$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
+$application = new Application($kernel);
+$application->run($input);
diff --git a/datamodels/2.x/itop-portal-base/portal/composer.json b/datamodels/2.x/itop-portal-base/portal/composer.json
new file mode 100644
index 000000000..cee09ae57
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/composer.json
@@ -0,0 +1,60 @@
+{
+ "type": "project",
+ "license": "proprietary",
+ "require": {
+ "php": ">=5.6.0",
+ "ext-ctype": "*",
+ "ext-iconv": "*",
+ "symfony/console": "3.4.*",
+ "symfony/dotenv": "3.4.*",
+ "symfony/flex": "^1.1",
+ "symfony/framework-bundle": "3.4.*",
+ "symfony/twig-bundle": "3.4.*",
+ "symfony/yaml": "3.4.*"
+ },
+ "config": {
+ "vendor-dir": "../../../../lib/composer-vendor",
+ "preferred-install": {
+ "*": "dist"
+ },
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "Combodo\\iTop\\Portal\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Combodo\\iTop\\Portal\\Tests\\": "tests/"
+ }
+ },
+ "replace": {
+ "paragonie/random_compat": "2.*",
+ "symfony/polyfill-ctype": "*",
+ "symfony/polyfill-iconv": "*",
+ "symfony/polyfill-php70": "*",
+ "symfony/polyfill-php56": "*"
+ },
+ "scripts": {
+ "auto-scripts": {
+ "cache:clear": "symfony-cmd",
+ "assets:install %PUBLIC_DIR%": "symfony-cmd"
+ },
+ "post-install-cmd": [
+ "@auto-scripts"
+ ],
+ "post-update-cmd": [
+ "@auto-scripts"
+ ]
+ },
+ "conflict": {
+ "symfony/symfony": "*"
+ },
+ "extra": {
+ "symfony": {
+ "allow-contrib": false,
+ "require": "3.4.*"
+ }
+ }
+}
diff --git a/datamodels/2.x/itop-portal-base/portal/composer.lock b/datamodels/2.x/itop-portal-base/portal/composer.lock
new file mode 100644
index 000000000..8240d8f47
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/composer.lock
@@ -0,0 +1,1607 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "a38a6d242ba58ae1d9a31fdd35cf960c",
+ "packages": [
+ {
+ "name": "psr/cache",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/cache.git",
+ "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
+ "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Cache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for caching libraries",
+ "keywords": [
+ "cache",
+ "psr",
+ "psr-6"
+ ],
+ "time": "2016-08-06T20:24:11+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+ "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "time": "2017-02-14T16:28:37+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
+ "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "time": "2018-11-20T15:27:04+00:00"
+ },
+ {
+ "name": "psr/simple-cache",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/simple-cache.git",
+ "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+ "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\SimpleCache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interfaces for simple caching",
+ "keywords": [
+ "cache",
+ "caching",
+ "psr",
+ "psr-16",
+ "simple-cache"
+ ],
+ "time": "2017-10-23T01:57:42+00:00"
+ },
+ {
+ "name": "symfony/cache",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/cache.git",
+ "reference": "380b8395b43f60e7d26a32f84f80c0a7ba93e7c5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/cache/zipball/380b8395b43f60e7d26a32f84f80c0a7ba93e7c5",
+ "reference": "380b8395b43f60e7d26a32f84f80c0a7ba93e7c5",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "psr/cache": "~1.0",
+ "psr/log": "~1.0",
+ "psr/simple-cache": "^1.0",
+ "symfony/polyfill-apcu": "~1.1"
+ },
+ "conflict": {
+ "symfony/var-dumper": "<3.3"
+ },
+ "provide": {
+ "psr/cache-implementation": "1.0",
+ "psr/simple-cache-implementation": "1.0"
+ },
+ "require-dev": {
+ "cache/integration-tests": "dev-master",
+ "doctrine/cache": "~1.6",
+ "doctrine/dbal": "~2.4",
+ "predis/predis": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Cache\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Cache component with PSR-6, PSR-16, and tags",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "caching",
+ "psr6"
+ ],
+ "time": "2019-04-16T09:03:16+00:00"
+ },
+ {
+ "name": "symfony/class-loader",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/class-loader.git",
+ "reference": "4459eef5298dedfb69f771186a580062b8516497"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/class-loader/zipball/4459eef5298dedfb69f771186a580062b8516497",
+ "reference": "4459eef5298dedfb69f771186a580062b8516497",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8"
+ },
+ "require-dev": {
+ "symfony/finder": "~2.8|~3.0|~4.0",
+ "symfony/polyfill-apcu": "~1.1"
+ },
+ "suggest": {
+ "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\ClassLoader\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony ClassLoader Component",
+ "homepage": "https://symfony.com",
+ "time": "2019-01-16T09:39:14+00:00"
+ },
+ {
+ "name": "symfony/config",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/config.git",
+ "reference": "177a276c01575253c95cefe0866e3d1b57637fe0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/config/zipball/177a276c01575253c95cefe0866e3d1b57637fe0",
+ "reference": "177a276c01575253c95cefe0866e3d1b57637fe0",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "symfony/filesystem": "~2.8|~3.0|~4.0",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<3.3",
+ "symfony/finder": "<3.3"
+ },
+ "require-dev": {
+ "symfony/dependency-injection": "~3.3|~4.0",
+ "symfony/event-dispatcher": "~3.3|~4.0",
+ "symfony/finder": "~3.3|~4.0",
+ "symfony/yaml": "~3.0|~4.0"
+ },
+ "suggest": {
+ "symfony/yaml": "To use the yaml reference dumper"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Config\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Config Component",
+ "homepage": "https://symfony.com",
+ "time": "2019-02-23T15:06:07+00:00"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "15a9104356436cb26e08adab97706654799d31d8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/15a9104356436cb26e08adab97706654799d31d8",
+ "reference": "15a9104356436cb26e08adab97706654799d31d8",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "symfony/debug": "~2.8|~3.0|~4.0",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<3.4",
+ "symfony/process": "<3.3"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "~3.3|~4.0",
+ "symfony/dependency-injection": "~3.4|~4.0",
+ "symfony/event-dispatcher": "~2.8|~3.0|~4.0",
+ "symfony/lock": "~3.4|~4.0",
+ "symfony/process": "~3.3|~4.0"
+ },
+ "suggest": {
+ "psr/log": "For using the console logger",
+ "symfony/event-dispatcher": "",
+ "symfony/lock": "",
+ "symfony/process": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Console Component",
+ "homepage": "https://symfony.com",
+ "time": "2019-04-08T09:29:13+00:00"
+ },
+ {
+ "name": "symfony/debug",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/debug.git",
+ "reference": "681afbb26488903c5ac15e63734f1d8ac430c9b9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/debug/zipball/681afbb26488903c5ac15e63734f1d8ac430c9b9",
+ "reference": "681afbb26488903c5ac15e63734f1d8ac430c9b9",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "psr/log": "~1.0"
+ },
+ "conflict": {
+ "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
+ },
+ "require-dev": {
+ "symfony/http-kernel": "~2.8|~3.0|~4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Debug\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Debug Component",
+ "homepage": "https://symfony.com",
+ "time": "2019-04-11T09:48:14+00:00"
+ },
+ {
+ "name": "symfony/dependency-injection",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/dependency-injection.git",
+ "reference": "be0feb3fa202aedfd8d1956f2dafd563fb13acbf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/be0feb3fa202aedfd8d1956f2dafd563fb13acbf",
+ "reference": "be0feb3fa202aedfd8d1956f2dafd563fb13acbf",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "psr/container": "^1.0"
+ },
+ "conflict": {
+ "symfony/config": "<3.3.7",
+ "symfony/finder": "<3.3",
+ "symfony/proxy-manager-bridge": "<3.4",
+ "symfony/yaml": "<3.4"
+ },
+ "provide": {
+ "psr/container-implementation": "1.0"
+ },
+ "require-dev": {
+ "symfony/config": "~3.3|~4.0",
+ "symfony/expression-language": "~2.8|~3.0|~4.0",
+ "symfony/yaml": "~3.4|~4.0"
+ },
+ "suggest": {
+ "symfony/config": "",
+ "symfony/expression-language": "For using expressions in service container configuration",
+ "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required",
+ "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them",
+ "symfony/yaml": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\DependencyInjection\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony DependencyInjection Component",
+ "homepage": "https://symfony.com",
+ "time": "2019-04-20T15:32:49+00:00"
+ },
+ {
+ "name": "symfony/dotenv",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/dotenv.git",
+ "reference": "7b33c7b6f497898a173e4b9d6a7698cd789d54ce"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/dotenv/zipball/7b33c7b6f497898a173e4b9d6a7698cd789d54ce",
+ "reference": "7b33c7b6f497898a173e4b9d6a7698cd789d54ce",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8"
+ },
+ "require-dev": {
+ "symfony/process": "~3.2|~4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Dotenv\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Registers environment variables from a .env file",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "dotenv",
+ "env",
+ "environment"
+ ],
+ "time": "2019-04-01T07:08:40+00:00"
+ },
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher.git",
+ "reference": "a088aafcefb4eef2520a290ed82e4374092a6dff"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a088aafcefb4eef2520a290ed82e4374092a6dff",
+ "reference": "a088aafcefb4eef2520a290ed82e4374092a6dff",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<3.3"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "~2.8|~3.0|~4.0",
+ "symfony/dependency-injection": "~3.3|~4.0",
+ "symfony/expression-language": "~2.8|~3.0|~4.0",
+ "symfony/stopwatch": "~2.8|~3.0|~4.0"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\EventDispatcher\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony EventDispatcher Component",
+ "homepage": "https://symfony.com",
+ "time": "2019-04-02T08:51:52+00:00"
+ },
+ {
+ "name": "symfony/filesystem",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "acf99758b1df8e9295e6b85aa69f294565c9fedb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/acf99758b1df8e9295e6b85aa69f294565c9fedb",
+ "reference": "acf99758b1df8e9295e6b85aa69f294565c9fedb",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Filesystem Component",
+ "homepage": "https://symfony.com",
+ "time": "2019-02-04T21:34:32+00:00"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "61af5ce0b34b942d414fe8f1b11950d0e9a90e98"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/61af5ce0b34b942d414fe8f1b11950d0e9a90e98",
+ "reference": "61af5ce0b34b942d414fe8f1b11950d0e9a90e98",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Finder Component",
+ "homepage": "https://symfony.com",
+ "time": "2019-04-02T19:54:57+00:00"
+ },
+ {
+ "name": "symfony/flex",
+ "version": "v1.2.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/flex.git",
+ "reference": "27909122a3da4676c3dc5dc34c8f82323c610d69"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/flex/zipball/27909122a3da4676c3dc5dc34c8f82323c610d69",
+ "reference": "27909122a3da4676c3dc5dc34c8f82323c610d69",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0",
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "composer/composer": "^1.0.2",
+ "symfony/dotenv": "^3.4|^4.0",
+ "symfony/phpunit-bridge": "^3.4.19|^4.1.8",
+ "symfony/process": "^2.7|^3.0|^4.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2-dev"
+ },
+ "class": "Symfony\\Flex\\Flex"
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Flex\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien.potencier@gmail.com"
+ }
+ ],
+ "description": "Composer plugin for Symfony",
+ "time": "2019-05-07T08:10:46+00:00"
+ },
+ {
+ "name": "symfony/framework-bundle",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/framework-bundle.git",
+ "reference": "bbe9bb6028542fee549ff577617af33c2ad85a85"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/bbe9bb6028542fee549ff577617af33c2ad85a85",
+ "reference": "bbe9bb6028542fee549ff577617af33c2ad85a85",
+ "shasum": ""
+ },
+ "require": {
+ "ext-xml": "*",
+ "php": "^5.5.9|>=7.0.8",
+ "symfony/cache": "~3.4|~4.0",
+ "symfony/class-loader": "~3.2",
+ "symfony/config": "~3.4|~4.0",
+ "symfony/dependency-injection": "^3.4.24|^4.2.5",
+ "symfony/event-dispatcher": "~3.4|~4.0",
+ "symfony/filesystem": "~2.8|~3.0|~4.0",
+ "symfony/finder": "~2.8|~3.0|~4.0",
+ "symfony/http-foundation": "^3.3.11|~4.0",
+ "symfony/http-kernel": "~3.4|~4.0",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/routing": "^3.4.5|^4.0.5"
+ },
+ "conflict": {
+ "phpdocumentor/reflection-docblock": "<3.0",
+ "phpdocumentor/type-resolver": "<0.2.1",
+ "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
+ "symfony/asset": "<3.3",
+ "symfony/console": "<3.4",
+ "symfony/form": "<3.4",
+ "symfony/property-info": "<3.3",
+ "symfony/serializer": "<3.3",
+ "symfony/stopwatch": "<3.4",
+ "symfony/translation": "<3.4",
+ "symfony/validator": "<3.4",
+ "symfony/workflow": "<3.3"
+ },
+ "require-dev": {
+ "doctrine/annotations": "~1.0",
+ "doctrine/cache": "~1.0",
+ "fig/link-util": "^1.0",
+ "phpdocumentor/reflection-docblock": "^3.0|^4.0",
+ "symfony/asset": "~3.3|~4.0",
+ "symfony/browser-kit": "~2.8|~3.0|~4.0",
+ "symfony/console": "~3.4|~4.0",
+ "symfony/css-selector": "~2.8|~3.0|~4.0",
+ "symfony/dom-crawler": "~2.8|~3.0|~4.0",
+ "symfony/expression-language": "~2.8|~3.0|~4.0",
+ "symfony/form": "^3.4.22|~4.1.11|^4.2.3",
+ "symfony/lock": "~3.4|~4.0",
+ "symfony/polyfill-intl-icu": "~1.0",
+ "symfony/process": "~2.8|~3.0|~4.0",
+ "symfony/property-info": "~3.3|~4.0",
+ "symfony/security-core": "~3.2|~4.0",
+ "symfony/security-csrf": "^2.8.31|^3.3.13|~4.0",
+ "symfony/serializer": "~3.3|~4.0",
+ "symfony/stopwatch": "~3.4|~4.0",
+ "symfony/templating": "~2.8|~3.0|~4.0",
+ "symfony/translation": "~3.4|~4.0",
+ "symfony/validator": "~3.4|~4.0",
+ "symfony/var-dumper": "~3.3|~4.0",
+ "symfony/web-link": "~3.3|~4.0",
+ "symfony/workflow": "~3.3|~4.0",
+ "symfony/yaml": "~3.2|~4.0",
+ "twig/twig": "~1.34|~2.4"
+ },
+ "suggest": {
+ "ext-apcu": "For best performance of the system caches",
+ "symfony/console": "For using the console commands",
+ "symfony/form": "For using forms",
+ "symfony/property-info": "For using the property_info service",
+ "symfony/serializer": "For using the serializer service",
+ "symfony/validator": "For using validation",
+ "symfony/web-link": "For using web links, features such as preloading, prefetching or prerendering",
+ "symfony/yaml": "For using the debug:config and lint:yaml commands"
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bundle\\FrameworkBundle\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony FrameworkBundle",
+ "homepage": "https://symfony.com",
+ "time": "2019-05-01T08:11:03+00:00"
+ },
+ {
+ "name": "symfony/http-foundation",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-foundation.git",
+ "reference": "fa02215233be8de1c2b44617088192f9e8db3512"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/fa02215233be8de1c2b44617088192f9e8db3512",
+ "reference": "fa02215233be8de1c2b44617088192f9e8db3512",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "symfony/polyfill-mbstring": "~1.1",
+ "symfony/polyfill-php70": "~1.6"
+ },
+ "require-dev": {
+ "symfony/expression-language": "~2.8|~3.0|~4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpFoundation\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony HttpFoundation Component",
+ "homepage": "https://symfony.com",
+ "time": "2019-05-01T08:04:33+00:00"
+ },
+ {
+ "name": "symfony/http-kernel",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-kernel.git",
+ "reference": "586046f5adc6a08eaebbe4519ef18ad52f54e453"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/586046f5adc6a08eaebbe4519ef18ad52f54e453",
+ "reference": "586046f5adc6a08eaebbe4519ef18ad52f54e453",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "psr/log": "~1.0",
+ "symfony/debug": "^3.3.3|~4.0",
+ "symfony/event-dispatcher": "~2.8|~3.0|~4.0",
+ "symfony/http-foundation": "~3.4.12|~4.0.12|^4.1.1",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "conflict": {
+ "symfony/config": "<2.8",
+ "symfony/dependency-injection": "<3.4.10|<4.0.10,>=4",
+ "symfony/var-dumper": "<3.3",
+ "twig/twig": "<1.34|<2.4,>=2"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0"
+ },
+ "require-dev": {
+ "psr/cache": "~1.0",
+ "symfony/browser-kit": "~2.8|~3.0|~4.0",
+ "symfony/class-loader": "~2.8|~3.0",
+ "symfony/config": "~2.8|~3.0|~4.0",
+ "symfony/console": "~2.8|~3.0|~4.0",
+ "symfony/css-selector": "~2.8|~3.0|~4.0",
+ "symfony/dependency-injection": "^3.4.10|^4.0.10",
+ "symfony/dom-crawler": "~2.8|~3.0|~4.0",
+ "symfony/expression-language": "~2.8|~3.0|~4.0",
+ "symfony/finder": "~2.8|~3.0|~4.0",
+ "symfony/process": "~2.8|~3.0|~4.0",
+ "symfony/routing": "~3.4|~4.0",
+ "symfony/stopwatch": "~2.8|~3.0|~4.0",
+ "symfony/templating": "~2.8|~3.0|~4.0",
+ "symfony/translation": "~2.8|~3.0|~4.0",
+ "symfony/var-dumper": "~3.3|~4.0"
+ },
+ "suggest": {
+ "symfony/browser-kit": "",
+ "symfony/config": "",
+ "symfony/console": "",
+ "symfony/dependency-injection": "",
+ "symfony/finder": "",
+ "symfony/var-dumper": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpKernel\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony HttpKernel Component",
+ "homepage": "https://symfony.com",
+ "time": "2019-05-01T13:03:24+00:00"
+ },
+ {
+ "name": "symfony/polyfill-apcu",
+ "version": "v1.11.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-apcu.git",
+ "reference": "a502face1da6a53289480166f24de2c3c68e5c3c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/a502face1da6a53289480166f24de2c3c68e5c3c",
+ "reference": "a502face1da6a53289480166f24de2c3c68e5c3c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.11-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Apcu\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "apcu",
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2019-02-06T07:57:58+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.11.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "fe5e94c604826c35a32fa832f35bd036b6799609"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609",
+ "reference": "fe5e94c604826c35a32fa832f35bd036b6799609",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.11-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2019-02-06T07:57:58+00:00"
+ },
+ {
+ "name": "symfony/routing",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/routing.git",
+ "reference": "ff11aac46d6cb8a65f2855687bb9a1ac9d860eec"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/ff11aac46d6cb8a65f2855687bb9a1ac9d860eec",
+ "reference": "ff11aac46d6cb8a65f2855687bb9a1ac9d860eec",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8"
+ },
+ "conflict": {
+ "symfony/config": "<3.3.1",
+ "symfony/dependency-injection": "<3.3",
+ "symfony/yaml": "<3.4"
+ },
+ "require-dev": {
+ "doctrine/annotations": "~1.0",
+ "psr/log": "~1.0",
+ "symfony/config": "^3.3.1|~4.0",
+ "symfony/dependency-injection": "~3.3|~4.0",
+ "symfony/expression-language": "~2.8|~3.0|~4.0",
+ "symfony/http-foundation": "~2.8|~3.0|~4.0",
+ "symfony/yaml": "~3.4|~4.0"
+ },
+ "suggest": {
+ "doctrine/annotations": "For using the annotation loader",
+ "symfony/config": "For using the all-in-one router or any loader",
+ "symfony/expression-language": "For using expression matching",
+ "symfony/http-foundation": "For using a Symfony Request object",
+ "symfony/yaml": "For using the YAML loader"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Routing\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Routing Component",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "router",
+ "routing",
+ "uri",
+ "url"
+ ],
+ "time": "2019-03-29T21:58:42+00:00"
+ },
+ {
+ "name": "symfony/twig-bridge",
+ "version": "v3.4.28",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/twig-bridge.git",
+ "reference": "9fc026c05e927fe59cf89f67b9f9926e44301eea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/9fc026c05e927fe59cf89f67b9f9926e44301eea",
+ "reference": "9fc026c05e927fe59cf89f67b9f9926e44301eea",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "twig/twig": "^1.40|^2.9"
+ },
+ "conflict": {
+ "symfony/console": "<3.4",
+ "symfony/form": "<3.4.13|>=4.0,<4.0.13|>=4.1,<4.1.2"
+ },
+ "require-dev": {
+ "symfony/asset": "~2.8|~3.0|~4.0",
+ "symfony/console": "~3.4|~4.0",
+ "symfony/dependency-injection": "~2.8|~3.0|~4.0",
+ "symfony/expression-language": "~2.8|~3.0|~4.0",
+ "symfony/finder": "~2.8|~3.0|~4.0",
+ "symfony/form": "^3.4.23|^4.2.4",
+ "symfony/http-foundation": "^3.3.11|~4.0",
+ "symfony/http-kernel": "~3.2|~4.0",
+ "symfony/polyfill-intl-icu": "~1.0",
+ "symfony/routing": "~2.8|~3.0|~4.0",
+ "symfony/security": "^2.8.31|^3.3.13|~4.0",
+ "symfony/security-acl": "~2.8|~3.0",
+ "symfony/stopwatch": "~2.8|~3.0|~4.0",
+ "symfony/templating": "~2.8|~3.0|~4.0",
+ "symfony/translation": "~2.8|~3.0|~4.0",
+ "symfony/var-dumper": "~2.8.10|~3.1.4|~3.2|~4.0",
+ "symfony/web-link": "~3.3|~4.0",
+ "symfony/workflow": "~3.3|~4.0",
+ "symfony/yaml": "~2.8|~3.0|~4.0"
+ },
+ "suggest": {
+ "symfony/asset": "For using the AssetExtension",
+ "symfony/expression-language": "For using the ExpressionExtension",
+ "symfony/finder": "",
+ "symfony/form": "For using the FormExtension",
+ "symfony/http-kernel": "For using the HttpKernelExtension",
+ "symfony/routing": "For using the RoutingExtension",
+ "symfony/security": "For using the SecurityExtension",
+ "symfony/stopwatch": "For using the StopwatchExtension",
+ "symfony/templating": "For using the TwigEngine",
+ "symfony/translation": "For using the TranslationExtension",
+ "symfony/var-dumper": "For using the DumpExtension",
+ "symfony/web-link": "For using the WebLinkExtension",
+ "symfony/yaml": "For using the YamlExtension"
+ },
+ "type": "symfony-bridge",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bridge\\Twig\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Twig Bridge",
+ "homepage": "https://symfony.com",
+ "time": "2019-04-30T12:26:26+00:00"
+ },
+ {
+ "name": "symfony/twig-bundle",
+ "version": "v3.4.28",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/twig-bundle.git",
+ "reference": "9f3f12943d37153a671bbab71d58812825f41e4c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/9f3f12943d37153a671bbab71d58812825f41e4c",
+ "reference": "9f3f12943d37153a671bbab71d58812825f41e4c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "symfony/config": "~3.2|~4.0",
+ "symfony/http-foundation": "~2.8|~3.0|~4.0",
+ "symfony/http-kernel": "^3.3|~4.0",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/twig-bridge": "^3.4.3|^4.0.3",
+ "twig/twig": "~1.40|~2.9"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<3.4",
+ "symfony/event-dispatcher": "<3.3.1"
+ },
+ "require-dev": {
+ "doctrine/annotations": "~1.0",
+ "doctrine/cache": "~1.0",
+ "symfony/asset": "~2.8|~3.0|~4.0",
+ "symfony/dependency-injection": "~3.4.24|^4.2.5",
+ "symfony/expression-language": "~2.8|~3.0|~4.0",
+ "symfony/finder": "~2.8|~3.0|~4.0",
+ "symfony/form": "~2.8|~3.0|~4.0",
+ "symfony/framework-bundle": "^3.3.11|~4.0",
+ "symfony/routing": "~2.8|~3.0|~4.0",
+ "symfony/stopwatch": "~2.8|~3.0|~4.0",
+ "symfony/templating": "~2.8|~3.0|~4.0",
+ "symfony/web-link": "~3.3|~4.0",
+ "symfony/yaml": "~2.8|~3.0|~4.0"
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bundle\\TwigBundle\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony TwigBundle",
+ "homepage": "https://symfony.com",
+ "time": "2019-04-27T18:55:44+00:00"
+ },
+ {
+ "name": "symfony/yaml",
+ "version": "v3.4.27",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/yaml.git",
+ "reference": "212a27b731e5bfb735679d1ffaac82bd6a1dc996"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/212a27b731e5bfb735679d1ffaac82bd6a1dc996",
+ "reference": "212a27b731e5bfb735679d1ffaac82bd6a1dc996",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "conflict": {
+ "symfony/console": "<3.4"
+ },
+ "require-dev": {
+ "symfony/console": "~3.4|~4.0"
+ },
+ "suggest": {
+ "symfony/console": "For validating YAML files using the lint command"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Yaml\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Yaml Component",
+ "homepage": "https://symfony.com",
+ "time": "2019-03-25T07:48:46+00:00"
+ },
+ {
+ "name": "twig/twig",
+ "version": "v2.11.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/twigphp/Twig.git",
+ "reference": "699ed2342557c88789a15402de5eb834dedd6792"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/699ed2342557c88789a15402de5eb834dedd6792",
+ "reference": "699ed2342557c88789a15402de5eb834dedd6792",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "symfony/polyfill-ctype": "^1.8",
+ "symfony/polyfill-mbstring": "^1.3"
+ },
+ "require-dev": {
+ "psr/container": "^1.0",
+ "symfony/debug": "^2.7",
+ "symfony/phpunit-bridge": "^3.4.19|^4.1.8|^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.11-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Twig_": "lib/"
+ },
+ "psr-4": {
+ "Twig\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com",
+ "homepage": "http://fabien.potencier.org",
+ "role": "Lead Developer"
+ },
+ {
+ "name": "Armin Ronacher",
+ "email": "armin.ronacher@active-4.com",
+ "role": "Project Founder"
+ },
+ {
+ "name": "Twig Team",
+ "homepage": "https://twig.symfony.com/contributors",
+ "role": "Contributors"
+ }
+ ],
+ "description": "Twig, the flexible, fast, and secure template language for PHP",
+ "homepage": "https://twig.symfony.com",
+ "keywords": [
+ "templating"
+ ],
+ "time": "2019-06-18T15:37:11+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=5.6.0",
+ "ext-ctype": "*",
+ "ext-iconv": "*"
+ },
+ "platform-dev": []
+}
diff --git a/datamodels/2.x/itop-portal-base/portal/config/bootstrap.php b/datamodels/2.x/itop-portal-base/portal/config/bootstrap.php
new file mode 100644
index 000000000..b620ffa29
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/bootstrap.php
@@ -0,0 +1,51 @@
+=1.2)
+if (is_array($env = @include dirname(__DIR__).'/.env.local.php')) {
+ $_ENV += $env;
+} elseif (!class_exists(Dotenv::class)) {
+ throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.');
+} else {
+ $path = dirname(__DIR__).'/.env';
+ $dotenv = new Dotenv(false);
+
+ // load all the .env files
+ if (method_exists($dotenv, 'loadEnv')) {
+ $dotenv->loadEnv($path);
+ } else {
+ // fallback code in case your Dotenv component is not 4.2 or higher (when loadEnv() was added)
+
+ if (file_exists($path) || !file_exists($p = "$path.dist")) {
+ $dotenv->load($path);
+ } else {
+ $dotenv->load($p);
+ }
+
+ if (null === $env = (isset($_SERVER['APP_ENV']) ? $_SERVER['APP_ENV'] : (isset($_ENV['APP_ENV']) ? $_ENV['APP_ENV'] : null))) {
+ $dotenv->populate(array('APP_ENV' => $env = 'dev'));
+ }
+
+ if ('test' !== $env && file_exists($p = "$path.local")) {
+ $dotenv->load($p);
+ $env = isset($_SERVER['APP_ENV']) ? $_SERVER['APP_ENV'] : (isset($_ENV['APP_ENV']) ? $_ENV['APP_ENV'] : $env);
+ }
+
+ if (file_exists($p = "$path.$env")) {
+ $dotenv->load($p);
+ }
+
+ if (file_exists($p = "$path.$env.local")) {
+ $dotenv->load($p);
+ }
+ }
+}
+
+$_SERVER += $_ENV;
+$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = (isset($_SERVER['APP_ENV']) ? $_SERVER['APP_ENV'] : (isset($_ENV['APP_ENV']) ? $_ENV['APP_ENV'] : null)) ?: 'dev';
+$_SERVER['APP_DEBUG'] = (isset($_SERVER['APP_DEBUG']) ? $_SERVER['APP_DEBUG'] : (isset($_ENV['APP_DEBUG']) ? $_ENV['APP_DEBUG'] : 'prod')) !== $_SERVER['APP_ENV'];
+$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0';
diff --git a/datamodels/2.x/itop-portal-base/portal/config/bundles.php b/datamodels/2.x/itop-portal-base/portal/config/bundles.php
new file mode 100644
index 000000000..bdd37acde
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/bundles.php
@@ -0,0 +1,6 @@
+ ['all' => true],
+ Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
+];
diff --git a/datamodels/2.x/itop-portal-base/portal/config/legacy_silex_compat_layer.php b/datamodels/2.x/itop-portal-base/portal/config/legacy_silex_compat_layer.php
new file mode 100644
index 000000000..6ba1a194d
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/legacy_silex_compat_layer.php
@@ -0,0 +1,104 @@
+process($container);
+
+//create %combodo.portal.instance.conf%
+$formsCompat = new Forms($moduleDesign);
+$formsCompat->process($container);
+
+//append into %combodo.portal.instance.conf%
+$listesCompat = new Lists($moduleDesign);
+$listesCompat->process($container);
+
+// - Generating CSS files
+$aImportPaths = array(COMBODO_PORTAL_BASE_ABSOLUTE_PATH.'css/');
+$aPortalConf = $container->getParameter('combodo.portal.instance.conf');
+foreach ($aPortalConf['properties']['themes'] as $key => $value)
+{
+ if (!is_array($value))
+ {
+ $aPortalConf['properties']['themes'][$key] = COMBODO_ABSOLUTE_URL.utils::GetCSSFromSASS('env-'.utils::GetCurrentEnvironment().'/'.$value, $aImportPaths);
+ }
+ else
+ {
+ $aValues = array();
+ foreach ($value as $sSubvalue)
+ {
+ $aValues[] = COMBODO_ABSOLUTE_URL.utils::GetCSSFromSASS('env-'.utils::GetCurrentEnvironment().'/'.$sSubvalue, $aImportPaths);
+ }
+ $aPortalConf['properties']['themes'][$key] = $aValues;
+ }
+}
+$container->setParameter('combodo.portal.instance.conf', $aPortalConf);
+
+//TODO: The following needs to be refactored
+//session messages
+$aAllMessages = array();
+if ((array_key_exists('obj_messages', $_SESSION)) && (!empty($_SESSION['obj_messages'])))
+{
+ foreach ($_SESSION['obj_messages'] as $sMessageKey => $aMessageObjectData)
+ {
+ $aObjectMessages = array();
+ $aRanks = array();
+ foreach ($aMessageObjectData as $sMessageId => $aMessageData)
+ {
+ $sMsgClass = 'alert alert-';
+ switch ($aMessageData['severity'])
+ {
+ case 'info':
+ $sMsgClass .= 'info';
+ break;
+ case 'error':
+ $sMsgClass .= 'danger';
+ break;
+ case 'ok':
+ default:
+ $sMsgClass .= 'success';
+ break;
+ }
+ $aObjectMessages[] = array('cssClass' => $sMsgClass, 'message' => $aMessageData['message']);
+ $aRanks[] = $aMessageData['rank'];
+ }
+ unset($_SESSION['obj_messages'][$sMessageKey]);
+ array_multisort($aRanks, $aObjectMessages);
+ foreach ($aObjectMessages as $aObjectMessage)
+ {
+ $aAllMessages[] = $aObjectMessage;
+ }
+ }
+}
+$container->setParameter('combodo.current_user.session_messages', $aAllMessages);
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/config/packages/cache.yaml b/datamodels/2.x/itop-portal-base/portal/config/packages/cache.yaml
new file mode 100644
index 000000000..93e620efa
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/packages/cache.yaml
@@ -0,0 +1,19 @@
+framework:
+ cache:
+ # Put the unique name of your app here: the prefix seed
+ # is used to compute stable namespaces for cache keys.
+ #prefix_seed: your_vendor_name/app_name
+
+ # The app cache caches to the filesystem by default.
+ # Other options include:
+
+ # Redis
+ #app: cache.adapter.redis
+ #default_redis_provider: redis://localhost
+
+ # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
+ #app: cache.adapter.apcu
+
+ # Namespaced pools use the above "app" backend by default
+ #pools:
+ #my.dedicated.cache: ~
diff --git a/datamodels/2.x/itop-portal-base/portal/config/packages/dev/routing.yaml b/datamodels/2.x/itop-portal-base/portal/config/packages/dev/routing.yaml
new file mode 100644
index 000000000..4116679a2
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/packages/dev/routing.yaml
@@ -0,0 +1,3 @@
+framework:
+ router:
+ strict_requirements: true
diff --git a/datamodels/2.x/itop-portal-base/portal/config/packages/framework.yaml b/datamodels/2.x/itop-portal-base/portal/config/packages/framework.yaml
new file mode 100644
index 000000000..60f24ae25
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/packages/framework.yaml
@@ -0,0 +1,15 @@
+framework:
+ secret: '%env(APP_SECRET)%'
+ #default_locale: en
+ #csrf_protection: true
+ #http_method_override: true
+
+ # Enables session support. Note that the session will ONLY be started if you read or write from it.
+ # Remove or comment this section to explicitly disable session support.
+ session:
+ handler_id: ~
+
+ #esi: true
+ #fragments: true
+ php_errors:
+ log: true
diff --git a/datamodels/2.x/itop-portal-base/portal/config/packages/routing.yaml b/datamodels/2.x/itop-portal-base/portal/config/packages/routing.yaml
new file mode 100644
index 000000000..368bc7f49
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/packages/routing.yaml
@@ -0,0 +1,3 @@
+framework:
+ router:
+ strict_requirements: ~
diff --git a/datamodels/2.x/itop-portal-base/portal/config/packages/test/framework.yaml b/datamodels/2.x/itop-portal-base/portal/config/packages/test/framework.yaml
new file mode 100644
index 000000000..d051c8400
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/packages/test/framework.yaml
@@ -0,0 +1,4 @@
+framework:
+ test: true
+ session:
+ storage_id: session.storage.mock_file
diff --git a/datamodels/2.x/itop-portal-base/portal/config/packages/test/routing.yaml b/datamodels/2.x/itop-portal-base/portal/config/packages/test/routing.yaml
new file mode 100644
index 000000000..4116679a2
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/packages/test/routing.yaml
@@ -0,0 +1,3 @@
+framework:
+ router:
+ strict_requirements: true
diff --git a/datamodels/2.x/itop-portal-base/portal/config/packages/twig.yaml b/datamodels/2.x/itop-portal-base/portal/config/packages/twig.yaml
new file mode 100644
index 000000000..c5d5a94c3
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/packages/twig.yaml
@@ -0,0 +1,4 @@
+twig:
+ default_path: '%combodo.modules.absolute_path%'
+ debug: '%kernel.debug%'
+ strict_variables: '%kernel.debug%'
diff --git a/datamodels/2.x/itop-portal-base/portal/config/routes.yaml b/datamodels/2.x/itop-portal-base/portal/config/routes.yaml
new file mode 100644
index 000000000..08e96dbc5
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/routes.yaml
@@ -0,0 +1,40 @@
+# Copyright (C) 2010-2018 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
+
+# This file is the entry point to configure your own HTTP routes.
+# Files in the routes/ subdirectory configure the routes for your dependencies.
+
+defaults:
+ resource: 'routes/default.yaml'
+
+#browse_brick:
+# resource: 'routes/browse_brick.yaml'
+#
+#create_brick:
+# resource: 'routes/create_brick.yaml'
+#
+#manage_brick:
+# resource: 'routes/manage_brick.yaml'
+
+user_profile_brick:
+ resource: 'routes/user_profile_brick.yaml'
+#
+#object_router:
+# resource: 'routes/object_router.php'
+
+extensions_extra_routes:
+ resource: 'routes/extensions_extra_routes.php'
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/config/routes/default.yaml b/datamodels/2.x/itop-portal-base/portal/config/routes/default.yaml
new file mode 100644
index 000000000..7b9463d2f
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/routes/default.yaml
@@ -0,0 +1,27 @@
+# Copyright (C) 2010-2018 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
+
+p_home:
+ path: '/'
+ controller: 'Combodo\iTop\Portal\Controller\DefaultController::homeAction'
+
+# Example route
+#p_example:
+# path: /url-pattern
+# controller: 'Combodo\iTop\Portal\Controller\DefaultController::exampleAction'
+# _defaults:
+# _fragment: 'string-to-be-append-to-the-pattern-after-a-#'
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/config/routes/dev/twig.yaml b/datamodels/2.x/itop-portal-base/portal/config/routes/dev/twig.yaml
new file mode 100644
index 000000000..f4ee83960
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/routes/dev/twig.yaml
@@ -0,0 +1,3 @@
+_errors:
+ resource: '@TwigBundle/Resources/config/routing/errors.xml'
+ prefix: /_error
diff --git a/datamodels/2.x/itop-portal-base/portal/config/routes/extensions_extra_routes.php b/datamodels/2.x/itop-portal-base/portal/config/routes/extensions_extra_routes.php
new file mode 100644
index 000000000..d72d9e241
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/routes/extensions_extra_routes.php
@@ -0,0 +1,34 @@
+add(
+ $route['bind'],
+ new Route(
+ $route['pattern'],
+ array_merge(
+ ['_controller' => $route['callback']],
+ $route['values']
+ ),
+ $route['asserts']
+ )
+ );
+}
+
+return $routes;
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/config/routes/user_profile_brick.yaml b/datamodels/2.x/itop-portal-base/portal/config/routes/user_profile_brick.yaml
new file mode 100644
index 000000000..ffe7e0630
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/routes/user_profile_brick.yaml
@@ -0,0 +1,22 @@
+# Copyright (C) 2010-2018 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
+
+p_user_profile_brick:
+ path: '/user/{sBrickId}'
+ defaults:
+ _controller: 'Combodo\iTop\Portal\Controller\UserProfileBrickController::DisplayAction'
+ sBrickId: ~
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/config/services.yaml b/datamodels/2.x/itop-portal-base/portal/config/services.yaml
new file mode 100644
index 000000000..0b3735e3f
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/config/services.yaml
@@ -0,0 +1,121 @@
+# Copyright (C) 2010-2018 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
+
+# This file is the entry point to configure your own services.
+# Files in the packages/ subdirectory configure your dependencies.
+
+imports:
+ - { resource: "legacy_silex_compat_layer.php" }
+
+# Put parameters here that don't need to change on each machine where the app is deployed
+# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
+parameters:
+ # Replace default url generator service
+ router.options.generator_base_class: Combodo\iTop\Portal\Routing\UrlGenerator
+
+ # Used in templates
+ combodo.current_environment: !php/const COMBODO_CURRENT_ENVIRONMENT
+ combodo.absolute_url: !php/const COMBODO_ABSOLUTE_URL
+ combodo.modules.absolute_url: !php/const COMBODO_MODULES_ABSOLUTE_URL
+ combodo.modules.absolute_path: !php/const MODULESROOT
+ combodo.portal.base.absolute_url: !php/const COMBODO_PORTAL_BASE_ABSOLUTE_URL
+ combodo.portal.base.absolute_path: !php/const COMBODO_PORTAL_BASE_ABSOLUTE_PATH
+ combodo.portal.instance.absolute_url: !php/const COMBODO_PORTAL_INSTANCE_ABSOLUTE_URL
+ combodo.portal.instance.id: !php/const PORTAL_ID
+
+services:
+ # Default configuration for services in *this* file
+ _defaults:
+ autowire: true # Automatically injects dependencies in your services.
+ autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
+ public: false # Allows optimizing the container by removing unused services; this also means
+ # fetching services directly from the container via $container->get() won't work.
+ # The best practice is to be explicit about your dependencies anyway.
+ bind:
+ $sPortalCachePath: !php/const PORTAL_CACHE_PATH
+ $sPortalId: !php/const PORTAL_ID
+ $sCombodoPortalBaseAbsolutePath: !php/const COMBODO_PORTAL_BASE_ABSOLUTE_PATH
+
+ # Makes classes in src/ available to be used as services
+ # This creates a service per class whose id is the fully-qualified class name
+ Combodo\iTop\Portal\:
+ resource: '../src/*'
+ exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
+
+ # Controllers are imported separately to make sure services can be injected
+ # as action arguments even if you don't extend any base controller class
+ Combodo\iTop\Portal\Controller\:
+ resource: '../src/Controller'
+ tags: ['controller.service_arguments']
+
+ # Tag services without defining them (see https://symfony.com/doc/current/service_container/tags.html#autoconfiguring-tags)
+ _instanceof:
+ Combodo\iTop\Portal\EventListener\UserProvider:
+ tags: [{ name: 'kernel.event_listener', event: 'kernel.request' }]
+ Combodo\iTop\Portal\EventListener\ApplicationContextSetUrlMakerClass:
+ tags: [{ name: 'kernel.event_listener', event: 'kernel.request' }]
+
+
+ # Add more service definitions when explicit configuration is needed
+ # Please note that last definitions always *replace* previous ones
+
+ # VariableAccessor: When a service require a parameter, you have to fully declare it: let's create intermediate (& simple) services and have them auto-wired
+ Combodo\iTop\Portal\VariableAccessor\CombodoPortalInstanceConf:
+ arguments:
+ - '%combodo.portal.instance.conf%'
+ combodo.current_contact.photo_url:
+ class: Combodo\iTop\Portal\VariableAccessor\CombodoCurrentContactPhotoUrl
+ arguments:
+ - '@Combodo\iTop\Portal\Security\ItopUser'
+ combodo.current_user:
+ class: Combodo\iTop\Portal\VariableAccessor\CombodoCurrentUser
+ arguments:
+ - '@Combodo\iTop\Portal\Security\ItopUser'
+
+ # Legacy code as a service: since it is not in the auto-wiring path, it needs to be explicitly declared
+ ModuleDesign:
+ class: 'ModuleDesign'
+ arguments:
+ - '%combodo.portal.instance.id%'
+
+ # Decoration
+ # - Compatibility layer with Silex\Application which was used almost everywhere in the portal's templates
+ Combodo\iTop\Portal\Twig\AppVariable:
+ decorates: twig.app_variable
+ arguments:
+ - '@Combodo\iTop\Portal\Twig\AppVariable.inner'
+ - '@service_container'
+
+ # Aliases
+ brick_collection:
+ alias: Combodo\iTop\Portal\Brick\BrickCollection
+ public: true
+ request_manipulator:
+ alias: Combodo\iTop\Portal\Helper\RequestManipulatorHelper
+ public: true
+ scope_validator:
+ alias: Combodo\iTop\Portal\Helper\ScopeValidatorHelper
+ public: true
+ context_manipulator:
+ alias: Combodo\iTop\Portal\Helper\ContextManipulatorHelper
+ public: true
+ lifecycle_validator:
+ alias: Combodo\iTop\Portal\Helper\LifecycleValidatorHelper
+ public: true
+ url_generator:
+ alias: router
+ public: true
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/css/bootstrap-theme-combodo.css b/datamodels/2.x/itop-portal-base/portal/public/css/bootstrap-theme-combodo.css
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/css/bootstrap-theme-combodo.css
rename to datamodels/2.x/itop-portal-base/portal/public/css/bootstrap-theme-combodo.css
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/css/bootstrap-theme-combodo.scss b/datamodels/2.x/itop-portal-base/portal/public/css/bootstrap-theme-combodo.scss
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/css/bootstrap-theme-combodo.scss
rename to datamodels/2.x/itop-portal-base/portal/public/css/bootstrap-theme-combodo.scss
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/css/portal.css b/datamodels/2.x/itop-portal-base/portal/public/css/portal.css
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/css/portal.css
rename to datamodels/2.x/itop-portal-base/portal/public/css/portal.css
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/css/portal.scss b/datamodels/2.x/itop-portal-base/portal/public/css/portal.scss
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/css/portal.scss
rename to datamodels/2.x/itop-portal-base/portal/public/css/portal.scss
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/css/variables.scss b/datamodels/2.x/itop-portal-base/portal/public/css/variables.scss
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/css/variables.scss
rename to datamodels/2.x/itop-portal-base/portal/public/css/variables.scss
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/img/backgrounds/geometric-orange.svg b/datamodels/2.x/itop-portal-base/portal/public/img/backgrounds/geometric-orange.svg
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/img/backgrounds/geometric-orange.svg
rename to datamodels/2.x/itop-portal-base/portal/public/img/backgrounds/geometric-orange.svg
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/img/icons/checklist-ok-orange-100px.png b/datamodels/2.x/itop-portal-base/portal/public/img/icons/checklist-ok-orange-100px.png
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/img/icons/checklist-ok-orange-100px.png
rename to datamodels/2.x/itop-portal-base/portal/public/img/icons/checklist-ok-orange-100px.png
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/img/icons/headset-mic-orange-100px.png b/datamodels/2.x/itop-portal-base/portal/public/img/icons/headset-mic-orange-100px.png
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/img/icons/headset-mic-orange-100px.png
rename to datamodels/2.x/itop-portal-base/portal/public/img/icons/headset-mic-orange-100px.png
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/img/icons/hierarchy-white-13px.png b/datamodels/2.x/itop-portal-base/portal/public/img/icons/hierarchy-white-13px.png
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/img/icons/hierarchy-white-13px.png
rename to datamodels/2.x/itop-portal-base/portal/public/img/icons/hierarchy-white-13px.png
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/img/icons/laptop-cursor-orange-100px.png b/datamodels/2.x/itop-portal-base/portal/public/img/icons/laptop-cursor-orange-100px.png
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/img/icons/laptop-cursor-orange-100px.png
rename to datamodels/2.x/itop-portal-base/portal/public/img/icons/laptop-cursor-orange-100px.png
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/img/icons/network-device-orange-100px.png b/datamodels/2.x/itop-portal-base/portal/public/img/icons/network-device-orange-100px.png
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/img/icons/network-device-orange-100px.png
rename to datamodels/2.x/itop-portal-base/portal/public/img/icons/network-device-orange-100px.png
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/img/icons/puzzle-piece-orange-100px.png b/datamodels/2.x/itop-portal-base/portal/public/img/icons/puzzle-piece-orange-100px.png
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/img/icons/puzzle-piece-orange-100px.png
rename to datamodels/2.x/itop-portal-base/portal/public/img/icons/puzzle-piece-orange-100px.png
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/img/icons/warning-sign-orange-100px.png b/datamodels/2.x/itop-portal-base/portal/public/img/icons/warning-sign-orange-100px.png
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/img/icons/warning-sign-orange-100px.png
rename to datamodels/2.x/itop-portal-base/portal/public/img/icons/warning-sign-orange-100px.png
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/img/user-profile-default-256px.png b/datamodels/2.x/itop-portal-base/portal/public/img/user-profile-default-256px.png
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/img/user-profile-default-256px.png
rename to datamodels/2.x/itop-portal-base/portal/public/img/user-profile-default-256px.png
diff --git a/datamodels/2.x/itop-portal-base/portal/public/index.php b/datamodels/2.x/itop-portal-base/portal/public/index.php
new file mode 100644
index 000000000..f694c1001
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/public/index.php
@@ -0,0 +1,30 @@
+handle($request);
+$response->send();
+$kernel->terminate($request, $response);
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/js/dataTables.accentNeutraliseForFilter.js b/datamodels/2.x/itop-portal-base/portal/public/js/dataTables.accentNeutraliseForFilter.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/js/dataTables.accentNeutraliseForFilter.js
rename to datamodels/2.x/itop-portal-base/portal/public/js/dataTables.accentNeutraliseForFilter.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/js/export.js b/datamodels/2.x/itop-portal-base/portal/public/js/export.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/js/export.js
rename to datamodels/2.x/itop-portal-base/portal/public/js/export.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/js/portal_form_field.js b/datamodels/2.x/itop-portal-base/portal/public/js/portal_form_field.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/js/portal_form_field.js
rename to datamodels/2.x/itop-portal-base/portal/public/js/portal_form_field.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/js/portal_form_field_html.js b/datamodels/2.x/itop-portal-base/portal/public/js/portal_form_field_html.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/js/portal_form_field_html.js
rename to datamodels/2.x/itop-portal-base/portal/public/js/portal_form_field_html.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/js/portal_form_field_set.js b/datamodels/2.x/itop-portal-base/portal/public/js/portal_form_field_set.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/js/portal_form_field_set.js
rename to datamodels/2.x/itop-portal-base/portal/public/js/portal_form_field_set.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/js/portal_form_handler.js b/datamodels/2.x/itop-portal-base/portal/public/js/portal_form_handler.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/js/portal_form_handler.js
rename to datamodels/2.x/itop-portal-base/portal/public/js/portal_form_handler.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker-standalone.css b/datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker-standalone.css
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker-standalone.css
rename to datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker-standalone.css
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css b/datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css
rename to datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css b/datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css
rename to datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js b/datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/css/bootstrap-theme.css.map b/datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/css/bootstrap-theme.css.map
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/css/bootstrap-theme.css.map
rename to datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/css/bootstrap-theme.css.map
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/css/bootstrap-theme.min.css b/datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/css/bootstrap-theme.min.css
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/css/bootstrap-theme.min.css
rename to datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/css/bootstrap-theme.min.css
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/css/bootstrap.css.map b/datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/css/bootstrap.css.map
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/css/bootstrap.css.map
rename to datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/css/bootstrap.css.map
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/css/bootstrap.min.css b/datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/css/bootstrap.min.css
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/css/bootstrap.min.css
rename to datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/css/bootstrap.min.css
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/fonts/glyphicons-halflings-regular.eot b/datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/fonts/glyphicons-halflings-regular.eot
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/fonts/glyphicons-halflings-regular.eot
rename to datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/fonts/glyphicons-halflings-regular.eot
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/fonts/glyphicons-halflings-regular.svg b/datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/fonts/glyphicons-halflings-regular.svg
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/fonts/glyphicons-halflings-regular.svg
rename to datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/fonts/glyphicons-halflings-regular.svg
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf b/datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf
rename to datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/fonts/glyphicons-halflings-regular.woff b/datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/fonts/glyphicons-halflings-regular.woff
rename to datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 b/datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2
rename to datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/js/bootstrap.min.js b/datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/js/bootstrap.min.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/js/bootstrap.min.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/js/bootstrap.min.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/js/npm.js b/datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/js/npm.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/bootstrap/js/npm.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/bootstrap/js/npm.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/css/dataTables.bootstrap.min.css b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/dataTables.bootstrap.min.css
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/css/dataTables.bootstrap.min.css
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/dataTables.bootstrap.min.css
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/css/fixedHeader.bootstrap.min.css b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/fixedHeader.bootstrap.min.css
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/css/fixedHeader.bootstrap.min.css
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/fixedHeader.bootstrap.min.css
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/css/responsive.bootstrap.min.css b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/responsive.bootstrap.min.css
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/css/responsive.bootstrap.min.css
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/responsive.bootstrap.min.css
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/css/scroller.bootstrap.min.css b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/scroller.bootstrap.min.css
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/css/scroller.bootstrap.min.css
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/scroller.bootstrap.min.css
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/css/select.bootstrap.min.css b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/select.bootstrap.min.css
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/css/select.bootstrap.min.css
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/select.bootstrap.min.css
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/css/select.dataTables.min.css b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/select.dataTables.min.css
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/css/select.dataTables.min.css
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/select.dataTables.min.css
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/images/sort_asc.png b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_asc.png
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/images/sort_asc.png
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_asc.png
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/images/sort_asc_disabled.png b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_asc_disabled.png
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/images/sort_asc_disabled.png
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_asc_disabled.png
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/images/sort_both.png b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_both.png
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/images/sort_both.png
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_both.png
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/images/sort_desc.png b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_desc.png
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/images/sort_desc.png
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_desc.png
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/images/sort_desc_disabled.png b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_desc_disabled.png
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/images/sort_desc_disabled.png
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_desc_disabled.png
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/js/dataTables.bootstrap.min.js b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.bootstrap.min.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/js/dataTables.bootstrap.min.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.bootstrap.min.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/js/dataTables.fixedHeader.min.js b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.fixedHeader.min.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/js/dataTables.fixedHeader.min.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.fixedHeader.min.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/js/dataTables.responsive.min.js b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.responsive.min.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/js/dataTables.responsive.min.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.responsive.min.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/js/dataTables.scroller.min.js b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.scroller.min.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/js/dataTables.scroller.min.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.scroller.min.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/js/dataTables.select.min.js b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.select.min.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/js/dataTables.select.min.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.select.min.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/js/datetime-moment.js b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/datetime-moment.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/js/datetime-moment.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/datetime-moment.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/js/jquery.dataTables.min.js b/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/jquery.dataTables.min.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/datatables/js/jquery.dataTables.min.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/jquery.dataTables.min.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/handlebars/js/handlebars.min-768ddbd.js b/datamodels/2.x/itop-portal-base/portal/public/lib/handlebars/js/handlebars.min-768ddbd.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/handlebars/js/handlebars.min-768ddbd.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/handlebars/js/handlebars.min-768ddbd.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/jquery-base64/js/jquery.base64.min.js b/datamodels/2.x/itop-portal-base/portal/public/lib/jquery-base64/js/jquery.base64.min.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/jquery-base64/js/jquery.base64.min.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/jquery-base64/js/jquery.base64.min.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/jquery-treelistfilter/js/TreeListFilter.js b/datamodels/2.x/itop-portal-base/portal/public/lib/jquery-treelistfilter/js/TreeListFilter.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/jquery-treelistfilter/js/TreeListFilter.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/jquery-treelistfilter/js/TreeListFilter.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/jquery-visible/js/jquery.visible.min.js b/datamodels/2.x/itop-portal-base/portal/public/lib/jquery-visible/js/jquery.visible.min.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/jquery-visible/js/jquery.visible.min.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/jquery-visible/js/jquery.visible.min.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/typeahead/css/typeaheadjs.bootstrap.css b/datamodels/2.x/itop-portal-base/portal/public/lib/typeahead/css/typeaheadjs.bootstrap.css
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/typeahead/css/typeaheadjs.bootstrap.css
rename to datamodels/2.x/itop-portal-base/portal/public/lib/typeahead/css/typeaheadjs.bootstrap.css
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/typeahead/js/bloodhound.min.js b/datamodels/2.x/itop-portal-base/portal/public/lib/typeahead/js/bloodhound.min.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/typeahead/js/bloodhound.min.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/typeahead/js/bloodhound.min.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/typeahead/js/typeahead.bundle.min.js b/datamodels/2.x/itop-portal-base/portal/public/lib/typeahead/js/typeahead.bundle.min.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/typeahead/js/typeahead.bundle.min.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/typeahead/js/typeahead.bundle.min.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/typeahead/js/typeahead.jquery.min.js b/datamodels/2.x/itop-portal-base/portal/public/lib/typeahead/js/typeahead.jquery.min.js
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/typeahead/js/typeahead.jquery.min.js
rename to datamodels/2.x/itop-portal-base/portal/public/lib/typeahead/js/typeahead.jquery.min.js
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/web/lib/typeahead/js/update.txt b/datamodels/2.x/itop-portal-base/portal/public/lib/typeahead/js/update.txt
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/web/lib/typeahead/js/update.txt
rename to datamodels/2.x/itop-portal-base/portal/public/lib/typeahead/js/update.txt
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/entities/abstractbrick.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/Brick/AbstractBrick.php
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/entities/abstractbrick.class.inc.php
rename to datamodels/2.x/itop-portal-base/portal/src/Brick/AbstractBrick.php
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/entities/aggregatepagebrick.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/Brick/AggregatePageBrick.php
similarity index 95%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/entities/aggregatepagebrick.class.inc.php
rename to datamodels/2.x/itop-portal-base/portal/src/Brick/AggregatePageBrick.php
index 4191a3e92..b379f19d5 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/entities/aggregatepagebrick.class.inc.php
+++ b/datamodels/2.x/itop-portal-base/portal/src/Brick/AggregatePageBrick.php
@@ -27,7 +27,7 @@ class AggregatePageBrick extends PortalBrick
{
const DEFAULT_DECORATION_CLASS_HOME = 'fa fa-dashboard';
const DEFAULT_DECORATION_CLASS_NAVIGATION_MENU = 'fa fa-dashboard fa-2x';
- const DEFAULT_PAGE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/aggregate-page/layout.html.twig';
+ const DEFAULT_PAGE_TEMPLATE_PATH = 'itop-portal-base/portal/templates/bricks/aggregate-page/layout.html.twig';
static $sRouteName = 'p_aggregatepage_brick';
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Brick/BrickCollection.php b/datamodels/2.x/itop-portal-base/portal/src/Brick/BrickCollection.php
new file mode 100644
index 000000000..219ab07b7
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/Brick/BrickCollection.php
@@ -0,0 +1,173 @@
+oModuleDesign = $oModuleDesign;
+ }
+
+ public function __call($method, $arguments)
+ {
+ return $this->GetBrickProperty($method);
+ }
+
+ /**
+ * @param string $sId
+ *
+ * @return mixed
+ * @throws PropertyNotFoundException
+ */
+ private function GetBrickProperty($sId)
+ {
+ if (array_key_exists($sId, $this->getBricks())) {
+ return $this->getBricks()[$sId];
+ }
+
+ throw new PropertyNotFoundException(
+ "The property '$sId' do not exists in BricksCollection with keys: ".array_keys($this->getBricks())
+ );
+ }
+
+ public function getBricks()
+ {
+ if (! isset($this->aAllowedBricksData)) {
+ $this->LazyLoad();
+ }
+
+ return $this->aAllowedBricksData;
+ }
+
+ public function getBrickById($id)
+ {
+ foreach ($this->getBricks()['bricks'] as $brick) {
+ if ($brick->GetId() === $id)
+ {
+ return $brick;
+ }
+ }
+
+ throw new BrickNotFoundException('Brick with id = "'.$id.'" was not found among loaded bricks.');
+ }
+
+ private function LazyLoad()
+ {
+ $aRawBrickList = $this->GetRawBrickList();
+
+ $this->aAllowedBricksData = array(
+ 'bricks' => array(),
+ 'bricks_total_width' => 0,
+ 'bricks_home_count' => 0,
+ 'bricks_navigation_menu_count' => 0
+ );
+
+ foreach ($aRawBrickList as $oBrick) {
+ ApplicationHelper::LoadBrickSecurity($oBrick);
+
+ if ($oBrick->GetActive() && $oBrick->IsGrantedForProfiles(UserRights::ListProfiles()))
+ {
+ $this->aAllowedBricksData['bricks'][] = $oBrick;
+ $this->aAllowedBricksData['bricks_total_width'] += $oBrick->GetWidth();
+ if ($oBrick->GetVisibleHome())
+ {
+ $this->aAllowedBricksData['bricks_home_count']++;
+ }
+ if ($oBrick->GetVisibleNavigationMenu())
+ {
+ $this->aAllowedBricksData['bricks_navigation_menu_count']++;
+ }
+ }
+ }
+
+ // - Sorting bricks by rank
+ $this->aAllowedBricksData['bricks_ordering'] = array();
+ // - Home
+ $this->aAllowedBricksData['bricks_ordering']['home'] = $this->aAllowedBricksData['bricks'];
+ usort($this->aAllowedBricksData['bricks_ordering']['home'], function (PortalBrick $a, PortalBrick $b) {
+ return $a->GetRankHome() > $b->GetRankHome();
+ });
+ // - Navigation menu
+ $this->aAllowedBricksData['bricks_ordering']['navigation_menu'] = $this->aAllowedBricksData['bricks'];
+ usort($this->aAllowedBricksData['bricks_ordering']['navigation_menu'], function (PortalBrick $a, PortalBrick $b) {
+ return $a->GetRankNavigationMenu() > $b->GetRankNavigationMenu();
+ });
+ }
+
+ private function GetRawBrickList()
+ {
+ $aBricks = array();
+ foreach ($this->oModuleDesign->GetNodes('/module_design/bricks/brick') as $oBrickNode)
+ {
+ $sBrickClass = $oBrickNode->getAttribute('xsi:type');
+ try
+ {
+ if (class_exists($sBrickClass))
+ {
+ /** @var \Combodo\iTop\Portal\Brick\PortalBrick $oBrick */
+ $oBrick = new $sBrickClass();
+ $oBrick->LoadFromXml($oBrickNode);
+
+ $aBricks[] = $oBrick;
+ }
+ else
+ {
+ throw new DOMFormatException('Unknown brick class "'.$sBrickClass.'" from xsi:type attribute', null,
+ null, $oBrickNode);
+ }
+ }
+ catch (DOMFormatException $e)
+ {
+ throw new Exception('Could not create brick ('.$sBrickClass.') from XML because of a DOM problem : '.$e->getMessage());
+ }
+ catch (Exception $e)
+ {
+ throw new Exception('Could not create brick ('.$sBrickClass.') from XML : '.$oBrickNode->Dump().' '.$e->getMessage());
+ }
+ }
+
+ return $aBricks;
+ }
+
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Brick/BrickNotFoundException.php b/datamodels/2.x/itop-portal-base/portal/src/Brick/BrickNotFoundException.php
new file mode 100644
index 000000000..c0bfb0234
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/Brick/BrickNotFoundException.php
@@ -0,0 +1,21 @@
+
+/**
+ * Copyright (C) 2013-2019 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
+ *
+ *
+ */
namespace Combodo\iTop\Portal\Brick;
@@ -266,7 +269,7 @@ class BrowseBrick extends PortalBrick
}
else
{
- $sTemplatePath = 'itop-portal-base/portal/src/views/bricks/browse/mode_' . $sModeId . '.html.twig';
+ $sTemplatePath = 'itop-portal-base/portal/templates/bricks/browse/mode_' . $sModeId . '.html.twig';
}
$aModeData['template'] = $sTemplatePath;
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/entities/createbrick.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/Brick/CreateBrick.php
similarity index 76%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/entities/createbrick.class.inc.php
rename to datamodels/2.x/itop-portal-base/portal/src/Brick/CreateBrick.php
index dcc21a83c..08e514bf4 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/entities/createbrick.class.inc.php
+++ b/datamodels/2.x/itop-portal-base/portal/src/Brick/CreateBrick.php
@@ -1,21 +1,24 @@
+/**
+ * Copyright (C) 2013-2019 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
+ *
+ *
+ */
namespace Combodo\iTop\Portal\Brick;
@@ -31,7 +34,7 @@ class CreateBrick extends PortalBrick
{
const DEFAULT_DECORATION_CLASS_HOME = 'fa fa-plus';
const DEFAULT_DECORATION_CLASS_NAVIGATION_MENU = 'fa fa-plus fa-2x';
- const DEFAULT_PAGE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/create/modal.html.twig';
+ const DEFAULT_PAGE_TEMPLATE_PATH = 'itop-portal-base/portal/templates/bricks/create/modal.html.twig';
const DEFAULT_CLASS = '';
static $sRouteName = 'p_create_brick';
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/entities/filterbrick.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/Brick/FilterBrick.php
similarity index 84%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/entities/filterbrick.class.inc.php
rename to datamodels/2.x/itop-portal-base/portal/src/Brick/FilterBrick.php
index 849d89754..31cbf39ce 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/entities/filterbrick.class.inc.php
+++ b/datamodels/2.x/itop-portal-base/portal/src/Brick/FilterBrick.php
@@ -1,21 +1,24 @@
+/**
+ * Copyright (C) 2013-2019 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
+ *
+ *
+ */
namespace Combodo\iTop\Portal\Brick;
@@ -30,7 +33,7 @@ use Combodo\iTop\DesignElement;
class FilterBrick extends PortalBrick
{
const DEFAULT_VISIBLE_NAVIGATION_MENU = false;
- const DEFAULT_TILE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/filter/tile.html.twig';
+ const DEFAULT_TILE_TEMPLATE_PATH = 'itop-portal-base/portal/templates/bricks/filter/tile.html.twig';
const DEFAULT_DECORATION_CLASS_HOME = 'fa fa-search';
const DEFAULT_DECORATION_CLASS_NAVIGATION_MENU = 'fa fa-search fa-2x';
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/entities/managebrick.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/Brick/ManageBrick.php
similarity index 95%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/entities/managebrick.class.inc.php
rename to datamodels/2.x/itop-portal-base/portal/src/Brick/ManageBrick.php
index 4b19438c5..c08d144c8 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/entities/managebrick.class.inc.php
+++ b/datamodels/2.x/itop-portal-base/portal/src/Brick/ManageBrick.php
@@ -1,22 +1,24 @@
-//
+/**
+ * Copyright (C) 2013-2019 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
+ *
+ *
+ */
namespace Combodo\iTop\Portal\Brick;
@@ -41,8 +43,8 @@ class ManageBrick extends PortalBrick
const ENUM_DISPLAY_MODE_PIE = 'pie-chart';
const ENUM_DISPLAY_MODE_BAR = 'bar-chart';
- const ENUM_PAGE_TEMPLATE_PATH_TABLE = 'itop-portal-base/portal/src/views/bricks/manage/layout-table.html.twig';
- const ENUM_PAGE_TEMPLATE_PATH_CHART = 'itop-portal-base/portal/src/views/bricks/manage/layout-chart.html.twig';
+ const ENUM_PAGE_TEMPLATE_PATH_TABLE = 'itop-portal-base/portal/templates/bricks/manage/layout-table.html.twig';
+ const ENUM_PAGE_TEMPLATE_PATH_CHART = 'itop-portal-base/portal/templates/bricks/manage/layout-chart.html.twig';
const DEFAULT_DECORATION_CLASS_HOME = 'fa fa-pencil-square';
const DEFAULT_DECORATION_CLASS_NAVIGATION_MENU = 'fa fa-pencil-square fa-2x';
@@ -58,7 +60,7 @@ class ManageBrick extends PortalBrick
const DEFAULT_GROUP_LIMIT = 0;
const DEFAULT_GROUP_SHOW_OTHERS = true;
- const DEFAULT_TILE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/manage/tile-default.html.twig';
+ const DEFAULT_TILE_TEMPLATE_PATH = 'itop-portal-base/portal/templates/bricks/manage/tile-default.html.twig';
const DEFAULT_TILE_CONTROLLER_ACTION = 'Combodo\\iTop\\Portal\\Controller\\ManageBrickController::TileAction';
static $aDisplayModes = array(
@@ -76,28 +78,28 @@ class ManageBrick extends PortalBrick
static $aPresentationData = array(
self::ENUM_TILE_MODE_BADGE => array(
'decorationCssClass' => 'fa fa-id-card-o fa-2x',
- 'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-badge.html.twig',
+ 'tileTemplate' => 'itop-portal-base/portal/templates/bricks/manage/tile-badge.html.twig',
'layoutTemplate' => self::ENUM_PAGE_TEMPLATE_PATH_TABLE,
'layoutDisplayMode' => self::ENUM_DISPLAY_MODE_LIST,
'need_details' => true,
),
self::ENUM_TILE_MODE_TOP => array(
'decorationCssClass' => 'fa fa-signal fa-rotate-270 fa-2x',
- 'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-top-list.html.twig',
+ 'tileTemplate' => 'itop-portal-base/portal/templates/bricks/manage/tile-top-list.html.twig',
'layoutTemplate' => self::ENUM_PAGE_TEMPLATE_PATH_TABLE,
'layoutDisplayMode' => self::ENUM_DISPLAY_MODE_LIST,
'need_details' => true,
),
self::ENUM_TILE_MODE_PIE => array(
'decorationCssClass' => 'fa fa-pie-chart fa-2x',
- 'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-chart.html.twig',
+ 'tileTemplate' => 'itop-portal-base/portal/templates/bricks/manage/tile-chart.html.twig',
'layoutTemplate' => self::ENUM_PAGE_TEMPLATE_PATH_CHART,
'layoutDisplayMode' => self::ENUM_DISPLAY_MODE_PIE,
'need_details' => false,
),
self::ENUM_TILE_MODE_BAR => array(
'decorationCssClass' => 'fa fa-bar-chart fa-2x',
- 'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-chart.html.twig',
+ 'tileTemplate' => 'itop-portal-base/portal/templates/bricks/manage/tile-chart.html.twig',
'layoutTemplate' => self::ENUM_PAGE_TEMPLATE_PATH_CHART,
'layoutDisplayMode' => self::ENUM_DISPLAY_MODE_BAR,
'need_details' => false,
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/entities/portalbrick.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/Brick/PortalBrick.php
similarity index 94%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/entities/portalbrick.class.inc.php
rename to datamodels/2.x/itop-portal-base/portal/src/Brick/PortalBrick.php
index f88c01aef..ceec38dd1 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/entities/portalbrick.class.inc.php
+++ b/datamodels/2.x/itop-portal-base/portal/src/Brick/PortalBrick.php
@@ -1,21 +1,24 @@
+/**
+ * Copyright (C) 2013-2019 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
+ *
+ *
+ */
namespace Combodo\iTop\Portal\Brick;
@@ -43,7 +46,7 @@ abstract class PortalBrick extends AbstractBrick
const DEFAULT_VISIBLE_NAVIGATION_MENU = true;
const DEFAULT_DECORATION_CLASS_HOME = '';
const DEFAULT_DECORATION_CLASS_NAVIGATION_MENU = '';
- const DEFAULT_TILE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/tile.html.twig';
+ const DEFAULT_TILE_TEMPLATE_PATH = 'itop-portal-base/portal/templates/bricks/tile.html.twig';
const DEFAULT_TILE_CONTROLLER_ACTION = null;
const DEFAULT_OPENING_TARGET = self::ENUM_OPENING_TARGET_MODAL;
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Brick/PropertyNotFoundException.php b/datamodels/2.x/itop-portal-base/portal/src/Brick/PropertyNotFoundException.php
new file mode 100644
index 000000000..3b7f507f7
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/Brick/PropertyNotFoundException.php
@@ -0,0 +1,21 @@
+
+/**
+ * Copyright (C) 2013-2019 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
+ *
+ *
+ */
namespace Combodo\iTop\Portal\Brick;
@@ -28,8 +31,8 @@ use Combodo\iTop\DesignElement;
*/
class UserProfileBrick extends PortalBrick
{
- const DEFAULT_PAGE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/user-profile/layout.html.twig';
- const DEFAULT_TILE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/user-profile/tile.html.twig';
+ const DEFAULT_PAGE_TEMPLATE_PATH = 'itop-portal-base/portal/templates/bricks/user-profile/layout.html.twig';
+ const DEFAULT_TILE_TEMPLATE_PATH = 'itop-portal-base/portal/templates/bricks/user-profile/tile.html.twig';
const DEFAULT_VISIBLE_NAVIGATION_MENU = false;
const DEFAULT_VISIBLE_HOME = false;
const DEFAUT_TITLE = 'Brick:Portal:UserProfile:Title';
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Controller/.gitignore b/datamodels/2.x/itop-portal-base/portal/src/Controller/.gitignore
new file mode 100644
index 000000000..e69de29bb
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Controller/AbstractController.php b/datamodels/2.x/itop-portal-base/portal/src/Controller/AbstractController.php
new file mode 100644
index 000000000..80e54b780
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/Controller/AbstractController.php
@@ -0,0 +1,37 @@
+
+ * @since 2.3.0
+ */
+abstract class AbstractController extends Controller
+{
+
+}
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Controller/DefaultController.php b/datamodels/2.x/itop-portal-base/portal/src/Controller/DefaultController.php
new file mode 100644
index 000000000..5e58725a4
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/Controller/DefaultController.php
@@ -0,0 +1,84 @@
+
+ * @since 2.3.0
+ */
+class DefaultController extends AbstractController
+{
+ /**
+ * @param \Symfony\Component\HttpFoundation\Request $oRequest
+ * @param \Combodo\iTop\Portal\Brick\BrickCollection $bricksCollection
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ */
+ public function homeAction(Request $oRequest, BrickCollection $bricksCollection)
+ {
+ $aData = array();
+
+ // Rendering tiles
+ $aData['aTilesRendering'] = array();
+ /** @var \Combodo\iTop\Portal\Brick\PortalBrick $oBrick */
+ foreach($bricksCollection->getBricks()['bricks'] as $oBrick)
+ {
+ // Doing it only for tile visible on home page to avoid unnecessary rendering
+ if (($oBrick->GetVisibleHome() === true) && ($oBrick->GetTileControllerAction() !== null))
+ {
+ $aControllerActionParts = explode('::', $oBrick->GetTileControllerAction());
+ if (count($aControllerActionParts) !== 2)
+ {
+ return new Response('Tile controller action must be of form "\Namespace\ControllerClass::FunctionName" for brick "' . $oBrick->GetId() . '"', 500);
+ }
+
+ $sControllerName = $aControllerActionParts[0];
+ $sControllerAction = $aControllerActionParts[1];
+
+ $oController = new $sControllerName();
+ if(!$oController instanceof ContainerAwareInterface)
+ {
+ return new Response('Tile controller must be implement ContainerAwareInterface for brick "' . $oBrick->GetId() . '"', 500);
+ }
+ $oController->setContainer($this->container);
+ /** @var Response $oResponse */
+ $oResponse = $oController->$sControllerAction($oRequest, $oBrick->GetId());
+ $aData['aTilesRendering'][$oBrick->GetId()] = $oResponse->getContent();
+ }
+ }
+
+ // Home page template
+ $template = $this->getParameter('combodo.portal.instance.conf')['properties']['templates']['home'];
+
+ return $this->render($template, $aData);
+ }
+
+}
diff --git a/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/AbstractConfiguration.php b/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/AbstractConfiguration.php
new file mode 100644
index 000000000..3e74eb11b
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/AbstractConfiguration.php
@@ -0,0 +1,31 @@
+moduleDesign = $moduleDesign;
+ }
+
+ /**
+ * @return \ModuleDesign
+ */
+ public function getModuleDesign()
+ {
+ return $this->moduleDesign;
+ }
+
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Basic.php b/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Basic.php
new file mode 100644
index 000000000..3cafecb4f
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Basic.php
@@ -0,0 +1,335 @@
+getInitialPortalConf();
+ // - Global portal properties
+ $aPortalConf = $this->ParseGlobalProperties($aPortalConf);
+ // - Rectifying portal logo url
+ $aPortalConf = $this->appendLogoUri($aPortalConf);
+ // - User allowed portals
+ $aPortalConf['portals'] = UserRights::GetAllowedPortals();
+
+ // - class list
+ $aPortalConf['ui_extensions'] = $this->getUiExtensions($container);
+ }
+ catch (Exception $e)
+ {
+ throw new Exception('Error while parsing portal configuration file : '.$e->getMessage());
+ }
+
+ $container->setParameter('combodo.portal.instance.conf', $aPortalConf);
+ }
+
+
+
+ /**
+ * @return array
+ */
+ private function getInitialPortalConf()
+ {
+ $aPortalConf = array(
+ 'properties' => array(
+ 'id' => PORTAL_ID,
+ 'name' => 'Page:DefaultTitle',
+ 'logo' => (file_exists(MODULESROOT.'branding/portal-logo.png')) ? utils::GetAbsoluteUrlModulesRoot().'branding/portal-logo.png' : '../images/logo-itop-dark-bg.svg',
+ 'themes' => array(
+ 'bootstrap' => 'itop-portal-base/portal/public/css/bootstrap-theme-combodo.scss',
+ 'portal' => 'itop-portal-base/portal/public/css/portal.scss',
+ 'others' => array(),
+ ),
+ 'templates' => array(
+ 'layout' => 'itop-portal-base/portal/templates/layout.html.twig',
+ 'home' => 'itop-portal-base/portal/templates/home/layout.html.twig'
+ ),
+ 'urlmaker_class' => null,
+ 'triggers_query' => null,
+ 'attachments' => array(
+ 'allow_delete' => true
+ ),
+ 'allowed_portals' => array(
+ 'opening_mode' => null,
+ ),
+ ),
+ 'portals' => array(),
+ 'forms' => array(),
+ 'ui_extensions' => array(
+ 'css_files' => array(),
+ 'css_inline' => null,
+ 'js_files' => array(),
+ 'js_inline' => null,
+ 'html' => array(),
+ ),
+ 'bricks' => array(),
+ 'bricks_total_width' => 0,
+ );
+
+ return $aPortalConf;
+ }
+
+ /**
+ * @param ModuleDesign $oDesign
+ * @param array $aPortalConf
+ *
+ * @return array
+ */
+ private function ParseGlobalProperties(array $aPortalConf)
+ {
+ foreach ($this->getModuleDesign()->GetNodes('/module_design/properties/*') as $oPropertyNode) {
+ switch ($oPropertyNode->nodeName) {
+ case 'name':
+ case 'urlmaker_class':
+ case 'triggers_query':
+ $aPortalConf['properties'][$oPropertyNode->nodeName] = $oPropertyNode->GetText(
+ $aPortalConf['properties'][$oPropertyNode->nodeName]
+ );
+ break;
+ case 'logo':
+ $aPortalConf['properties'][$oPropertyNode->nodeName] = $oPropertyNode->GetText(
+ $aPortalConf['properties'][$oPropertyNode->nodeName]
+ );
+ break;
+ case 'themes':
+ case 'templates':
+ $aPortalConf = $this->ParseTemplateAndTheme($aPortalConf, $oPropertyNode);
+ break;
+ case 'attachments':
+ $aPortalConf = $this->ParseAttachments($aPortalConf, $oPropertyNode);
+ break;
+ case 'allowed_portals':
+ $aPortalConf = $this->ParseAllowedPortals($aPortalConf, $oPropertyNode);
+ break;
+ }
+ }
+
+ return $aPortalConf;
+ }
+
+ /**
+ * @param array $aPortalConf
+ * @param $oPropertyNode
+ *
+ * @return array
+ */
+ private function ParseTemplateAndTheme(array $aPortalConf, $oPropertyNode)
+ {
+ foreach ($oPropertyNode->GetNodes('template|theme') as $oSubNode) {
+ if (!$oSubNode->hasAttribute('id') || $oSubNode->GetText(null) === null) {
+ throw new DOMFormatException(
+ 'Tag '.$oSubNode->nodeName.' must have a "id" attribute as well as a value',
+ null, null, $oSubNode
+ );
+ }
+
+ $sNodeId = $oSubNode->getAttribute('id');
+ switch ($oSubNode->nodeName) {
+ case 'theme':
+ switch ($sNodeId) {
+ case 'bootstrap':
+ case 'portal':
+ case 'custom':
+ $aPortalConf['properties']['themes'][$sNodeId] = $oSubNode->GetText(null);
+ break;
+ default:
+ $aPortalConf['properties']['themes']['others'][] = $oSubNode->GetText(null);
+ break;
+ }
+ break;
+ case 'template':
+ switch ($sNodeId) {
+ case 'layout':
+ case 'home':
+ $aPortalConf['properties']['templates'][$sNodeId] = $oSubNode->GetText(null);
+ break;
+ default:
+ throw new DOMFormatException(
+ 'Value "'.$sNodeId.'" is not handled for template[@id]',
+ null, null, $oSubNode
+ );
+ break;
+ }
+ break;
+ }
+ }
+
+ return $aPortalConf;
+}
+
+ /**
+ * @param array $aPortalConf
+ * @param $oPropertyNode
+ *
+ * @return array
+ */
+ private function ParseAttachments(array $aPortalConf, $oPropertyNode)
+ {
+ foreach ($oPropertyNode->GetNodes('*') as $oSubNode) {
+ switch ($oSubNode->nodeName) {
+ case 'allow_delete':
+ $sValue = $oSubNode->GetText();
+ // If the text is null, we keep the default value
+ // Else we set it
+ if ($sValue !== null) {
+ $aPortalConf['properties']['attachments'][$oSubNode->nodeName] = ($sValue === 'true') ? true : false;
+ }
+ break;
+ }
+ }
+
+ return$aPortalConf;
+}
+
+ /**
+ * @param array $aPortalConf
+ * @param $oPropertyNode
+ *
+ * @return array
+ */
+ private function ParseAllowedPortals(array $aPortalConf, $oPropertyNode)
+ {
+ foreach ($oPropertyNode->GetNodes('*') as $oSubNode) {
+ switch ($oSubNode->nodeName) {
+ case 'opening_mode':
+ $sValue = $oSubNode->GetText();
+ // If the text is null, we keep the default value
+ // Else we set it
+ if ($sValue !== null) {
+ $aPortalConf['properties']['allowed_portals'][$oSubNode->nodeName] = ($sValue === 'self') ? 'self' : 'tab';
+ }
+ break;
+ }
+ }
+
+ return $aPortalConf;
+}
+
+ /**
+ * @param array $aPortalConf
+ *
+ * @return array
+ */
+ private function appendLogoUri(array $aPortalConf)
+ {
+ $sLogoUri = $aPortalConf['properties']['logo'];
+ if (!preg_match('/^http/', $sLogoUri)) {
+ // We prefix it with the server base url
+ $sLogoUri = utils::GetAbsoluteUrlAppRoot().'env-'.utils::GetCurrentEnvironment().'/'.$sLogoUri;
+ }
+ $aPortalConf['properties']['logo'] = $sLogoUri;
+
+ return $aPortalConf;
+ }
+
+ private function getUiExtensions($container)
+ {
+ $aUIExtensions = array(
+ 'css_files' => array(),
+ 'css_inline' => null,
+ 'js_files' => array(),
+ 'js_inline' => null,
+ 'html' => array(),
+ );
+ $aUIExtensionHooks = array(
+ \iPortalUIExtension::ENUM_PORTAL_EXT_UI_BODY,
+ \iPortalUIExtension::ENUM_PORTAL_EXT_UI_NAVIGATION_MENU,
+ \iPortalUIExtension::ENUM_PORTAL_EXT_UI_MAIN_CONTENT,
+ );
+
+ /** @var iPortalUIExtension $oExtensionInstance */
+ foreach (MetaModel::EnumPlugins('iPortalUIExtension') as $oExtensionInstance)
+ {
+ // Adding CSS files
+ $aImportPaths = array($container->getParameter('combodo.portal.base.absolute_path').'css/');
+ foreach($oExtensionInstance->GetCSSFiles($container) as $sCSSFile)
+ {
+ // Removing app root url as we need to pass a path on the file system (relative to app root)
+ $sCSSFilePath = str_replace(utils::GetAbsoluteUrlAppRoot(), '', $sCSSFile);
+ // Compiling SCSS file
+ $sCSSFileCompiled = $container->getParameter('combodo.absolute_url').utils::GetCSSFromSASS($sCSSFilePath,
+ $aImportPaths);
+
+ if(!in_array($sCSSFileCompiled, $aUIExtensions['css_files']))
+ {
+ $aUIExtensions['css_files'][] = $sCSSFileCompiled;
+ }
+ }
+
+ // Adding CSS inline
+ $sCSSInline = $oExtensionInstance->GetCSSInline($container);
+ if ($sCSSInline !== null)
+ {
+ $aUIExtensions['css_inline'] .= "\n\n".$sCSSInline;
+ }
+
+ // Adding JS files
+ $aUIExtensions['js_files'] = array_merge($aUIExtensions['js_files'],
+ $oExtensionInstance->GetJSFiles($container));
+
+ // Adding JS inline
+ $sJSInline = $oExtensionInstance->GetJSInline($container);
+ if ($sJSInline !== null)
+ {
+ // Note: Semi-colon is to prevent previous script that would have omitted it.
+ $aUIExtensions['js_inline'] .= "\n\n;\n".$sJSInline;
+ }
+
+ // Adding HTML for each hook
+ foreach ($aUIExtensionHooks as $sUIExtensionHook)
+ {
+ $sFunctionName = 'Get'.$sUIExtensionHook.'HTML';
+ $sHTML = $oExtensionInstance->$sFunctionName($container);
+ if ($sHTML !== null)
+ {
+ if (!array_key_exists($sUIExtensionHook, $aUIExtensions['html']))
+ {
+ $aUIExtensions['html'][$sUIExtensionHook] = '';
+ }
+ $aUIExtensions['html'][$sUIExtensionHook] .= "\n\n".$sHTML;
+ }
+ }
+ }
+
+ return $aUIExtensions;
+ }
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Forms.php b/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Forms.php
new file mode 100644
index 000000000..5618ba781
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Forms.php
@@ -0,0 +1,271 @@
+getModuleDesign()->GetNodes('/module_design/forms/form') as $oFormNode)
+ {
+ try
+ {
+ // Parsing form id
+ if ($oFormNode->getAttribute('id') === '')
+ {
+ throw new DOMFormatException('form tag must have an id attribute', null, null, $oFormNode);
+ }
+
+ // Parsing form object class
+ if ($oFormNode->GetUniqueElement('class')->GetText() !== null)
+ {
+ // Parsing class
+ $sFormClass = $oFormNode->GetUniqueElement('class')->GetText();
+
+ // Parsing properties
+ $aFormProperties = array(
+ 'display_mode' => ApplicationHelper::FORM_DEFAULT_DISPLAY_MODE,
+ 'always_show_submit' => ApplicationHelper::FORM_DEFAULT_ALWAYS_SHOW_SUBMIT,
+ );
+ if ($oFormNode->GetOptionalElement('properties') !== null)
+ {
+ foreach ($oFormNode->GetOptionalElement('properties')->childNodes as $oPropertyNode)
+ {
+ switch ($oPropertyNode->nodeName)
+ {
+ case 'display_mode':
+ $aFormProperties['display_mode'] = $oPropertyNode->GetText(ApplicationHelper::FORM_DEFAULT_DISPLAY_MODE);
+ break;
+ case 'always_show_submit':
+ $aFormProperties['always_show_submit'] = ($oPropertyNode->GetText('false') === 'true') ? true : false;
+ break;
+ }
+ }
+ }
+
+ // Parsing availables modes for that form (view, edit, create, apply_stimulus)
+ $aFormStimuli = array();
+ if (($oFormNode->GetOptionalElement('modes') !== null) && ($oFormNode->GetOptionalElement('modes')->GetNodes('mode')->length > 0))
+ {
+ $aModes = array();
+ foreach ($oFormNode->GetOptionalElement('modes')->GetNodes('mode') as $oModeNode)
+ {
+ if ($oModeNode->getAttribute('id') !== '')
+ {
+ $aModes[] = $oModeNode->getAttribute('id');
+ }
+ else
+ {
+ throw new DOMFormatException('Mode tag must have an id attribute', null, null,
+ $oFormNode);
+ }
+
+ // If apply_stimulus mode, checking if stimuli are defined
+ if ($oModeNode->getAttribute('id') === 'apply_stimulus')
+ {
+ $oStimuliNode = $oModeNode->GetOptionalElement('stimuli');
+ // if stimuli are defined, we overwrite the form that could have been set by the generic form
+ if ($oStimuliNode !== null)
+ {
+ foreach ($oStimuliNode->GetNodes('stimulus') as $oStimulusNode)
+ {
+ $sStimulusCode = $oStimulusNode->getAttribute('id');
+
+ // Removing default form is present (in case the default forms were parsed before the current one (from current or parent class))
+ if (isset($aForms[$sFormClass]['apply_stimulus'][$sStimulusCode]))
+ {
+ unset($aForms[$sFormClass]['apply_stimulus'][$sStimulusCode]);
+ }
+
+ $aFormStimuli[] = $oStimulusNode->getAttribute('id');
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // If no mode was specified, we set it all but stimuli as it would have no sense that every transition forms
+ // have as many fields displayed as a regular edit form for example.
+ $aModes = array('view', 'edit', 'create');
+ }
+
+ // Parsing fields
+ $aFields = array(
+ 'id' => $oFormNode->getAttribute('id'),
+ 'type' => null,
+ 'properties' => $aFormProperties,
+ 'fields' => null,
+ 'layout' => null
+ );
+ // ... either enumerated fields ...
+ if ($oFormNode->GetOptionalElement('fields') !== null)
+ {
+ $aFields['type'] = 'custom_list';
+ $aFields['fields'] = array();
+
+ foreach ($oFormNode->GetOptionalElement('fields')->GetNodes('field') as $oFieldNode)
+ {
+ $sFieldId = $oFieldNode->getAttribute('id');
+ if ($sFieldId !== '')
+ {
+ $aField = array();
+ // Parsing field options like read_only, hidden and mandatory
+ if ($oFieldNode->GetOptionalElement('read_only'))
+ {
+ $aField['readonly'] = ($oFieldNode->GetOptionalElement('read_only')->GetText('true') === 'true') ? true : false;
+ }
+ if ($oFieldNode->GetOptionalElement('mandatory'))
+ {
+ $aField['mandatory'] = ($oFieldNode->GetOptionalElement('mandatory')->GetText('true') === 'true') ? true : false;
+ }
+ if ($oFieldNode->GetOptionalElement('hidden'))
+ {
+ $aField['hidden'] = ($oFieldNode->GetOptionalElement('hidden')->GetText('true') === 'true') ? true : false;
+ }
+
+ $aFields['fields'][$sFieldId] = $aField;
+ }
+ else
+ {
+ throw new DOMFormatException('Field tag must have an id attribute', null, null,
+ $oFormNode);
+ }
+ }
+ }
+ // ... or the default zlist
+ else
+ {
+ $aFields['type'] = 'zlist';
+ $aFields['fields'] = 'details';
+ }
+
+ // Parsing presentation
+ if ($oFormNode->GetOptionalElement('twig') !== null)
+ {
+ // Extracting the twig template and removing the first and last lines (twig tags)
+ $sXml = $this->getModuleDesign()->saveXML($oFormNode->GetOptionalElement('twig'));
+ $sXml = preg_replace('/^.+\n/', '', $sXml);
+ $sXml = preg_replace('/\n.+$/', '', $sXml);
+
+ $aFields['layout'] = array(
+ 'type' => (preg_match('/\{\{|\{\#|\{\%/', $sXml) === 1) ? 'twig' : 'xhtml',
+ 'content' => $sXml
+ );
+ }
+
+ // Adding form for each class / mode
+ foreach ($aModes as $sMode)
+ {
+ // Initializing current class if necessary
+ if (!isset($aForms[$sFormClass]))
+ {
+ $aForms[$sFormClass] = array();
+ }
+
+ if ($sMode === 'apply_stimulus')
+ {
+ // Iterating over current class and child classes to fill stimuli forms
+ foreach (MetaModel::EnumChildClasses($sFormClass, ENUM_CHILD_CLASSES_ALL) as $sChildClass)
+ {
+ // Initializing child class if necessary
+ if (!isset($aForms[$sChildClass][$sMode]))
+ {
+ $aForms[$sChildClass][$sMode] = array();
+ }
+
+ // If stimuli are implicitly defined (empty tag), we define all those that have not already been by other forms.
+ $aChildStimuli = $aFormStimuli;
+ if (empty($aChildStimuli))
+ {
+ // Stimuli already declared
+ $aDeclaredStimuli = array();
+ if (array_key_exists($sChildClass, $aForms) && array_key_exists('apply_stimulus',
+ $aForms[$sChildClass]))
+ {
+ $aDeclaredStimuli = array_keys($aForms[$sChildClass]['apply_stimulus']);
+ }
+ // All stimuli
+ $aDatamodelStimuli = array_keys(MetaModel::EnumStimuli($sChildClass));
+ // Missing stimuli
+ $aChildStimuli = array_diff($aDatamodelStimuli, $aDeclaredStimuli);
+ }
+
+ foreach ($aChildStimuli as $sFormStimulus)
+ {
+ // Setting form if not defined OR if it was defined by a parent (abstract) class
+ if (!isset($aForms[$sChildClass][$sMode][$sFormStimulus]) || !empty($aFormStimuli))
+ {
+ $aForms[$sChildClass][$sMode][$sFormStimulus] = $aFields;
+ $aForms[$sChildClass][$sMode][$sFormStimulus]['id'] = 'apply_stimulus-'.$sChildClass.'-'.$sFormStimulus;
+ }
+ }
+ }
+ }
+ elseif (!isset($aForms[$sFormClass][$sMode]))
+ {
+ $aForms[$sFormClass][$sMode] = $aFields;
+ }
+ else
+ {
+ throw new \DOMFormatException('There is already a form for the class "'.$sFormClass.'" in "'.$sMode.'"',
+ null, null, $oFormNode);
+ }
+ }
+ }
+ else
+ {
+ throw new \DOMFormatException('Class tag must be defined', null, null, $oFormNode);
+ }
+ }
+ catch (\DOMFormatException $e)
+ {
+ throw new \Exception('Could not create from [id="'.$oFormNode->getAttribute('id').'"] from XML because of a DOM problem : '.$e->getMessage());
+ }
+ catch (\Exception $e)
+ {
+ throw new \Exception('Could not create from from XML : '.$oFormNode->Dump().' '.$e->getMessage());
+ }
+ }
+
+ $aPortalConf = $container->getParameter('combodo.portal.instance.conf');
+ $aPortalConf['forms'] = $aForms;
+ $container->setParameter('combodo.portal.instance.conf', $aPortalConf);
+ }
+
+
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Lists.php b/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Lists.php
new file mode 100644
index 000000000..893e056da
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Lists.php
@@ -0,0 +1,86 @@
+getModuleDesign()->GetNodes('/module_design/classes/class') as $oClassNode)
+ {
+ $aClassLists = array();
+ $sClassId = $oClassNode->getAttribute('id');
+ if ($sClassId === null)
+ {
+ throw new DOMFormatException('Class tag must have an id attribute', null, null, $oClassNode);
+ }
+
+ // - Each lists
+ foreach ($oClassNode->GetNodes('./lists/list') as $oListNode)
+ {
+ $aListItems = array();
+ $sListId = $oListNode->getAttribute('id');
+ if ($sListId === null)
+ {
+ throw new DOMFormatException('List tag of "'.$sClassId.'" class must have an id attribute', null,
+ null, $oListNode);
+ }
+
+ // - Each items
+ foreach ($oListNode->GetNodes('./items/item') as $oItemNode)
+ {
+ $sItemId = $oItemNode->getAttribute('id');
+ if ($sItemId === null)
+ {
+ throw new DOMFormatException('Item tag of "'.$sItemId.'" list must have an id attribute', null,
+ null, $oItemNode);
+ }
+
+ $aItem = array(
+ 'att_code' => $sItemId,
+ 'rank' => $iDefaultItemRank
+ );
+
+ $oRankNode = $oItemNode->GetOptionalElement('rank');
+ if ($oRankNode !== null)
+ {
+ $aItem['rank'] = $oRankNode->GetText($iDefaultItemRank);
+ }
+
+ $aListItems[] = $aItem;
+ }
+ // - Sorting list items by rank
+ usort($aListItems, function ($a, $b) {
+ return $a['rank'] > $b['rank'];
+ });
+ $aClassLists[$sListId] = $aListItems;
+ }
+
+ // - Adding class only if it has at least one list
+ if (!empty($aClassLists))
+ {
+ $aClassesLists[$sClassId] = $aClassLists;
+ }
+ }
+ $aPortalConf = $container->getParameter('combodo.portal.instance.conf');
+ $aPortalConf['lists'] = $aClassLists;
+ $container->setParameter('combodo.portal.instance.conf', $aPortalConf);
+ }
+
+
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/src/EventListener/ApplicationContextSetUrlMakerClass.php b/datamodels/2.x/itop-portal-base/portal/src/EventListener/ApplicationContextSetUrlMakerClass.php
new file mode 100644
index 000000000..58d991bff
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/EventListener/ApplicationContextSetUrlMakerClass.php
@@ -0,0 +1,56 @@
+aCombodoPortalInstanceConf = $oCombodoPortalInstanceConf->getVariable();
+ }
+
+ public function onKernelRequest(GetResponseEvent $oGetResponseEvent)
+ {
+ if ($this->aCombodoPortalInstanceConf['properties']['urlmaker_class'] !== null)
+ {
+ \ApplicationContext::SetUrlMakerClass($this->aCombodoPortalInstanceConf['properties']['urlmaker_class']);
+ }
+ }
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/src/EventListener/UserProvider.php b/datamodels/2.x/itop-portal-base/portal/src/EventListener/UserProvider.php
new file mode 100644
index 000000000..321621794
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/EventListener/UserProvider.php
@@ -0,0 +1,135 @@
+oModuleDesign = $oModuleDesign;
+ $this->oItopUser = $oItopUser;
+ $this->sCombodoPortalBaseAbsoluteUrl = $sCombodoPortalBaseAbsolutePath;
+ }
+
+ /**
+ * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $oGetResponseEvent
+ *
+ * @throws \Exception
+ */
+ public function onKernelRequest(GetResponseEvent $oGetResponseEvent)
+ {
+ // User pre-checks
+ // Note: At this point the Exception handler is not registered, so we can't use $oApp::abort() method, hence the die().
+ // - Checking user rights and prompt if needed (401 HTTP code returned if XHR request)
+ $iExitMethod = ($oGetResponseEvent->getRequest()->isXmlHttpRequest()) ? LoginWebPage::EXIT_RETURN : LoginWebPage::EXIT_PROMPT;
+ $iLogonRes = LoginWebPage::DoLoginEx(PORTAL_ID, false, $iExitMethod);
+ if( ($iExitMethod === LoginWebPage::EXIT_RETURN) && ($iLogonRes != 0) )
+ {
+ die(Dict::S('Portal:ErrorUserLoggedOut'));
+ }
+ // - User must be associated with a Contact
+ if (UserRights::GetContactId() == 0)
+ {
+ die(Dict::S('Portal:ErrorNoContactForThisUser'));
+ }
+
+ // User
+ $oUser = UserRights::GetUserObject();
+ if ($oUser === null)
+ {
+ throw new \Exception('Could not load connected user.');
+ }
+ $this->oItopUser->setUser($oUser);
+
+ // Contact
+ $sContactPhotoUrl = "{$this->sCombodoPortalBaseAbsoluteUrl}img/user-profile-default-256px.png";
+ // - Checking if we can load the contact
+ try
+ {
+ $oContact = UserRights::GetContactObject();
+ }
+ catch (Exception $e)
+ {
+ $oAllowedOrgSet = $oUser->Get('allowed_org_list');
+ if ($oAllowedOrgSet->Count() > 0)
+ {
+ throw new Exception('Could not load contact related to connected user. (Tip: Make sure the contact\'s organization is among the user\'s allowed organizations)');
+ }
+ else
+ {
+ throw new Exception('Could not load contact related to connected user.');
+ }
+ }
+ // - Retrieving picture
+ if ($oContact)
+ {
+ if (MetaModel::IsValidAttCode(get_class($oContact), 'picture'))
+ {
+ $oImage = $oContact->Get('picture');
+ if (is_object($oImage) && !$oImage->IsEmpty())
+ {
+ $sContactPhotoUrl = $oImage->GetDownloadURL(get_class($oContact), $oContact->GetKey(), 'picture');
+ }
+ else
+ {
+ $sContactPhotoUrl = MetaModel::GetAttributeDef(get_class($oContact), 'picture')->Get('default_image');
+ }
+ }
+ }
+ $this->oItopUser->setContactPhotoUrl($sContactPhotoUrl);
+ }
+
+
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Helper/ApplicationHelper.php b/datamodels/2.x/itop-portal-base/portal/src/Helper/ApplicationHelper.php
new file mode 100644
index 000000000..0ca0b8493
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/Helper/ApplicationHelper.php
@@ -0,0 +1,1432 @@
+
+ */
+class ApplicationHelper
+{
+ const FORM_ENUM_DISPLAY_MODE_COSY = 'cosy';
+ const FORM_ENUM_DISPLAY_MODE_COMPACT = 'compact';
+ const FORM_DEFAULT_DISPLAY_MODE = self::FORM_ENUM_DISPLAY_MODE_COSY;
+ const FORM_DEFAULT_ALWAYS_SHOW_SUBMIT = false;
+
+ /**
+ * Loads classes from the base portal
+ *
+ * @deprecated Since 2.7.0
+ *
+ * @param string $sScannedDir Directory to load the files from
+ * @param string $sFilePattern Pattern of files to load
+ * @param string $sType Type of files to load, used only in the Exception message, can be anything
+ *
+ * @throws \Exception
+ */
+ public static function LoadClasses($sScannedDir, $sFilePattern, $sType)
+ {
+ @trigger_error(
+ sprintf(
+ 'Usage of legacy LoadClasses is deprecated. You should rely on autoloading (and therefore follow PSR4).',
+ __FILE__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ // Loading classes from base portal
+ foreach (scandir($sScannedDir) as $sFile)
+ {
+ if (strpos($sFile, $sFilePattern) !== false && file_exists($sFilepath = $sScannedDir.'/'.$sFile))
+ {
+ try
+ {
+ require_once $sFilepath;
+ }
+ catch (Exception $e)
+ {
+ throw new Exception('Error while trying to load '.$sType.' '.$sFile);
+ }
+ }
+ }
+ }
+
+ /**
+ * Loads controllers from the base portal
+ *
+ * @param string $sScannedDir Directory to load the controllers from
+ *
+ * @throws \Exception
+ */
+// public static function LoadControllers($sScannedDir = null)
+// {
+// if ($sScannedDir === null)
+// {
+// $sScannedDir = __DIR__.'/../controllers';
+// }
+//
+// // Loading controllers from base portal (those from modules have already been loaded by module.xxx.php files)
+// self::LoadClasses($sScannedDir, 'controller.class.inc.php', 'controller');
+// }
+
+ /**
+ * Loads routers from the base portal
+ *
+ * @param string $sScannedDir Directory to load the routers from
+ *
+ * @throws \Exception
+ */
+// public static function LoadRouters($sScannedDir = null)
+// {
+// if ($sScannedDir === null)
+// {
+// $sScannedDir = __DIR__.'/../routers';
+// }
+//
+// // Loading routers from base portal (those from modules have already been loaded by module.xxx.php files)
+// self::LoadClasses($sScannedDir, 'router.class.inc.php', 'router');
+// }
+
+ /**
+ * Loads bricks from the base portal
+ *
+ * @param string $sScannedDir Directory to load the bricks from
+ *
+ * @throws \Exception
+ */
+// public static function LoadBricks($sScannedDir = null)
+// {
+// if ($sScannedDir === null)
+// {
+// $sScannedDir = __DIR__.'/../Brick';
+// }
+//
+// // Loading bricks from base portal (those from modules have already been loaded by module.xxx.php files)
+// self::LoadClasses($sScannedDir, 'brick.php', 'brick');
+// }
+
+ /**
+ * Loads form managers from the base portal
+ *
+ * @param string $sScannedDir Directory to load the managers from
+ *
+ * @throws \Exception
+ */
+// public static function LoadFormManagers($sScannedDir = null)
+// {
+// if ($sScannedDir === null)
+// {
+// $sScannedDir = __DIR__.'/../forms';
+// }
+//
+// // Loading form managers from base portal (those from modules have already been loaded by module.xxx.php files)
+// self::LoadClasses($sScannedDir, 'formmanager.class.inc.php', 'brick');
+// }
+
+ /**
+ * Registers routes in the Silex Application from all declared Router classes
+ *
+ * @param \Silex\Application $oApp
+ *
+ * @throws \Exception
+ */
+// public static function RegisterRoutes(Application $oApp)
+// {
+// $aAllRoutes = array();
+//
+// foreach (get_declared_classes() as $sPHPClass)
+// {
+// if (is_subclass_of($sPHPClass, 'Combodo\\iTop\\Portal\\Router\\AbstractRouter'))
+// {
+// try
+// {
+// // Registering to Silex Application
+// $sPHPClass::RegisterAllRoutes($oApp);
+//
+// // Registering them together so we can access them from everywhere
+// foreach ($sPHPClass::GetRoutes() as $aRoute)
+// {
+// $aAllRoutes[$aRoute['bind']] = $aRoute;
+// }
+// }
+// catch (Exception $e)
+// {
+// throw new Exception('Error while trying to register routes');
+// }
+// }
+// }
+//
+// $oApp['combodo.portal.instance.routes'] = $aAllRoutes;
+// }
+
+ /**
+ * Returns all registered routes for the current portal instance
+ *
+ * @param \Silex\Application $oApp
+ * @param boolean $bNamesOnly If set to true, function will return only the routes' names, not the objects
+ *
+ * @return array
+ */
+// public static function GetRoutes(Application $oApp, $bNamesOnly = false)
+// {
+// return ($bNamesOnly) ? array_keys($oApp['combodo.portal.instance.routes']) : $oApp['combodo.portal.instance.routes'];
+// }
+
+ /**
+ * Registers an exception handler that will intercept controllers exceptions and display them in a nice template.
+ * Note : It is only active when $oApp['debug'] is false
+ *
+ * @param Application $oApp
+ */
+ public static function RegisterExceptionHandler(Application $oApp)
+ {
+ // Intercepting fatal errors and exceptions
+ ErrorHandler::register();
+ ExceptionHandler::register(($oApp['debug'] === true));
+
+ // Intercepting manually aborted request
+ if (1 || !$oApp['debug'])
+ {
+ $oApp->error(function (Exception $oException /*, Request $oRequest*/) use ($oApp) {
+ $iErrorCode = ($oException instanceof HttpException) ? $oException->getStatusCode() : 500;
+
+ $aData = array(
+ 'exception' => $oException,
+ 'code' => $iErrorCode,
+ 'error_title' => '',
+ 'error_message' => $oException->getMessage()
+ );
+
+ switch ($iErrorCode)
+ {
+ case 404:
+ $aData['error_title'] = Dict::S('Error:HTTP:404');
+ break;
+ default:
+ $aData['error_title'] = Dict::S('Error:HTTP:500');
+ break;
+ }
+
+ IssueLog::Error($aData['error_title'].' : '.$aData['error_message']);
+
+ if ($oApp['request_stack']->getCurrentRequest()->isXmlHttpRequest())
+ {
+ $oResponse = $oApp->json($aData, $iErrorCode);
+ }
+ else
+ {
+ // Preparing debug trace
+ $aSteps = array();
+ foreach ($oException->getTrace() as $aStep)
+ {
+ // - Default file name
+ if (!isset($aStep['file']))
+ {
+ $aStep['file'] = '';
+ }
+ $aFileParts = explode('\\', $aStep['file']);
+ // - Default line number
+ if (!isset($aStep['line']))
+ {
+ $aStep['line'] = 'unknown';
+ }
+ // - Default class name
+ if (isset($aStep['class']) && isset($aStep['function']) && isset($aStep['type']))
+ {
+ $aClassParts = explode('\\', $aStep['class']);
+ $sClassName = $aClassParts[count($aClassParts) - 1];
+ $sClassFQ = $aStep['class'];
+
+ $aArgsAsString = array();
+ foreach ($aStep['args'] as $arg)
+ {
+ if (is_array($arg))
+ {
+ $aArgsAsString[] = 'array(...)';
+ }
+ elseif (is_object($arg))
+ {
+ $aArgsAsString[] = 'object('.get_class($arg).')';
+ }
+ else
+ {
+ $aArgsAsString[] = $arg;
+ }
+ }
+
+ $sFunctionCall = $sClassName.$aStep['type'].$aStep['function'].'('.implode(', ',
+ $aArgsAsString).')';
+ }
+ else
+ {
+ $sClassName = null;
+ $sClassFQ = null;
+ $sFunctionCall = null;
+ }
+
+ $aSteps[] = array(
+ 'file_fq' => $aStep['file'],
+ 'file_name' => $aFileParts[count($aFileParts) - 1],
+ 'line' => $aStep['line'],
+ 'class_name' => $sClassName,
+ 'class_fq' => $sClassFQ,
+ 'function_call' => $sFunctionCall,
+ );
+ }
+
+ $aData['debug_trace_steps'] = $aSteps;
+
+ $oResponse = $oApp['twig']->render('itop-portal-base/portal/templates/errors/layout.html.twig',
+ $aData);
+ }
+
+ return $oResponse;
+ });
+ }
+ }
+
+ /**
+ * Loads the portal instance configuration from its module design into the Silex application
+ *
+ * @param \Silex\Application $oApp
+ *
+ * @throws Exception
+ */
+// public static function LoadPortalConfiguration(Application $oApp)
+// {
+// try
+// {
+// // Loading file
+// if (!defined('PORTAL_ID'))
+// {
+// throw new Exception('Cannot load module design, Portal ID is not defined');
+// }
+// $oDesign = new ModuleDesign(PORTAL_ID);
+//
+// // Parsing file
+// // - Default values
+// $aPortalConf = array(
+// 'properties' => array(
+// 'id' => PORTAL_ID,
+// 'name' => 'Page:DefaultTitle',
+// 'logo' => (file_exists(MODULESROOT.'branding/portal-logo.png')) ? utils::GetAbsoluteUrlModulesRoot().'branding/portal-logo.png' : '../images/logo-itop-dark-bg.svg',
+// 'themes' => array(
+// 'bootstrap' => 'itop-portal-base/portal/web/css/bootstrap-theme-combodo.scss',
+// 'portal' => 'itop-portal-base/portal/web/css/portal.scss',
+// 'others' => array(),
+// ),
+// 'templates' => array(
+// 'layout' => 'itop-portal-base/portal/templates/layout.html.twig',
+// 'home' => 'itop-portal-base/portal/templates/home/layout.html.twig'
+// ),
+// 'urlmaker_class' => null,
+// 'triggers_query' => null,
+// 'attachments' => array(
+// 'allow_delete' => true
+// ),
+// 'allowed_portals' => array(
+// 'opening_mode' => null,
+// ),
+// ),
+// 'portals' => array(),
+// 'forms' => array(),
+// 'ui_extensions' => array(
+// 'css_files' => array(),
+// 'css_inline' => null,
+// 'js_files' => array(),
+// 'js_inline' => null,
+// 'html' => array(),
+// ),
+// 'bricks' => array(),
+// 'bricks_total_width' => 0,
+// );
+// // - Global portal properties
+// foreach ($oDesign->GetNodes('/module_design/properties/*') as $oPropertyNode)
+// {
+// switch ($oPropertyNode->nodeName)
+// {
+// case 'name':
+// case 'urlmaker_class':
+// case 'triggers_query':
+// $aPortalConf['properties'][$oPropertyNode->nodeName] = $oPropertyNode->GetText($aPortalConf['properties'][$oPropertyNode->nodeName]);
+// break;
+// case 'logo':
+// $aPortalConf['properties'][$oPropertyNode->nodeName] = $oPropertyNode->GetText($aPortalConf['properties'][$oPropertyNode->nodeName]);
+// break;
+// case 'themes':
+// case 'templates':
+// foreach ($oPropertyNode->GetNodes('template|theme') as $oSubNode)
+// {
+// if (!$oSubNode->hasAttribute('id') || $oSubNode->GetText(null) === null)
+// {
+// throw new DOMFormatException('Tag '.$oSubNode->nodeName.' must have a "id" attribute as well as a value',
+// null, null, $oSubNode);
+// }
+//
+// $sNodeId = $oSubNode->getAttribute('id');
+// switch ($oSubNode->nodeName)
+// {
+// case 'theme':
+// switch ($sNodeId)
+// {
+// case 'bootstrap':
+// case 'portal':
+// case 'custom':
+// $aPortalConf['properties']['themes'][$sNodeId] = $oSubNode->GetText(null);
+// break;
+// default:
+// $aPortalConf['properties']['themes']['others'][] = $oSubNode->GetText(null);
+// break;
+// }
+// break;
+// case 'template':
+// switch ($sNodeId)
+// {
+// case 'layout':
+// case 'home':
+// $aPortalConf['properties']['templates'][$sNodeId] = $oSubNode->GetText(null);
+// break;
+// default:
+// throw new DOMFormatException('Value "'.$sNodeId.'" is not handled for template[@id]',
+// null, null, $oSubNode);
+// break;
+// }
+// break;
+// }
+// }
+// break;
+// case 'attachments':
+// foreach ($oPropertyNode->GetNodes('*') as $oSubNode)
+// {
+// switch ($oSubNode->nodeName)
+// {
+// case 'allow_delete':
+// $sValue = $oSubNode->GetText();
+// // If the text is null, we keep the default value
+// // Else we set it
+// if ($sValue !== null)
+// {
+// $aPortalConf['properties']['attachments'][$oSubNode->nodeName] = ($sValue === 'true') ? true : false;
+// }
+// break;
+// }
+// }
+// break;
+// case 'allowed_portals':
+// foreach ($oPropertyNode->GetNodes('*') as $oSubNode)
+// {
+// switch ($oSubNode->nodeName)
+// {
+// case 'opening_mode':
+// $sValue = $oSubNode->GetText();
+// // If the text is null, we keep the default value
+// // Else we set it
+// if ($sValue !== null)
+// {
+// $aPortalConf['properties']['allowed_portals'][$oSubNode->nodeName] = ($sValue === 'self') ? 'self' : 'tab';
+// }
+// break;
+// }
+// }
+// break;
+// }
+// }
+// // - Rectifying portal logo url
+// $sLogoUri = $aPortalConf['properties']['logo'];
+// if (!preg_match('/^http/', $sLogoUri))
+// {
+// // We prefix it with the server base url
+// $sLogoUri = utils::GetAbsoluteUrlAppRoot().'env-'.utils::GetCurrentEnvironment().'/'.$sLogoUri;
+// }
+// $aPortalConf['properties']['logo'] = $sLogoUri;
+// // - User allowed portals
+// $aPortalConf['portals'] = UserRights::GetAllowedPortals();
+// // - Bricks
+// $aPortalConf = static::LoadBricksConfiguration($oApp, $oDesign) + $aPortalConf;
+// // - Forms
+// $aPortalConf['forms'] = static::LoadFormsConfiguration($oApp, $oDesign);
+// // - Scopes
+// static::LoadScopesConfiguration($oApp, $oDesign);
+// // - Lifecycle
+// static::LoadLifecycleConfiguration($oApp, $oDesign);
+// // - Presentation lists
+// $aPortalConf['lists'] = static::LoadListsConfiguration($oApp, $oDesign);
+// // - UI extensions
+// $aPortalConf['ui_extensions'] = static::LoadUIExtensions($oApp);
+// // - Action rules
+// static::LoadActionRulesConfiguration($oApp, $oDesign);
+// // - Setting UrlMakerClass
+// if ($aPortalConf['properties']['urlmaker_class'] !== null)
+// {
+// ApplicationContext::SetUrlMakerClass($aPortalConf['properties']['urlmaker_class']);
+// }
+// // - Generating CSS files
+// $aImportPaths = array($oApp['combodo.portal.base.absolute_path'].'css/');
+// foreach ($aPortalConf['properties']['themes'] as $key => $value)
+// {
+// if (!is_array($value))
+// {
+// $aPortalConf['properties']['themes'][$key] = $oApp['combodo.absolute_url'].utils::GetCSSFromSASS('env-'.utils::GetCurrentEnvironment().'/'.$value,
+// $aImportPaths);
+// }
+// else
+// {
+// $aValues = array();
+// foreach ($value as $sSubvalue)
+// {
+// $aValues[] = $oApp['combodo.absolute_url'].utils::GetCSSFromSASS('env-'.utils::GetCurrentEnvironment().'/'.$sSubvalue,
+// $aImportPaths);
+// }
+// $aPortalConf['properties']['themes'][$key] = $aValues;
+// }
+// }
+//
+// $oApp['combodo.portal.instance.conf'] = $aPortalConf;
+// }
+// catch (Exception $e)
+// {
+// throw new Exception('Error while parsing portal configuration file : '.$e->getMessage());
+// }
+// }
+
+ /**
+ * Loads the current user and stores it in the Silex application so we can use it wherever in the application
+ *
+ * @param \Silex\Application $oApp
+ *
+ * @throws \Exception
+ */
+// public static function LoadCurrentUser(Application $oApp)
+// {
+// // User
+// $oUser = UserRights::GetUserObject();
+// if ($oUser === null)
+// {
+// throw new Exception('Could not load connected user.');
+// }
+//
+// $oApp['combodo.current_user'] = $oUser;
+//
+// // Contact
+// $sContactPhotoUrl = $oApp['combodo.portal.base.absolute_url'].'img/user-profile-default-256px.png';
+// // - Checking if we can load the contact
+// try
+// {
+// $oContact = UserRights::GetContactObject();
+// }
+// catch (Exception $e)
+// {
+// $oAllowedOrgSet = $oUser->Get('allowed_org_list');
+// if ($oAllowedOrgSet->Count() > 0)
+// {
+// throw new Exception('Could not load contact related to connected user. (Tip: Make sure the contact\'s organization is among the user\'s allowed organizations)');
+// }
+// else
+// {
+// throw new Exception('Could not load contact related to connected user.');
+// }
+// }
+// // - Retrieving picture
+// if ($oContact)
+// {
+// if (MetaModel::IsValidAttCode(get_class($oContact), 'picture'))
+// {
+// $oImage = $oContact->Get('picture');
+// if (is_object($oImage) && !$oImage->IsEmpty())
+// {
+// $sContactPhotoUrl = $oImage->GetDownloadURL(get_class($oContact), $oContact->GetKey(), 'picture');
+// }
+// else
+// {
+// $sContactPhotoUrl = MetaModel::GetAttributeDef(get_class($oContact),
+// 'picture')->Get('default_image');
+// }
+// }
+// }
+// $oApp['combodo.current_contact.photo_url'] = $sContactPhotoUrl;
+// }
+
+ /**
+ * Loads the brick's security from the OQL queries to profiles arrays
+ *
+ * @param \Combodo\iTop\Portal\Brick\AbstractBrick $oBrick
+ *
+ * @throws \Exception
+ */
+ public static function LoadBrickSecurity(AbstractBrick $oBrick)
+ {
+ try
+ {
+ // Allowed profiles
+ if ($oBrick->GetAllowedProfilesOql() !== null && $oBrick->GetAllowedProfilesOql() !== '')
+ {
+ $oSearch = DBObjectSearch::FromOQL($oBrick->GetAllowedProfilesOql());
+ $oSet = new DBObjectSet($oSearch);
+ while ($oProfile = $oSet->Fetch())
+ {
+ $oBrick->AddAllowedProfile($oProfile->Get('name'));
+ }
+ }
+
+ // Denied profiles
+ if ($oBrick->GetDeniedProfilesOql() !== null && $oBrick->GetDeniedProfilesOql() !== '')
+ {
+ $oSearch = DBObjectSearch::FromOQL($oBrick->GetDeniedProfilesOql());
+ $oSet = new DBObjectSet($oSearch);
+ while ($oProfile = $oSet->Fetch())
+ {
+ $oBrick->AddDeniedProfile($oProfile->Get('name'));
+ }
+ }
+ }
+ catch (Exception $e)
+ {
+ throw new Exception('Error while loading security from '.$oBrick->GetId().' brick');
+ }
+ }
+
+ /**
+ * Finds an AbstractBrick loaded in the $oApp instance configuration from its ID.
+ *
+ * @param \Silex\Application $oApp
+ * @param string $sBrickId
+ *
+ * @return \Combodo\iTop\Portal\Brick\PortalBrick
+ *
+ * @throws \Exception
+ */
+// public static function GetLoadedBrickFromId(Application $oApp, $sBrickId)
+// {
+// foreach ($oApp['combodo.portal.instance.conf']['bricks'] as $oBrick)
+// {
+// if ($oBrick->GetId() === $sBrickId)
+// {
+// return $oBrick;
+// }
+// }
+//
+// throw new Exception('Brick with id = "'.$sBrickId.'" was not found among loaded bricks.');
+// }
+
+ /**
+ * Return the form properties for the $sClassname in $sMode.
+ *
+ * If not found, tries to find one from the closest parent class.
+ * Else returns a default form based on zlist 'details'
+ *
+ * @param Application $oApp
+ * @param string $sClass Object class to find a form for
+ * @param string $sMode Form mode to find (view|edit|create)
+ *
+ * @return array
+ *
+ * @throws \CoreException
+ */
+ public static function GetLoadedFormFromClass($aForms, $sClass, $sMode)
+ {
+
+ // We try to find the form for that class
+ if (isset($aForms[$sClass]) && isset($aForms[$sClass][$sMode]))
+ {
+ $aForm = $aForms[$sClass][$sMode];
+ }
+ // If not found, we try find one from the closest parent class
+ else
+ {
+ $bFound = false;
+ foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false) as $sParentClass)
+ {
+ if (isset($aForms[$sParentClass]) && isset($aForms[$sParentClass][$sMode]))
+ {
+ $aForm = $aForms[$sParentClass][$sMode];
+ $bFound = true;
+ break;
+ }
+ }
+
+ // If we have still not found one, we return a default form
+ if (!$bFound)
+ {
+ $aForm = static::GenerateDefaultFormForClass($sClass);
+ }
+ }
+
+ return $aForm;
+ }
+
+ /**
+ * Return the attribute list for the $sClassname in $sList.
+ *
+ * If not found, tries to find one from the closest parent class.
+ * Else returns a default attribute list based on zlist 'list'
+ *
+ * @param array $combodoPortalInstanceConf
+ * @param string $sClass Object class to find a list for
+ * @param string $sList List name to find
+ *
+ * @return array Array of attribute codes
+ *
+ * @throws \CoreException
+ */
+ public static function GetLoadedListFromClass($combodoPortalInstanceConf, $sClass, $sList = 'default')
+ {
+ $aLists = $combodoPortalInstanceConf['lists'];
+ $aList = null;
+ $aAttCodes = array();
+
+ // We try to find the list for that class
+ if (isset($aLists[$sClass]) && isset($aLists[$sClass][$sList]))
+ {
+ $aList = $aLists[$sClass][$sList];
+ }
+ // Else we try to found the default list for that class
+ elseif (isset($aLists[$sClass]) && isset($aLists[$sClass]['default']))
+ {
+ $aList = $aLists[$sClass]['default'];
+ }
+ // If not found, we try find one from the closest parent class
+ else
+ {
+ foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass)
+ {
+ // Trying to find the right list
+ if (isset($aLists[$sParentClass]) && isset($aLists[$sParentClass][$sList]))
+ {
+ $aList = $aLists[$sParentClass][$sList];
+ break;
+ }
+ // Or the default list
+ elseif (isset($aLists[$sParentClass]) && isset($aLists[$sParentClass]['default']))
+ {
+ $aList = $aLists[$sParentClass]['default'];
+ break;
+ }
+ }
+ }
+
+ // If found, we flatten the list to keep only the attribute codes (not the rank)
+ if ($aList !== null)
+ {
+ foreach ($aList as $aItem)
+ {
+ $aAttCodes[] = $aItem['att_code'];
+ }
+ }
+ else
+ {
+ $aAttCodes = MetaModel::FlattenZList(MetaModel::GetZListItems($sClass, 'list'));
+ }
+
+ return $aAttCodes;
+ }
+
+ /**
+ * Loads the bricks configuration from the module design XML and returns it as an hash array containing :
+ * - 'brick' => array of PortalBrick objects
+ * - 'bricks_total_width' => an integer used to create the home page grid
+ *
+ * @param \Silex\Application $oApp
+ * @param \ModuleDesign $oDesign
+ *
+ * @return array
+ *
+ * @throws \Exception
+ */
+// protected static function LoadBricksConfiguration(Application $oApp, ModuleDesign $oDesign)
+// {
+// $aPortalConf = array(
+// 'bricks' => array(),
+// 'bricks_total_width' => 0,
+// 'bricks_home_count' => 0,
+// 'bricks_navigation_menu_count' => 0
+// );
+//
+// foreach ($oDesign->GetNodes('/module_design/bricks/brick') as $oBrickNode)
+// {
+// $sBrickClass = $oBrickNode->getAttribute('xsi:type');
+// try
+// {
+// if (class_exists($sBrickClass))
+// {
+// /** @var \Combodo\iTop\Portal\Brick\PortalBrick $oBrick */
+// $oBrick = new $sBrickClass();
+// $oBrick->LoadFromXml($oBrickNode);
+// static::LoadBrickSecurity($oBrick);
+//
+// // Checking brick security
+// if ($oBrick->GetActive() && $oBrick->IsGrantedForProfiles(UserRights::ListProfiles()))
+// {
+// $aPortalConf['bricks'][] = $oBrick;
+// $aPortalConf['bricks_total_width'] += $oBrick->GetWidth();
+// if ($oBrick->GetVisibleHome())
+// {
+// $aPortalConf['bricks_home_count']++;
+// }
+// if ($oBrick->GetVisibleNavigationMenu())
+// {
+// $aPortalConf['bricks_navigation_menu_count']++;
+// }
+// }
+// }
+// else
+// {
+// throw new DOMFormatException('Unknown brick class "'.$sBrickClass.'" from xsi:type attribute', null,
+// null, $oBrickNode);
+// }
+// }
+// catch (DOMFormatException $e)
+// {
+// throw new Exception('Could not create brick ('.$sBrickClass.') from XML because of a DOM problem : '.$e->getMessage());
+// }
+// catch (Exception $e)
+// {
+// throw new Exception('Could not create brick ('.$sBrickClass.') from XML : '.$oBrickNode->Dump().' '.$e->getMessage());
+// }
+// }
+// // - Sorting bricks by rank
+// $aPortalConf['bricks_ordering'] = array();
+// // - Home
+// $aPortalConf['bricks_ordering']['home'] = $aPortalConf['bricks'];
+// usort($aPortalConf['bricks_ordering']['home'], function (PortalBrick $a, PortalBrick $b) {
+// return $a->GetRankHome() > $b->GetRankHome();
+// });
+// // - Navigation menu
+// $aPortalConf['bricks_ordering']['navigation_menu'] = $aPortalConf['bricks'];
+// usort($aPortalConf['bricks_ordering']['navigation_menu'], function (PortalBrick $a, PortalBrick $b) {
+// return $a->GetRankNavigationMenu() > $b->GetRankNavigationMenu();
+// });
+//
+// return $aPortalConf;
+// }
+
+ /**
+ * Loads the forms configuration from the module design XML and returns it as an array containing :
+ * - => array(
+ * 'view'|'edit'|'create' => array(
+ * 'fields_type' => 'custom_list'|'twig'|'zlist',
+ * 'fields' =>
+ * ),
+ * ...
+ * ),
+ * ...
+ *
+ * @param \Silex\Application $oApp
+ * @param \ModuleDesign $oDesign
+ *
+ * @return array
+ *
+ * @throws \Exception
+ */
+// protected static function LoadFormsConfiguration(Application $oApp, ModuleDesign $oDesign)
+// {
+// $aForms = array();
+//
+// foreach ($oDesign->GetNodes('/module_design/forms/form') as $oFormNode)
+// {
+// try
+// {
+// // Parsing form id
+// if ($oFormNode->getAttribute('id') === '')
+// {
+// throw new DOMFormatException('form tag must have an id attribute', null, null, $oFormNode);
+// }
+//
+// // Parsing form object class
+// if ($oFormNode->GetUniqueElement('class')->GetText() !== null)
+// {
+// // Parsing class
+// $sFormClass = $oFormNode->GetUniqueElement('class')->GetText();
+//
+// // Parsing properties
+// $aFormProperties = array(
+// 'display_mode' => static::FORM_DEFAULT_DISPLAY_MODE,
+// 'always_show_submit' => static::FORM_DEFAULT_ALWAYS_SHOW_SUBMIT,
+// );
+// if ($oFormNode->GetOptionalElement('properties') !== null)
+// {
+// foreach ($oFormNode->GetOptionalElement('properties')->childNodes as $oPropertyNode)
+// {
+// switch ($oPropertyNode->nodeName)
+// {
+// case 'display_mode':
+// $aFormProperties['display_mode'] = $oPropertyNode->GetText(static::FORM_DEFAULT_DISPLAY_MODE);
+// break;
+// case 'always_show_submit':
+// $aFormProperties['always_show_submit'] = ($oPropertyNode->GetText('false') === 'true') ? true : false;
+// break;
+// }
+// }
+// }
+//
+// // Parsing availables modes for that form (view, edit, create, apply_stimulus)
+// $aFormStimuli = array();
+// if (($oFormNode->GetOptionalElement('modes') !== null) && ($oFormNode->GetOptionalElement('modes')->GetNodes('mode')->length > 0))
+// {
+// $aModes = array();
+// foreach ($oFormNode->GetOptionalElement('modes')->GetNodes('mode') as $oModeNode)
+// {
+// if ($oModeNode->getAttribute('id') !== '')
+// {
+// $aModes[] = $oModeNode->getAttribute('id');
+// }
+// else
+// {
+// throw new DOMFormatException('Mode tag must have an id attribute', null, null,
+// $oFormNode);
+// }
+//
+// // If apply_stimulus mode, checking if stimuli are defined
+// if ($oModeNode->getAttribute('id') === 'apply_stimulus')
+// {
+// $oStimuliNode = $oModeNode->GetOptionalElement('stimuli');
+// // if stimuli are defined, we overwrite the form that could have been set by the generic form
+// if ($oStimuliNode !== null)
+// {
+// foreach ($oStimuliNode->GetNodes('stimulus') as $oStimulusNode)
+// {
+// $sStimulusCode = $oStimulusNode->getAttribute('id');
+//
+// // Removing default form is present (in case the default forms were parsed before the current one (from current or parent class))
+// if (isset($aForms[$sFormClass]['apply_stimulus'][$sStimulusCode]))
+// {
+// unset($aForms[$sFormClass]['apply_stimulus'][$sStimulusCode]);
+// }
+//
+// $aFormStimuli[] = $oStimulusNode->getAttribute('id');
+// }
+// }
+// }
+// }
+// }
+// else
+// {
+// // If no mode was specified, we set it all but stimuli as it would have no sense that every transition forms
+// // have as many fields displayed as a regular edit form for example.
+// $aModes = array('view', 'edit', 'create');
+// }
+//
+// // Parsing fields
+// $aFields = array(
+// 'id' => $oFormNode->getAttribute('id'),
+// 'type' => null,
+// 'properties' => $aFormProperties,
+// 'fields' => null,
+// 'layout' => null
+// );
+// // ... either enumerated fields ...
+// if ($oFormNode->GetOptionalElement('fields') !== null)
+// {
+// $aFields['type'] = 'custom_list';
+// $aFields['fields'] = array();
+//
+// foreach ($oFormNode->GetOptionalElement('fields')->GetNodes('field') as $oFieldNode)
+// {
+// $sFieldId = $oFieldNode->getAttribute('id');
+// if ($sFieldId !== '')
+// {
+// $aField = array();
+// // Parsing field options like read_only, hidden and mandatory
+// if ($oFieldNode->GetOptionalElement('read_only'))
+// {
+// $aField['readonly'] = ($oFieldNode->GetOptionalElement('read_only')->GetText('true') === 'true') ? true : false;
+// }
+// if ($oFieldNode->GetOptionalElement('mandatory'))
+// {
+// $aField['mandatory'] = ($oFieldNode->GetOptionalElement('mandatory')->GetText('true') === 'true') ? true : false;
+// }
+// if ($oFieldNode->GetOptionalElement('hidden'))
+// {
+// $aField['hidden'] = ($oFieldNode->GetOptionalElement('hidden')->GetText('true') === 'true') ? true : false;
+// }
+//
+// $aFields['fields'][$sFieldId] = $aField;
+// }
+// else
+// {
+// throw new DOMFormatException('Field tag must have an id attribute', null, null,
+// $oFormNode);
+// }
+// }
+// }
+// // ... or the default zlist
+// else
+// {
+// $aFields['type'] = 'zlist';
+// $aFields['fields'] = 'details';
+// }
+//
+// // Parsing presentation
+// if ($oFormNode->GetOptionalElement('twig') !== null)
+// {
+// // Extracting the twig template and removing the first and last lines (twig tags)
+// $sXml = $oDesign->saveXML($oFormNode->GetOptionalElement('twig'));
+// $sXml = preg_replace('/^.+\n/', '', $sXml);
+// $sXml = preg_replace('/\n.+$/', '', $sXml);
+//
+// $aFields['layout'] = array(
+// 'type' => (preg_match('/\{\{|\{\#|\{\%/', $sXml) === 1) ? 'twig' : 'xhtml',
+// 'content' => $sXml
+// );
+// }
+//
+// // Adding form for each class / mode
+// foreach ($aModes as $sMode)
+// {
+// // Initializing current class if necessary
+// if (!isset($aForms[$sFormClass]))
+// {
+// $aForms[$sFormClass] = array();
+// }
+//
+// if ($sMode === 'apply_stimulus')
+// {
+// // Iterating over current class and child classes to fill stimuli forms
+// foreach (MetaModel::EnumChildClasses($sFormClass, ENUM_CHILD_CLASSES_ALL) as $sChildClass)
+// {
+// // Initializing child class if necessary
+// if (!isset($aForms[$sChildClass][$sMode]))
+// {
+// $aForms[$sChildClass][$sMode] = array();
+// }
+//
+// // If stimuli are implicitly defined (empty tag), we define all those that have not already been by other forms.
+// $aChildStimuli = $aFormStimuli;
+// if (empty($aChildStimuli))
+// {
+// // Stimuli already declared
+// $aDeclaredStimuli = array();
+// if (array_key_exists($sChildClass, $aForms) && array_key_exists('apply_stimulus',
+// $aForms[$sChildClass]))
+// {
+// $aDeclaredStimuli = array_keys($aForms[$sChildClass]['apply_stimulus']);
+// }
+// // All stimuli
+// $aDatamodelStimuli = array_keys(MetaModel::EnumStimuli($sChildClass));
+// // Missing stimuli
+// $aChildStimuli = array_diff($aDatamodelStimuli, $aDeclaredStimuli);
+// }
+//
+// foreach ($aChildStimuli as $sFormStimulus)
+// {
+// // Setting form if not defined OR if it was defined by a parent (abstract) class
+// if (!isset($aForms[$sChildClass][$sMode][$sFormStimulus]) || !empty($aFormStimuli))
+// {
+// $aForms[$sChildClass][$sMode][$sFormStimulus] = $aFields;
+// $aForms[$sChildClass][$sMode][$sFormStimulus]['id'] = 'apply_stimulus-'.$sChildClass.'-'.$sFormStimulus;
+// }
+// }
+// }
+// }
+// elseif (!isset($aForms[$sFormClass][$sMode]))
+// {
+// $aForms[$sFormClass][$sMode] = $aFields;
+// }
+// else
+// {
+// throw new DOMFormatException('There is already a form for the class "'.$sFormClass.'" in "'.$sMode.'"',
+// null, null, $oFormNode);
+// }
+// }
+// }
+// else
+// {
+// throw new DOMFormatException('Class tag must be defined', null, null, $oFormNode);
+// }
+// }
+// catch (DOMFormatException $e)
+// {
+// throw new Exception('Could not create from [id="'.$oFormNode->getAttribute('id').'"] from XML because of a DOM problem : '.$e->getMessage());
+// }
+// catch (Exception $e)
+// {
+// throw new Exception('Could not create from from XML : '.$oFormNode->Dump().' '.$e->getMessage());
+// }
+// }
+//
+// return $aForms;
+// }
+
+ /**
+ * Loads the scopes configuration from the module design XML
+ *
+ * @param \Silex\Application $oApp
+ * @param ModuleDesign $oDesign
+ */
+// public static function LoadScopesConfiguration(Application $oApp, ModuleDesign $oDesign)
+// {
+// $oApp['scope_validator']->Init($oDesign->GetNodes('/module_design/classes/class'));
+// }
+
+ /**
+ * Loads the lifecycle configuration from the module design XML
+ *
+ * @param \Silex\Application $oApp
+ * @param ModuleDesign $oDesign
+ */
+// protected static function LoadLifecycleConfiguration(Application $oApp, ModuleDesign $oDesign)
+// {
+// $oApp['lifecycle_validator']->Init($oDesign->GetNodes('/module_design/classes/class'));
+// }
+
+ /**
+ * Loads the context helper from the module design XML
+ *
+ * @param \Silex\Application $oApp
+ * @param ModuleDesign $oDesign
+ */
+// protected static function LoadActionRulesConfiguration(Application $oApp, ModuleDesign $oDesign)
+// {
+// $oApp['context_manipulator']->Init($oDesign->GetNodes('/module_design/action_rules/action_rule'));
+// }
+
+ /**
+ * Loads the classes lists from the module design XML. They are mainly used when searching an external key but
+ * could be used more extensively later
+ *
+ * @param \Silex\Application $oApp
+ * @param ModuleDesign $oDesign
+ *
+ * @return array
+ * @throws \DOMFormatException
+ */
+// protected static function LoadListsConfiguration(Application $oApp, ModuleDesign $oDesign)
+// {
+// $iDefaultItemRank = 0;
+// $aClassesLists = array();
+//
+// // Parsing XML file
+// // - Each classes
+// foreach ($oDesign->GetNodes('/module_design/classes/class') as $oClassNode)
+// {
+// $aClassLists = array();
+// $sClassId = $oClassNode->getAttribute('id');
+// if ($sClassId === null)
+// {
+// throw new DOMFormatException('Class tag must have an id attribute', null, null, $oClassNode);
+// }
+//
+// // - Each lists
+// foreach ($oClassNode->GetNodes('./lists/list') as $oListNode)
+// {
+// $aListItems = array();
+// $sListId = $oListNode->getAttribute('id');
+// if ($sListId === null)
+// {
+// throw new DOMFormatException('List tag of "'.$sClassId.'" class must have an id attribute', null,
+// null, $oListNode);
+// }
+//
+// // - Each items
+// foreach ($oListNode->GetNodes('./items/item') as $oItemNode)
+// {
+// $sItemId = $oItemNode->getAttribute('id');
+// if ($sItemId === null)
+// {
+// throw new DOMFormatException('Item tag of "'.$sItemId.'" list must have an id attribute', null,
+// null, $oItemNode);
+// }
+//
+// $aItem = array(
+// 'att_code' => $sItemId,
+// 'rank' => $iDefaultItemRank
+// );
+//
+// $oRankNode = $oItemNode->GetOptionalElement('rank');
+// if ($oRankNode !== null)
+// {
+// $aItem['rank'] = $oRankNode->GetText($iDefaultItemRank);
+// }
+//
+// $aListItems[] = $aItem;
+// }
+// // - Sorting list items by rank
+// usort($aListItems, function ($a, $b) {
+// return $a['rank'] > $b['rank'];
+// });
+// $aClassLists[$sListId] = $aListItems;
+// }
+//
+// // - Adding class only if it has at least one list
+// if (!empty($aClassLists))
+// {
+// $aClassesLists[$sClassId] = $aClassLists;
+// }
+// }
+//
+// return $aClassesLists;
+// }
+
+ /**
+ * Loads portal UI extensions
+ *
+ * @param \Silex\Application $oApp
+ *
+ * @return array
+ */
+// protected static function LoadUIExtensions(Application $oApp)
+// {
+// $aUIExtensions = array(
+// 'css_files' => array(),
+// 'css_inline' => null,
+// 'js_files' => array(),
+// 'js_inline' => null,
+// 'html' => array(),
+// );
+// $aUIExtensionHooks = array(
+// iPortalUIExtension::ENUM_PORTAL_EXT_UI_BODY,
+// iPortalUIExtension::ENUM_PORTAL_EXT_UI_NAVIGATION_MENU,
+// iPortalUIExtension::ENUM_PORTAL_EXT_UI_MAIN_CONTENT,
+// );
+//
+// /** @var iPortalUIExtension $oExtensionInstance */
+// foreach (MetaModel::EnumPlugins('iPortalUIExtension') as $oExtensionInstance)
+// {
+// // Adding CSS files
+// $aImportPaths = array($oApp['combodo.portal.base.absolute_path'].'css/');
+// foreach($oExtensionInstance->GetCSSFiles($oApp) as $sCSSFile)
+// {
+// // Removing app root url as we need to pass a path on the file system (relative to app root)
+// $sCSSFilePath = str_replace(utils::GetAbsoluteUrlAppRoot(), '', $sCSSFile);
+// // Compiling SCSS file
+// $sCSSFileCompiled = $oApp['combodo.absolute_url'].utils::GetCSSFromSASS($sCSSFilePath,
+// $aImportPaths);
+//
+// if(!in_array($sCSSFileCompiled, $aUIExtensions['css_files']))
+// {
+// $aUIExtensions['css_files'][] = $sCSSFileCompiled;
+// }
+// }
+//
+// // Adding CSS inline
+// $sCSSInline = $oExtensionInstance->GetCSSInline($oApp);
+// if ($sCSSInline !== null)
+// {
+// $aUIExtensions['css_inline'] .= "\n\n".$sCSSInline;
+// }
+//
+// // Adding JS files
+// $aUIExtensions['js_files'] = array_merge($aUIExtensions['js_files'],
+// $oExtensionInstance->GetJSFiles($oApp));
+//
+// // Adding JS inline
+// $sJSInline = $oExtensionInstance->GetJSInline($oApp);
+// if ($sJSInline !== null)
+// {
+// // Note: Semi-colon is to prevent previous script that would have omitted it.
+// $aUIExtensions['js_inline'] .= "\n\n;\n".$sJSInline;
+// }
+//
+// // Adding HTML for each hook
+// foreach ($aUIExtensionHooks as $sUIExtensionHook)
+// {
+// $sFunctionName = 'Get'.$sUIExtensionHook.'HTML';
+// $sHTML = $oExtensionInstance->$sFunctionName($oApp);
+// if ($sHTML !== null)
+// {
+// if (!array_key_exists($sUIExtensionHook, $aUIExtensions['html']))
+// {
+// $aUIExtensions['html'][$sUIExtensionHook] = '';
+// }
+// $aUIExtensions['html'][$sUIExtensionHook] .= "\n\n".$sHTML;
+// }
+// }
+// }
+//
+// return $aUIExtensions;
+// }
+
+// public static function LoadSessionMessages(Application $oApp)
+// {
+// $aAllMessages = array();
+// if ((array_key_exists('obj_messages', $_SESSION)) && (!empty($_SESSION['obj_messages'])))
+// {
+// foreach ($_SESSION['obj_messages'] as $sMessageKey => $aMessageObjectData)
+// {
+// $aObjectMessages = array();
+// $aRanks = array();
+// foreach ($aMessageObjectData as $sMessageId => $aMessageData)
+// {
+// $sMsgClass = 'alert alert-';
+// switch ($aMessageData['severity'])
+// {
+// case 'info':
+// $sMsgClass .= 'info';
+// break;
+// case 'error':
+// $sMsgClass .= 'danger';
+// break;
+// case 'ok':
+// default:
+// $sMsgClass .= 'success';
+// break;
+// }
+// $aObjectMessages[] = array('cssClass' => $sMsgClass, 'message' => $aMessageData['message']);
+// $aRanks[] = $aMessageData['rank'];
+// }
+// unset($_SESSION['obj_messages'][$sMessageKey]);
+// array_multisort($aRanks, $aObjectMessages);
+// foreach ($aObjectMessages as $aObjectMessage)
+// {
+// $aAllMessages[] = $aObjectMessage;
+// }
+// }
+// }
+// $oApp['combodo.current_user.session_messages'] = $aAllMessages;
+// }
+
+ /**
+ * Generate the form data for the $sClass.
+ * Form will look like the "Properties" tab of a $sClass object in the console.
+ *
+ * @param string $sClass
+ * @param bool $bAddLinksets
+ *
+ * @return array
+ */
+ protected static function GenerateDefaultFormForClass($sClass, $bAddLinksets = false)
+ {
+ $aForm = array(
+ 'id' => strtolower($sClass)."-default-".uniqid(),
+ 'type' => 'custom_list',
+ 'properties' => array(
+ 'display_mode' => static::FORM_DEFAULT_DISPLAY_MODE,
+ 'always_show_submit' => static::FORM_DEFAULT_ALWAYS_SHOW_SUBMIT,
+ ),
+ 'fields' => array(),
+ 'layout' => array(
+ 'type' => 'xhtml',
+ 'content' => '',
+ ),
+ );
+
+ // Generate layout
+ $sContent = "";
+
+ // - Retrieve zlist details
+ $aDetailsList = MetaModel::GetZListItems($sClass, 'details');
+ $aDetailsStruct = cmdbAbstractObject::ProcessZlist($aDetailsList, array(), 'UI:PropertiesTab', 'col1', '');
+ $aPropertiesStruct = $aDetailsStruct['UI:PropertiesTab'];
+
+ // Count cols (not linksets)
+ $iColCount = 0;
+ foreach($aPropertiesStruct as $sColId => $aColFieldsets)
+ {
+ if(substr($sColId, 0, 1) !== '_')
+ {
+ foreach($aColFieldsets as $sFieldsetName => $aAttCodes)
+ {
+ if(substr($sFieldsetName, 0, 1) !== '_')
+ {
+ $iColCount++;
+ break;
+ }
+ }
+ }
+ }
+ // If no cols, return a default form with all fields one after another
+ if($iColCount === 0)
+ {
+ return array(
+ 'id' => 'default',
+ 'type' => 'zlist',
+ 'fields' => 'details',
+ 'layout' => null
+ );
+ }
+ // Warning, this might not be great when 12 modulo $iColCount is greater than 0.
+ $sColCSSClass = 'col-sm-'.floor(12/$iColCount);
+
+ $sLinksetsHTML = "";
+ $sRowHTML = "\n";
+ foreach($aPropertiesStruct as $sColId => $aColFieldsets)
+ {
+ $sColsHTML = "\t
\n";
+ foreach($aColFieldsets as $sFieldsetName => $aAttCodes)
+ {
+ // Add fieldset, not linkset
+ if(substr($sFieldsetName, 0, 1) !== '_')
+ {
+ $sFieldsetHTML = "\t\t
\n";
+ $sFieldsetHTML .= "\t\t\t".htmlentities(Dict::S($sFieldsetName), ENT_QUOTES, 'UTF-8')." \n";
+
+ foreach($aAttCodes as $sAttCode)
+ {
+ $sFieldsetHTML .= "\t\t\t
\n";
+ }
+
+ $sFieldsetHTML .= "\t\t \n";
+
+ // Add to col
+ $sColsHTML .= $sFieldsetHTML;
+ }
+ elseif($bAddLinksets)
+ {
+ foreach($aAttCodes as $sAttCode)
+ {
+ $sLinksetsHTML .= "
\n";
+ }
+ }
+ }
+ $sColsHTML .= "\t
\n";
+
+ // Add col to row
+ $sRowHTML .= $sColsHTML;
+ }
+ $sRowHTML .= "
\n";
+
+ // Add row to twig
+ $sContent .= $sRowHTML;
+ // Add linksets to twig
+ $sContent .= $sLinksetsHTML;
+
+ $aForm['layout']['content'] = $sContent;
+
+ return $aForm;
+ }
+
+}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/helpers/contextmanipulatorhelper.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/Helper/ContextManipulatorHelper.php
similarity index 83%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/helpers/contextmanipulatorhelper.class.inc.php
rename to datamodels/2.x/itop-portal-base/portal/src/Helper/ContextManipulatorHelper.php
index 4b1b41d7f..63291d43c 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/helpers/contextmanipulatorhelper.class.inc.php
+++ b/datamodels/2.x/itop-portal-base/portal/src/Helper/ContextManipulatorHelper.php
@@ -1,28 +1,33 @@
+/**
+ * Copyright (C) 2013-2019 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
+ *
+ *
+ */
namespace Combodo\iTop\Portal\Helper;
+use ModuleDesign;
+use Combodo\iTop\Portal\Brick\BrickCollection;
use Exception;
-use Silex\Application;
use DOMNodeList;
use DOMFormatException;
+use Symfony\Component\Routing\RouterInterface;
use UserRights;
use DBObject;
use DBSearch;
@@ -40,13 +45,36 @@ class ContextManipulatorHelper
const ENUM_RULE_CALLBACK_OPEN_EDIT = 'edit';
const DEFAULT_RULE_CALLBACK_OPEN = self::ENUM_RULE_CALLBACK_OPEN_VIEW;
- protected $oApp;
protected $aRules;
- public function __construct()
+ /** @var \Symfony\Component\Routing\RouterInterface */
+ private $oRouter;
+ /** @var \Combodo\iTop\Portal\Brick\BrickCollection */
+ private $oBrickCollection;
+ /**
+ * @var \Combodo\iTop\Portal\Helper\ScopeValidatorHelper
+ */
+ private $oScopeValidator;
+
+ /**
+ * ContextManipulatorHelper constructor.
+ *
+ * @param \ModuleDesign $oModuleDesign
+ * @param \Symfony\Component\Routing\RouterInterface $oRouter
+ * @param \Combodo\iTop\Portal\Brick\BrickCollection $oBrickCollection
+ * @param \Combodo\iTop\Portal\Helper\ScopeValidatorHelper $oScopeValidator
+ *
+ * @throws \DOMFormatException
+ */
+ public function __construct(ModuleDesign $oModuleDesign, RouterInterface $oRouter, BrickCollection $oBrickCollection, ScopeValidatorHelper $oScopeValidator)
{
$this->aRules = array();
- }
+ $this->oRouter = $oRouter;
+ $this->oBrickCollection = $oBrickCollection;
+
+ $this->Init($oModuleDesign->GetNodes('/module_design/action_rules/action_rule'));
+ $this->oScopeValidator = $oScopeValidator;
+ }
/**
* Initializes the ScopeValidator by generating and caching the scopes compilation in the $this->sCachePath.$this->sFilename file.
@@ -181,10 +209,6 @@ class ContextManipulatorHelper
}
}
- public function SetApp($oApp)
- {
- $this->oApp = $oApp;
- }
/**
* Returns a hash array of rules
@@ -302,7 +326,7 @@ class ContextManipulatorHelper
}
// Checking for silos
- $oScopeSearch = $this->oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sSearchClass, UR_ACTION_READ);
+ $oScopeSearch = $this->oScopeValidator->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sSearchClass, UR_ACTION_READ);
if ($oScopeSearch->IsAllDataAllowed())
{
$oSearch->AllowAllData();
@@ -349,7 +373,6 @@ class ContextManipulatorHelper
* 'cancel' => null
* );
*
- * @param \Silex\Application $oApp
* @param array $aData
* @param \DBObject $oObject
* @param boolean $bModal
@@ -358,7 +381,7 @@ class ContextManipulatorHelper
*
* @throws \Exception
*/
- public function GetCallbackUrls(Application $oApp, array $aData, DBObject $oObject, $bModal = false)
+ public function GetCallbackUrls(array $aData, DBObject $oObject, $bModal = false)
{
$aResults = array(
'submit' => null,
@@ -389,12 +412,12 @@ class ContextManipulatorHelper
break;
case static::ENUM_RULE_CALLBACK_GOTO:
- $oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $aRule[$sCallbackName]['brick_id']);
- $sCallbackUrl = $oApp['url_generator']->generate($oBrick->GetRouteName(), array('sBrickId' => $oBrick->GetId()));
+ $oBrick = $this->oBrickCollection->getBrickById($aRule[$sCallbackName]['brick_id']);
+ $sCallbackUrl = $this->oRouter->generate($oBrick->GetRouteName(), array('sBrickId' => $oBrick->GetId()));
break;
case static::ENUM_RULE_CALLBACK_OPEN:
- $sCallbackUrl = ($oObject->IsNew()) ? null : $oApp['url_generator']->generate('p_object_' . $aRule[$sCallbackName]['mode'], array('sObjectClass' => get_class($oObject), 'sObjectId' => $oObject->GetKey()));
+ $sCallbackUrl = ($oObject->IsNew()) ? null : $this->oRouter->generate('p_object_' . $aRule[$sCallbackName]['mode'], array('sObjectClass' => get_class($oObject), 'sObjectId' => $oObject->GetKey()));
break;
}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/helpers/lifecyclevalidatorhelper.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/Helper/LifecycleValidatorHelper.php
similarity index 92%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/helpers/lifecyclevalidatorhelper.class.inc.php
rename to datamodels/2.x/itop-portal-base/portal/src/Helper/LifecycleValidatorHelper.php
index 4f00efbff..cad1e0352 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/helpers/lifecyclevalidatorhelper.class.inc.php
+++ b/datamodels/2.x/itop-portal-base/portal/src/Helper/LifecycleValidatorHelper.php
@@ -1,21 +1,24 @@
+/**
+ * Copyright (C) 2013-2019 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
+ *
+ *
+ */
namespace Combodo\iTop\Portal\Helper;
@@ -36,13 +39,15 @@ class LifecycleValidatorHelper
protected $sGeneratedClass;
protected $aProfilesMatrix;
- public function __construct($sFilename, $sCachePath = null)
+ public function __construct(\ModuleDesign $moduleDesign, $sPortalId, $sPortalCachePath = null)
{
- $this->sFilename = $sFilename;
- $this->sCachePath = $sCachePath;
- $this->sInstancePrefix = '';
+ $this->sFilename = "{$sPortalId}.lifecycle.php";
+ $this->sCachePath = $sPortalCachePath;
+ $this->sInstancePrefix = "{$sPortalId}-";;
$this->sGeneratedClass = static::DEFAULT_GENERATED_CLASS;
$this->aProfilesMatrix = array();
+
+ $this->Init($moduleDesign->GetNodes('/module_design/classes/class'));
}
/**
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/helpers/requestmanipulatorhelper.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/Helper/RequestManipulatorHelper.php
similarity index 95%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/helpers/requestmanipulatorhelper.class.inc.php
rename to datamodels/2.x/itop-portal-base/portal/src/Helper/RequestManipulatorHelper.php
index ffac1c943..9867de83c 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/helpers/requestmanipulatorhelper.class.inc.php
+++ b/datamodels/2.x/itop-portal-base/portal/src/Helper/RequestManipulatorHelper.php
@@ -1,7 +1,7 @@
+ *
+ *
*/
namespace Combodo\iTop\Portal\Helper;
@@ -41,7 +42,7 @@ class RequestManipulatorHelper
*
* @param \Symfony\Component\HttpFoundation\RequestStack $oRequestStack
*/
- public function __construct(RequestStack &$oRequestStack)
+ public function __construct(RequestStack $oRequestStack)
{
$this->oRequestStack = $oRequestStack;
}
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Helper/ScopeValidatorHelper.php b/datamodels/2.x/itop-portal-base/portal/src/Helper/ScopeValidatorHelper.php
new file mode 100644
index 000000000..efa81d445
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/Helper/ScopeValidatorHelper.php
@@ -0,0 +1,626 @@
+sFilename = "{$sPortalId}.scopes.php";
+ $this->sCachePath = $sPortalCachePath;
+ $this->sInstancePrefix = "{$sPortalId}-";
+ $this->sGeneratedClass = static::DEFAULT_GENERATED_CLASS;
+ $this->aProfilesMatrix = array();
+
+ $this->Init($moduleDesign->GetNodes('/module_design/classes/class'));
+ }
+
+ /**
+ * Initializes the ScopeValidator by generating and caching the scopes compilation in the $this->sCachePath.$this->sFilename file.
+ *
+ * @param DOMNodeList $oNodes
+ * @throws DOMFormatException
+ * @throws Exception
+ */
+ public function Init(DOMNodeList $oNodes)
+ {
+ // Checking cache path
+ if ($this->sCachePath === null)
+ {
+ $this->sCachePath = utils::GetCachePath();
+ }
+ // Building full pathname for file
+ $sFilePath = $this->sCachePath . $this->sFilename;
+
+ // Creating file if not existing
+ // Note : This is a temporary cache system, it should soon evolve to a cache provider (fs, apc, memcache, ...)
+ if (!file_exists($sFilePath))
+ {
+ $this->InitGenerateAndWriteCache($oNodes, $sFilePath);
+ }
+
+ if (!class_exists($this->sGeneratedClass))
+ {
+ require_once $this->sCachePath . $this->sFilename;
+ }
+ }
+
+ public static function EnumTypeValues()
+ {
+ return array(static::ENUM_TYPE_ALLOW, static::ENUM_TYPE_RESTRICT);
+ }
+
+ /**
+ * Returns the path where to cache the compiled scopes file
+ *
+ * @return string
+ */
+ public function GetCachePath()
+ {
+ return $this->sCachePath;
+ }
+
+ /**
+ * Returns the name of the compiled scopes file
+ *
+ * @return string
+ */
+ public function GetFilename()
+ {
+ return $this->sFilename;
+ }
+
+ /**
+ * Returns the instance prefix used for the generated scopes class name
+ *
+ * @return string
+ */
+ public function GetInstancePrefix()
+ {
+ return $this->sInstancePrefix;
+ }
+
+ /**
+ * Returns the name of the generated scopes class
+ *
+ * @return string
+ */
+ public function GetGeneratedClass()
+ {
+ return $this->sGeneratedClass;
+ }
+
+ /**
+ * Sets the scope validator instance prefix.
+ *
+ * This is used to create a unique scope values class in the cache directory (/data/cache-) as there can be several instance of the portal.
+ *
+ * @param string $sInstancePrefix
+ * @return \Combodo\iTop\Portal\Helper\ScopeValidatorHelper
+ */
+ public function SetInstancePrefix($sInstancePrefix)
+ {
+ $sInstancePrefix = preg_replace('/[-_]/', ' ', $sInstancePrefix);
+ $sInstancePrefix = ucwords($sInstancePrefix);
+ $sInstancePrefix = str_replace(' ', '', $sInstancePrefix);
+
+ $this->sInstancePrefix = $sInstancePrefix;
+ $this->sGeneratedClass = $this->sInstancePrefix . static::DEFAULT_GENERATED_CLASS;
+
+ return $this;
+ }
+
+ /**
+ * Returns the DBSearch for the $sProfile in $iAction for the class $sClass
+ *
+ * @param string $sProfile
+ * @param string $sClass
+ * @param integer $iAction
+ *
+ * @return \DBSearch
+ *
+ * @throws \Exception
+ * @throws \CoreException
+ * @throws \OQLException
+ */
+ public function GetScopeFilterForProfile($sProfile, $sClass, $iAction = null)
+ {
+ return $this->GetScopeFilterForProfiles(array($sProfile), $sClass, $iAction);
+ }
+
+ /**
+ * Returns the DBSearch for the $aProfiles in $iAction for the class $sClass.
+ * Profiles are a OR condition.
+ *
+ * @param array $aProfiles
+ * @param string $sClass
+ * @param integer $iAction
+ *
+ * @return \DBSearch
+ *
+ * @throws \Exception
+ * @throws \CoreException
+ * @throws \OQLException
+ */
+ public function GetScopeFilterForProfiles($aProfiles, $sClass, $iAction = null)
+ {
+ $oSearch = null;
+ $aAllowSearches = array();
+ $aRestrictSearches = array();
+ $bIgnoreSilos = static::DEFAULT_IGNORE_SILOS;
+
+ // Checking the default mode
+ if ($iAction === null)
+ {
+ $iAction = UR_ACTION_READ;
+ }
+
+ // Iterating on profiles to retrieving the different OQLs parts
+ foreach ($aProfiles as $sProfile)
+ {
+ // Retrieving matrix informtions
+ $iProfileId = $this->GetProfileIdFromProfileName($sProfile);
+ $sMode = ($iAction === UR_ACTION_READ) ? static::ENUM_MODE_READ : static::ENUM_MODE_WRITE;
+
+ // Retrieving profile OQLs
+ $sScopeValuesClass = $this->sGeneratedClass;
+ $aProfileMatrix = $sScopeValuesClass::GetProfileScope($iProfileId, $sClass, $sMode);
+ if ($aProfileMatrix !== null)
+ {
+ if (isset($aProfileMatrix['allow']) && $aProfileMatrix['allow'] !== null)
+ {
+ $aAllowSearches[] = DBSearch::FromOQL($aProfileMatrix['allow']);
+ }
+ if (isset($aProfileMatrix['restrict']) && $aProfileMatrix['restrict'] !== null)
+ {
+ $aRestrictSearches[] = DBSearch::FromOQL($aProfileMatrix['restrict']);
+ }
+ // If a profile should ignore allowed org, we set it for all its queries no matter the profile
+ if (isset($aProfileMatrix['ignore_silos']) && $aProfileMatrix['ignore_silos'] === true)
+ {
+ $bIgnoreSilos = true;
+ }
+ }
+ }
+
+ // Building the real OQL from all the parts from the differents profiles
+ for ($i = 0; $i < count($aAllowSearches); $i++)
+ {
+ foreach ($aRestrictSearches as $oRestrictSearch)
+ {
+ $aAllowSearches[$i] = $aAllowSearches[$i]->Intersect($oRestrictSearch);
+ }
+ }
+ if (count($aAllowSearches) > 0)
+ {
+ $oSearch = new DBUnionSearch($aAllowSearches);
+ $oSearch = $oSearch->RemoveDuplicateQueries();
+ }
+ if ($bIgnoreSilos === true)
+ {
+ $oSearch->AllowAllData();
+ }
+
+ return $oSearch;
+ }
+
+ /**
+ * Add the scope query (view or edit depending on $sAction) for $sClass to the $oQuery.
+ *
+ * @param \DBSearch $oQuery
+ * @param string $sClass
+ * @param int $sAction
+ *
+ * @return bool true if scope exists, false if scope is null
+ *
+ * @throws \CoreException
+ * @throws \OQLException
+ */
+ public function AddScopeToQuery(DBSearch &$oQuery, $sClass, $sAction = UR_ACTION_READ)
+ {
+ $oScopeQuery = $this->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sClass, $sAction);
+ if ($oScopeQuery !== null)
+ {
+ $oQuery = $oQuery->Intersect($oScopeQuery);
+ // - Allowing all data if necessary
+ if ($oScopeQuery->IsAllDataAllowed())
+ {
+ $oQuery->AllowAllData();
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if at least one of the $aProfiles has the ignore_silos flag set to true for the $sClass.
+ *
+ * @param array $aProfiles
+ * @param string $sClass
+ *
+ * @return boolean
+ *
+ * @throws \Exception
+ */
+ public function IsAllDataAllowedForScope($aProfiles, $sClass)
+ {
+ $bIgnoreSilos = false;
+
+ // Iterating on profiles to retrieving the different OQLs parts
+ foreach ($aProfiles as $sProfile)
+ {
+ // Retrieving matrix informtions
+ $iProfileId = $this->GetProfileIdFromProfileName($sProfile);
+
+ // Retrieving profile OQLs
+ $sScopeValuesClass = $this->sGeneratedClass;
+ $aProfileMatrix = $sScopeValuesClass::GetProfileScope($iProfileId, $sClass, static::ENUM_MODE_READ);
+ if ($aProfileMatrix !== null)
+ {
+ // If a profile should ignore allowed org, we set it for all its queries no matter the profile
+ if (isset($aProfileMatrix['ignore_silos']) && $aProfileMatrix['ignore_silos'] === true)
+ {
+ $bIgnoreSilos = true;
+ }
+ }
+ }
+
+ return $bIgnoreSilos;
+ }
+
+ /**
+ * Returns the profile id from a string being either a constant or its name.
+ *
+ * @param string $sProfile
+ *
+ * @return integer
+ *
+ * @throws \Exception
+ */
+ protected function GetProfileIdFromProfileName($sProfile)
+ {
+ $iProfileId = null;
+
+ // We try to find the profile from its name in order to retrieve it's id
+ // - If the regular UserRights addon is installed we check the profiles array
+ if (class_exists('ProfilesConfig'))
+ {
+ if (defined($sProfile) && in_array($sProfile, ProfilesConfig::GetProfilesValues()))
+ {
+ $iProfileId = constant($sProfile);
+ }
+ else
+ {
+ foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
+ {
+ if ($aValue['name'] === $sProfile)
+ {
+ $iProfileId = $iKey;
+ break;
+ }
+ }
+ }
+ }
+ // - Else, we can't find the id from the name as we don't know the used UserRights addon. It has to be a constant
+ else
+ {
+ throw new Exception('Scope validator : Unknown UserRights addon, scope\'s profile must be a constant');
+ }
+
+ // If profile was not found from its name or from a constant, we throw an exception
+ if ($iProfileId === null)
+ {
+ throw new Exception('Scope validator : Could not find "' . $sProfile . '" in the profiles list');
+ }
+
+ return $iProfileId;
+ }
+
+ /**
+ * Returns a string containing the generated PHP class for the compiled scopes
+ *
+ * @param array $aProfiles
+ * @return string
+ */
+ protected function BuildPHPClass($aProfiles = array())
+ {
+ $sProfiles = var_export($aProfiles, true);
+ $sClassName = ltrim($this->sGeneratedClass, '\\');
+ $sPHP = <<getAttribute('id');
+ if ($sClass === '') {
+ throw new DOMFormatException('Class tag must have an id attribute.', null, null, $oClassNode);
+ }
+
+ // Iterating over scope nodes of the class
+ $oScopesNode = $oClassNode->GetOptionalElement('scopes');
+ if ($oScopesNode !== null) {
+ foreach ($oScopesNode->GetNodes('./scope') as $oScopeNode) {
+ // Retrieving mandatory scope id attribute
+ $sScopeId = $oScopeNode->getAttribute('id');
+ if ($sScopeId === '') {
+ throw new DOMFormatException('Scope tag must have an id attribute.', null, null, $oScopeNode);
+ }
+
+ // Retrieving the type of query
+ // Note : This has been disabled as we don't want deny rules for now
+ // $oOqlViewTypeNode = $oClassNode->GetOptionalElement('oql_view_type');
+ // $sOqlViewType = ($oOqlViewTypeNode !== null && ($oOqlViewTypeNode->GetText() === static::ENUM_TYPE_RESTRICT)) ? static::ENUM_TYPE_RESTRICT : static::ENUM_TYPE_ALLOW;
+ $sOqlViewType = static::ENUM_TYPE_ALLOW;
+ // Retrieving the view query
+ $oOqlViewNode = $oScopeNode->GetUniqueElement('oql_view');
+ $sOqlView = $oOqlViewNode->GetText();
+ if ($sOqlView === null) {
+ throw new DOMFormatException(
+ 'Scope tag in class must have a not empty oql_view tag',
+ null,
+ null,
+ $oScopeNode
+ );
+ }
+ // Retrieving the edit query
+ $oOqlEditNode = $oScopeNode->GetOptionalElement('oql_edit');
+ $sOqlEdit = (($oOqlEditNode !== null) && ($oOqlEditNode->GetText(
+ ) !== null)) ? $oOqlEditNode->GetText() : null;
+ // Retrieving ignore allowed org flag
+ $oIgnoreSilosNode = $oScopeNode->GetOptionalElement('ignore_silos');
+ $bIgnoreSilos = (($oIgnoreSilosNode !== null) && ($oIgnoreSilosNode->GetText(
+ ) === 'true')) ? true : static::DEFAULT_IGNORE_SILOS;
+
+ // Retrieving profiles for the scope
+ $oProfilesNode = $oScopeNode->GetOptionalElement('allowed_profiles');
+ $aProfilesNames = array();
+ // If no profile is specified, we consider that it's for ALL the profiles
+ if (($oProfilesNode === null) || ($oProfilesNode->GetNodes('./allowed_profile')->length === 0)) {
+ foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue) {
+ $aProfilesNames[] = $aValue['name'];
+ }
+ } else {
+ foreach ($oProfilesNode->GetNodes('./allowed_profile') as $oProfileNode) {
+ // Retrieving mandatory profile id attribute
+ $sProfileId = $oProfileNode->getAttribute('id');
+ if ($sProfileId === '') {
+ throw new DOMFormatException(
+ 'Scope tag must have an id attribute.',
+ null,
+ null,
+ $oProfileNode
+ );
+ }
+ $aProfilesNames[] = $sProfileId;
+ }
+ }
+
+ //
+ foreach ($aProfilesNames as $sProfileName) {
+ // Scope profile id
+ $iProfileId = $this->GetProfileIdFromProfileName($sProfileName);
+
+ // Now that we have the queries infos, we are going to build the queries for that profile / class
+ $sMatrixPrefix = $iProfileId.'_'.$sClass.'_';
+ // - View query
+ $oViewFilter = DBSearch::FromOQL($sOqlView);
+ // ... We have to union the query if this profile has another scope for that class
+ if (array_key_exists($sMatrixPrefix.static::ENUM_MODE_READ, $aProfiles) && array_key_exists(
+ $sOqlViewType,
+ $aProfiles[$sMatrixPrefix.static::ENUM_MODE_READ]
+ )) {
+ $oExistingFilter = DBSearch::FromOQL(
+ $aProfiles[$sMatrixPrefix.static::ENUM_MODE_READ][$sOqlViewType]
+ );
+ $aFilters = array($oExistingFilter, $oViewFilter);
+ $oResFilter = new DBUnionSearch($aFilters);
+
+ // Applying ignore_silos flag on result filter if necessary (As the union will remove it if it is not on all sub-queries)
+ if ($aProfiles[$sMatrixPrefix.static::ENUM_MODE_READ]['ignore_silos'] === true) {
+ $bIgnoreSilos = true;
+ }
+ } else {
+ $oResFilter = $oViewFilter;
+ }
+ $aProfiles[$sMatrixPrefix.static::ENUM_MODE_READ] = array(
+ $sOqlViewType => $oResFilter->ToOQL(),
+ 'ignore_silos' => $bIgnoreSilos
+ );
+ // - Edit query
+ if ($sOqlEdit !== null) {
+ $oEditFilter = DBSearch::FromOQL($sOqlEdit);
+ // - If the queries are the same, we don't make an intersect, we just reuse the view query
+ if ($sOqlEdit === $sOqlView) {
+ // Do not intersect, edit query is identical to view query
+ } else {
+ if (($oEditFilter->GetClass() === $oViewFilter->GetClass()) && $oEditFilter->IsAny()) {
+ $oEditFilter = $oViewFilter;
+ // Do not intersect, edit query is identical to view query
+ } else {
+ // Intersect
+ $oEditFilter = $oViewFilter->Intersect($oEditFilter);
+ }
+ }
+
+ // ... We have to union the query if this profile has another scope for that class
+ if (array_key_exists(
+ $sMatrixPrefix.static::ENUM_MODE_WRITE,
+ $aProfiles
+ ) && array_key_exists(
+ $sOqlViewType,
+ $aProfiles[$sMatrixPrefix.static::ENUM_MODE_WRITE]
+ )) {
+ $oExistingFilter = DBSearch::FromOQL(
+ $aProfiles[$sMatrixPrefix.static::ENUM_MODE_WRITE][$sOqlViewType]
+ );
+ $aFilters = array($oExistingFilter, $oEditFilter);
+ $oResFilter = new DBUnionSearch($aFilters);
+ } else {
+ $oResFilter = $oEditFilter;
+ }
+ $aProfiles[$sMatrixPrefix.static::ENUM_MODE_WRITE] = array(
+ $sOqlViewType => $oResFilter->ToOQL(),
+ 'ignore_silos' => $bIgnoreSilos
+ );
+ }
+ }
+ }
+
+ $aProfileClasses[] = $sClass;
+ }
+ }
+
+ // Filling the array with missing classes from MetaModel, so we can have an inheritance principle on the scope
+ // For each class explicitly given in the scopes, we check if its child classes were also in the scope :
+ // If not, we add them with the same OQL
+ foreach ($aProfileClasses as $sProfileClass) {
+ foreach (MetaModel::EnumChildClasses($sProfileClass) as $sChildClass) {
+ // If the child class is not in the scope, we are going to try to add it
+ if (!in_array($sChildClass, $aProfileClasses)) {
+ foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue) {
+ $iProfileId = $iKey;
+ foreach (array(static::ENUM_MODE_READ, static::ENUM_MODE_WRITE) as $sAction) {
+ // If the current profile has scope for that class in that mode, we duplicate it
+ if (isset($aProfiles[$iProfileId.'_'.$sProfileClass.'_'.$sAction])) {
+ $aTmpProfile = $aProfiles[$iProfileId.'_'.$sProfileClass.'_'.$sAction];
+ foreach ($aTmpProfile as $sType => $sOql) {
+ // IF condition is just to skip the 'ignore_silos' flag
+ if (in_array($sType, array(static::ENUM_TYPE_ALLOW, static::ENUM_TYPE_RESTRICT))) {
+ $oTmpFilter = DBSearch::FromOQL($sOql);
+ $oTmpFilter->ChangeClass($sChildClass);
+
+ $aTmpProfile[$sType] = $oTmpFilter->ToOQL();
+ }
+ }
+
+ $aProfiles[$iProfileId.'_'.$sChildClass.'_'.$sAction] = $aTmpProfile;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // - Build php class
+ $sPHP = $this->BuildPHPClass($aProfiles);
+
+ // - Write file on disk
+ // - Creating dir if necessary
+ if (!is_dir($this->sCachePath)) {
+ mkdir($this->sCachePath, 0777, true);
+ }
+ // -- Then creating the file
+ $ret = file_put_contents($sFilePath, $sPHP);
+ if ($ret === false) {
+ $iLen = strlen($sPHP);
+ $fFree = @disk_free_space(dirname($sFilePath));
+ $aErr = error_get_last();
+ throw new Exception(
+ "Failed to write '$sFilePath'. Last error: '{$aErr['message']}', content to write: $iLen bytes, available free space on disk: $fFree."
+ );
+ }
+ }
+
+}
+
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Helper/SecurityHelper.php b/datamodels/2.x/itop-portal-base/portal/src/Helper/SecurityHelper.php
new file mode 100644
index 000000000..b54724f8b
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/Helper/SecurityHelper.php
@@ -0,0 +1,297 @@
+
+
+namespace Combodo\iTop\Portal\Helper;
+
+use Silex\Application;
+use UserRights;
+use IssueLog;
+use MetaModel;
+use DBSearch;
+use DBObjectSearch;
+use DBObjectSet;
+use FieldExpression;
+use VariableExpression;
+use BinaryExpression;
+
+/**
+ * SecurityHelper class
+ *
+ * Handle security checks through the different layers (portal scopes, iTop silos, user rights)
+ *
+ * @author Guillaume Lajarige
+ */
+class SecurityHelper
+{
+ public static $aAllowedScopeObjectsCache = array(
+ UR_ACTION_READ => array(),
+ UR_ACTION_MODIFY => array(),
+ );
+
+ /**
+ * Returns true if the current user is allowed to do the $sAction on an $sObjectClass object (with optionnal $sObjectId id)
+ * Checks are:
+ * - Has a scope query for the $sObjectClass / $sAction
+ * - Optionally, if $sObjectId provided: Is object within scope for $sObjectClass / $sObjectId / $sAction
+ * - Is allowed by datamodel for $sObjectClass / $sAction
+ *
+ * @param ScopeValidatorHelper $scopeValidator
+ * @param boolean $isDebugEnabled
+ * @param string $sAction Must be in UR_ACTION_READ|UR_ACTION_MODIFY|UR_ACTION_CREATE
+ * @param string $sObjectClass
+ * @param string $sObjectId
+ *
+ * @return boolean
+ *
+ * @throws \CoreException
+ * @throws \MissingQueryArgument
+ * @throws \MySQLException
+ * @throws \MySQLHasGoneAwayException
+ * @throws \OQLException
+ */
+ public static function IsActionAllowed(ScopeValidatorHelper $scopeValidator, $isDebugEnabled, $sAction, $sObjectClass, $sObjectId = null)
+ {
+ $sDebugTracePrefix = __CLASS__ . ' / ' . __METHOD__ . ' : Returned false for action ' . $sAction . ' on ' . $sObjectClass . '::' . $sObjectId;
+
+ // Checking action type
+ if (!in_array($sAction, array(UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_CREATE)))
+ {
+ if ($isDebugEnabled)
+ {
+ IssueLog::Info($sDebugTracePrefix . ' as the action value could not be understood (' . UR_ACTION_READ . '/' . UR_ACTION_MODIFY . '/' . UR_ACTION_CREATE . ' expected');
+ }
+ return false;
+ }
+
+ // Checking the scopes layer
+ // - Transforming scope action as there is only 2 values
+ $sScopeAction = ($sAction === UR_ACTION_READ) ? UR_ACTION_READ : UR_ACTION_MODIFY;
+ // - Retrieving the query. If user has no scope, it can't access that kind of objects
+ $oScopeQuery = $scopeValidator->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sObjectClass, $sScopeAction);
+ if ($oScopeQuery === null)
+ {
+ if ($isDebugEnabled)
+ {
+ IssueLog::Info($sDebugTracePrefix . ' as there was no scope defined for action ' . $sScopeAction . ' and profiles ' . implode('/', UserRights::ListProfiles()));
+ }
+ return false;
+ }
+ // - If action != create we do some additionnal checks
+ if ($sAction !== UR_ACTION_CREATE)
+ {
+ // - Checking specific object if id is specified
+ if ($sObjectId !== null)
+ {
+ // Checking if object status is in cache (to avoid unnecessary query)
+ if(isset(static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass][$sObjectId]) )
+ {
+ if(static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass][$sObjectId] === false)
+ {
+ if ($isDebugEnabled)
+ {
+ IssueLog::Info($sDebugTracePrefix . ' as it was denied in the scope objects cache');
+ }
+ return false;
+ }
+ }
+ else
+ {
+ // Modifying query to filter on the ID
+ // - Adding expression
+ $sObjectKeyAtt = MetaModel::DBGetKey($sObjectClass);
+ $oFieldExp = new FieldExpression($sObjectKeyAtt, $oScopeQuery->GetClassAlias());
+ $oBinExp = new BinaryExpression($oFieldExp, '=', new VariableExpression('object_id'));
+ $oScopeQuery->AddConditionExpression($oBinExp);
+ // - Setting value
+ $aQueryParams = $oScopeQuery->GetInternalParams();
+ $aQueryParams['object_id'] = $sObjectId;
+ $oScopeQuery->SetInternalParams($aQueryParams);
+ unset($aQueryParams);
+
+ // - Checking if query result is null (which means that the user has no right to view this specific object)
+ $oSet = new DBObjectSet($oScopeQuery);
+ if ($oSet->Count() === 0)
+ {
+ // Updating cache
+ static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass][$sObjectId] = false;
+
+ if ($isDebugEnabled)
+ {
+ IssueLog::Info($sDebugTracePrefix . ' as there was no result for the following scope query : ' . $oScopeQuery->ToOQL(true));
+ }
+ return false;
+ }
+
+ // Updating cache
+ static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass][$sObjectId] = true;
+ }
+ }
+ }
+
+ // Checking reading security layer. The object could be listed, check if it is actually allowed to view it
+ if (UserRights::IsActionAllowed($sObjectClass, $sAction) == UR_ALLOWED_NO)
+ {
+ // For security reasons, we don't want to give the user too many informations on why he cannot access the object.
+ //throw new SecurityException('User not allowed to view this object', array('class' => $sObjectClass, 'id' => $sObjectId));
+ if ($isDebugEnabled)
+ {
+ IssueLog::Info($sDebugTracePrefix . ' as the user is not allowed to access this object according to the datamodel security (cf. Console settings)');
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param LifecycleValidatorHelper $lifecycleValidator
+ * @param $sStimulusCode
+ * @param $sObjectClass
+ * @param null $oInstanceSet
+ *
+ * @return bool
+ * @throws \Exception
+ */
+ public static function IsStimulusAllowed(LifecycleValidatorHelper $lifecycleValidator, $sStimulusCode, $sObjectClass, $oInstanceSet = null)
+ {
+ // Checking DataModel layer
+ $aStimuliFromDatamodel = Metamodel::EnumStimuli($sObjectClass);
+ $iActionAllowed = (get_class($aStimuliFromDatamodel[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sObjectClass, $sStimulusCode, $oInstanceSet) : UR_ALLOWED_NO;
+ if( ($iActionAllowed === false) || ($iActionAllowed === UR_ALLOWED_NO) )
+ {
+ return false;
+ }
+
+ // Checking portal security layer
+ $aStimuliFromPortal = $lifecycleValidator->GetStimuliForProfiles(UserRights::ListProfiles(), $sObjectClass);
+ if(!in_array($sStimulusCode, $aStimuliFromPortal))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Preloads scope objects cache with objects from $oQuery
+ *
+ * @param ScopeValidatorHelper $scopeValidator
+ * @param \DBSearch $oSearch
+ * @param array $aExtKeysToPreload
+ *
+ * @throws \CoreException
+ * @throws \CoreUnexpectedValue
+ * @throws \MySQLException
+ * @throws \OQLException
+ */
+ public static function PreloadForCache(ScopeValidatorHelper $scopeValidator, DBSearch $oSearch, $aExtKeysToPreload = null)
+ {
+ $sObjectClass = $oSearch->GetClass();
+ $aObjectIds = array();
+ $aExtKeysIds = array();
+ $aColumnsToLoad = array();
+
+ if($aExtKeysToPreload !== null)
+ {
+ foreach($aExtKeysToPreload as $sAttCode)
+ {
+ /** @var \AttributeDefinition $oAttDef */
+ $oAttDef = MetaModel::GetAttributeDef($sObjectClass, $sAttCode);
+ if($oAttDef->IsExternalKey())
+ {
+ $aExtKeysIds[$oAttDef->GetTargetClass()] = array();
+ $aColumnsToLoad[] = $sAttCode;
+ }
+ }
+ }
+
+ // Retrieving IDs of all objects
+ // Note: We have to clone $oSet otherwise the source object will be modified
+ $oSet = new DBObjectSet($oSearch);
+ $oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => $aColumnsToLoad));
+ while($oCurrentRow = $oSet->Fetch())
+ {
+ // Note: By presetting value to false, it is quicker to find which objects where not returned by the scope query later
+ $aObjectIds[$oCurrentRow->GetKey()] = false;
+
+ // Preparing ExtKeys to preload
+ foreach($aColumnsToLoad as $sAttCode)
+ {
+ $iExtKey = $oCurrentRow->Get($sAttCode);
+ if($iExtKey > 0)
+ {
+ /** @var \AttributeExternalKey $oAttDef */
+ $oAttDef = MetaModel::GetAttributeDef($sObjectClass, $sAttCode);
+ if(!in_array($iExtKey, $aExtKeysIds[$oAttDef->GetTargetClass()]))
+ {
+ $aExtKeysIds[$oAttDef->GetTargetClass()][] = $iExtKey;
+ }
+ }
+ }
+ }
+
+ foreach(array(UR_ACTION_READ, UR_ACTION_MODIFY) as $sScopeAction)
+ {
+ // Retrieving scope query
+ /** @var DBSearch $oScopeQuery */
+ $oScopeQuery = $scopeValidator->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sObjectClass, $sScopeAction);
+ if($oScopeQuery !== null)
+ {
+ // Restricting scope if specified
+ if(!empty($aObjectIds))
+ {
+ $oScopeQuery->AddCondition('id', array_keys($aObjectIds), 'IN');
+ }
+
+ // Preparing object set
+ $oScopeSet = new DBObjectSet($oScopeQuery);
+ $oScopeSet->OptimizeColumnLoad(array());
+
+ // Checking objects status
+ $aScopeObjectIds = $aObjectIds;
+ while($oCurrentRow = $oScopeSet->Fetch())
+ {
+ $aScopeObjectIds[$oCurrentRow->GetKey()] = true;
+ }
+
+ // Updating cache
+ if(!isset(static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass]))
+ {
+ static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass] = $aScopeObjectIds;
+ }
+ else
+ {
+ static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass] = array_merge_recursive(static::$aAllowedScopeObjectsCache[$sScopeAction][$sObjectClass], $aScopeObjectIds);
+ }
+ }
+ }
+
+ // Preloading ExtKeys
+ foreach($aExtKeysIds as $sTargetClass => $aTargetIds)
+ {
+ if(!empty($aTargetIds))
+ {
+ $oTargetSearch = new DBObjectSearch($sTargetClass);
+ $oTargetSearch->AddCondition('id', $aTargetIds, 'IN');
+
+ static::PreloadForCache($scopeValidator, $oTargetSearch);
+ }
+ }
+ }
+}
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Kernel.php b/datamodels/2.x/itop-portal-base/portal/src/Kernel.php
new file mode 100644
index 000000000..bd766e17b
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/Kernel.php
@@ -0,0 +1,66 @@
+environment;
+
+ return utils::GetCachePath() . "/portals/$cacheDir";
+ }
+
+ public function getLogDir()
+ {
+ $logDir = PORTAL_ID . '-' . $this->environment;
+
+ return utils::GetLogPath() . "/portals/$logDir";
+ }
+
+ public function registerBundles()
+ {
+ $contents = require $this->getProjectDir().'/config/bundles.php';
+ foreach ($contents as $class => $envs) {
+ if (isset($envs[$this->environment]) || isset($envs['all'])) {
+ yield new $class();
+ }
+ }
+ }
+
+ protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
+ {
+ $container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php'));
+ // Feel free to remove the "container.autowiring.strict_mode" parameter
+ // if you are using symfony/dependency-injection 4.0+ as it's the default behavior
+ $container->setParameter('container.autowiring.strict_mode', true);
+ $container->setParameter('container.dumper.inline_class_loader', true);
+ $confDir = $this->getProjectDir().'/config';
+
+ $loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob');
+ $loader->load($confDir.'/{packages}/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob');
+ $loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob');
+ $loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob');
+ }
+
+ protected function configureRoutes(RouteCollectionBuilder $routes)
+ {
+ $confDir = $this->getProjectDir().'/config';
+
+ $routes->import($confDir.'/{routes}/'.$this->environment.'/**/*'.self::CONFIG_EXTS, '/', 'glob');
+ $routes->import($confDir.'/{routes}/*'.self::CONFIG_EXTS, '/', 'glob');
+ $routes->import($confDir.'/{routes}'.self::CONFIG_EXTS, '/', 'glob');
+ }
+}
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Routing/ItopExtensionsExtraRoutes.php b/datamodels/2.x/itop-portal-base/portal/src/Routing/ItopExtensionsExtraRoutes.php
new file mode 100644
index 000000000..3af039521
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/Routing/ItopExtensionsExtraRoutes.php
@@ -0,0 +1,71 @@
+getExtraParams($parameters);
+
+ return parent::doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes);
+ }
+
+ /**
+ * @param array $aParameters
+ *
+ * @return mixed
+ */
+ private function getExtraParams($aParameters)
+ {
+ $sExecModule = utils::ReadParam('exec_module', '', false, 'string');
+ $sExecPage = utils::ReadParam('exec_page', '', false, 'string');
+ if ($sExecModule !== '' && $sExecPage !== '') {
+ $aParameters['exec_module'] = $sExecModule;
+ $aParameters['exec_page'] = $sExecPage;
+ }
+
+ // Optional parameters
+ $sPortalId = utils::ReadParam('portal_id', '', false, 'string');
+ if ($sPortalId !== '') {
+ $aParameters['portal_id'] = $sPortalId;
+ }
+ $sEnvSwitch = utils::ReadParam('env_switch', '', false, 'string');
+ if ($sEnvSwitch !== '') {
+ $aParameters['env_switch'] = $sEnvSwitch;
+ }
+ $sDebug = utils::ReadParam('debug', '', false, 'string');
+ if ($sDebug !== '') {
+ $aParameters['debug'] = $sDebug;
+ }
+
+ return $aParameters;
+ }
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Security/ItopUser.php b/datamodels/2.x/itop-portal-base/portal/src/Security/ItopUser.php
new file mode 100644
index 000000000..e6bb7a5fd
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/Security/ItopUser.php
@@ -0,0 +1,62 @@
+oUser = $oUser;
+ }
+
+ public function setContactPhotoUrl($sContactPhotoUrl)
+ {
+ $this->sContactPhotoUrl = $sContactPhotoUrl;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getUser()
+ {
+ return $this->oUser;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getContactPhotoUrl()
+ {
+ return $this->sContactPhotoUrl;
+ }
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Twig/AppExtension.php b/datamodels/2.x/itop-portal-base/portal/src/Twig/AppExtension.php
new file mode 100644
index 000000000..8d0bae745
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/Twig/AppExtension.php
@@ -0,0 +1,107 @@
+Get($sParamName);
+ });
+
+ return $functions;
+ }
+
+
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Twig/AppVariable.php b/datamodels/2.x/itop-portal-base/portal/src/Twig/AppVariable.php
new file mode 100644
index 000000000..072663696
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/Twig/AppVariable.php
@@ -0,0 +1,166 @@
+decorated = $decorated;
+ $this->container = $container;
+ }
+
+ public function __call($name, $arguments)
+ {
+ if ($this->silexApplicationEmulation->offsetExists($name)) {
+ return $this->silexApplicationEmulation->offsetGet($name);
+ }
+
+ return $this->decorated->$name(...$arguments); //WARNING: use of http://php.net/manual/fr/functions.arguments.php#functions.variable-arg-list
+ }
+
+ /**
+ * Whether a offset exists
+ * @link https://php.net/manual/en/arrayaccess.offsetexists.php
+ *
+ * @param mixed $offset
+ * An offset to check for.
+ *
+ *
+ * @return boolean true on success or false on failure.
+ *
+ *
+ * The return value will be casted to boolean if non-boolean was returned.
+ * @since 5.0.0
+ */
+ public function offsetExists($offset)
+ {
+ if ($this->container->hasParameter($offset)) {
+ return true;
+ }
+ if ($this->container->has($offset)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Offset to retrieve
+ * @link https://php.net/manual/en/arrayaccess.offsetget.php
+ *
+ * @param mixed $offset
+ * The offset to retrieve.
+ *
+ *
+ * @return mixed Can return all value types.
+ * @since 5.0.0
+ */
+ public function offsetGet($offset)
+ {
+ if ($this->container->hasParameter($offset)) {
+ return $this->container->getParameter($offset);
+ }
+ if ($this->container->has($offset)) {
+ return $this->container->get($offset);
+ }
+
+ return;
+ }
+
+ /**
+ * Offset to set
+ * @link https://php.net/manual/en/arrayaccess.offsetset.php
+ *
+ * @param mixed $offset
+ * The offset to assign the value to.
+ *
+ * @param mixed $value
+ * The value to set.
+ *
+ *
+ * @return void
+ * @since 5.0.0
+ */
+ public function offsetSet($offset, $value)
+ {
+
+ if ($this->container->hasParameter($offset)) {
+ $this->container->setParameter($offset, $value);
+ return;
+ }
+
+ if ($this->container->has($offset)) {
+ $this->container->set($offset, $value);
+ return;
+ }
+
+ if (is_object($value)) {
+ $this->container->set($offset, $value);
+ return;
+ }
+
+ $this->container->setParameter($offset, $value);
+ return;
+ }
+
+ /**
+ * Offset to unset
+ * @link https://php.net/manual/en/arrayaccess.offsetunset.php
+ *
+ * @param mixed $offset
+ * The offset to unset.
+ *
+ *
+ * @return void
+ * @since 5.0.0
+ */
+ public function offsetUnset($offset){
+ if ($this->container->hasParameter($offset)) {
+ $this->container->setParameter($offset, null);
+ return;
+ }
+
+ if ($this->container->has($offset)) {
+ $this->container->set($offset, null);
+ return;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/src/VariableAccessor/AbstractStringVariableAccessor.php b/datamodels/2.x/itop-portal-base/portal/src/VariableAccessor/AbstractStringVariableAccessor.php
new file mode 100644
index 000000000..7d0498207
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/VariableAccessor/AbstractStringVariableAccessor.php
@@ -0,0 +1,19 @@
+getVariable();
+ }
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/src/VariableAccessor/AbstractVariableAccessor.php b/datamodels/2.x/itop-portal-base/portal/src/VariableAccessor/AbstractVariableAccessor.php
new file mode 100644
index 000000000..2be5ed033
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/VariableAccessor/AbstractVariableAccessor.php
@@ -0,0 +1,31 @@
+mStoredVariable = $mVariableToStore;
+ }
+
+ public function getVariable()
+ {
+ return $this->mStoredVariable;
+ }
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/src/VariableAccessor/CombodoCurrentContactPhotoUrl.php b/datamodels/2.x/itop-portal-base/portal/src/VariableAccessor/CombodoCurrentContactPhotoUrl.php
new file mode 100644
index 000000000..ae7c503d1
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/VariableAccessor/CombodoCurrentContactPhotoUrl.php
@@ -0,0 +1,40 @@
+getVariable()->getContactPhotoUrl();
+ }
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/src/VariableAccessor/CombodoCurrentUser.php b/datamodels/2.x/itop-portal-base/portal/src/VariableAccessor/CombodoCurrentUser.php
new file mode 100644
index 000000000..f62fe21a0
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/VariableAccessor/CombodoCurrentUser.php
@@ -0,0 +1,40 @@
+getVariable()->getUser();
+ }
+}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal/src/VariableAccessor/CombodoPortalInstanceConf.php b/datamodels/2.x/itop-portal-base/portal/src/VariableAccessor/CombodoPortalInstanceConf.php
new file mode 100644
index 000000000..14c011d58
--- /dev/null
+++ b/datamodels/2.x/itop-portal-base/portal/src/VariableAccessor/CombodoPortalInstanceConf.php
@@ -0,0 +1,14 @@
+
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/browse/mode_mosaic.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/browse/mode_mosaic.html.twig
similarity index 98%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/browse/mode_mosaic.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/browse/mode_mosaic.html.twig
index 0bd7a1472..774ef8d4f 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/browse/mode_mosaic.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/browse/mode_mosaic.html.twig
@@ -1,6 +1,6 @@
-{# itop-portal-base/portal/src/views/bricks/browse/mode_mosaic.html.twig #}
+{# itop-portal-base/portal/templates/bricks/browse/mode_mosaic.html.twig #}
{# Browse brick mosaic mode layout #}
-{% extends 'itop-portal-base/portal/src/views/bricks/browse/layout.html.twig' %}
+{% extends 'itop-portal-base/portal/templates/bricks/browse/layout.html.twig' %}
{% block bBrowseMainContent %}
@@ -21,7 +21,7 @@
{% block bBrowseMosaicOverlay %}
- {% include 'itop-portal-base/portal/src/views/helpers/loader.html.twig' %}
+ {% include 'itop-portal-base/portal/templates/helpers/loader.html.twig' %}
{% endblock %}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/browse/mode_tree.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/browse/mode_tree.html.twig
similarity index 98%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/browse/mode_tree.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/browse/mode_tree.html.twig
index 6e667fb40..1ecd1b988 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/browse/mode_tree.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/browse/mode_tree.html.twig
@@ -1,6 +1,6 @@
-{# itop-portal-base/portal/src/views/bricks/browse/mode_tree.html.twig #}
+{# itop-portal-base/portal/templates/bricks/browse/mode_tree.html.twig #}
{# Browse brick tree mode layout #}
-{% extends 'itop-portal-base/portal/src/views/bricks/browse/layout.html.twig' %}
+{% extends 'itop-portal-base/portal/templates/bricks/browse/layout.html.twig' %}
{#
Documentation :
@@ -36,7 +36,7 @@
- {% include 'itop-portal-base/portal/src/views/helpers/loader.html.twig' %}
+ {% include 'itop-portal-base/portal/templates/helpers/loader.html.twig' %}
{% endblock %}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/create/modal.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/create/modal.html.twig
similarity index 93%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/create/modal.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/create/modal.html.twig
index 256b6c9c1..255c4d366 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/create/modal.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/create/modal.html.twig
@@ -1,6 +1,6 @@
-{# itop-portal-base/portal/src/views/bricks/create/layout.html.twig #}
+{# itop-portal-base/portal/templates/bricks/create/layout.html.twig #}
{# Create brick base layout #}
-{% extends 'itop-portal-base/portal/src/views/modal/layout.html.twig' %}
+{% extends 'itop-portal-base/portal/templates/modal/layout.html.twig' %}
{% block pModalTitle %}
{{ oBrick.GetTitle()|dict_s }}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/filter/tile.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/filter/tile.html.twig
similarity index 96%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/filter/tile.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/filter/tile.html.twig
index 95cd02d8f..52d2f6bf3 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/filter/tile.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/filter/tile.html.twig
@@ -1,4 +1,4 @@
-{# itop-portal-base/portal/src/views/bricks/filter/tile.html.twig #}
+{# itop-portal-base/portal/templates/bricks/filter/tile.html.twig #}
{# Filter brick tile layout #}
{% if brick.GetTargetBrickClass == 'Combodo\\iTop\\Portal\\Brick\\BrowseBrick' %}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/layout.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/layout.html.twig
similarity index 96%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/layout.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/layout.html.twig
index e17bb6952..f1cd8c439 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/layout.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/layout.html.twig
@@ -1,4 +1,4 @@
-{# itop-portal-base/portal/src/views/bricks/layout.html.twig #}
+{# itop-portal-base/portal/templates/bricks/layout.html.twig #}
{# Brick base layout #}
{% extends app['combodo.portal.instance.conf'].properties.templates.layout %}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/layout-chart.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/layout-chart.html.twig
similarity index 69%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/layout-chart.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/layout-chart.html.twig
index 9f0667782..0684eea1b 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/layout-chart.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/layout-chart.html.twig
@@ -1,6 +1,6 @@
-{# itop-portal-base/portal/src/views/bricks/manage/layout-chart.html.twig #}
+{# itop-portal-base/portal/templates/bricks/manage/layout-chart.html.twig #}
{# Manage brick base layout #}
-{% extends 'itop-portal-base/portal/src/views/bricks/manage/layout.html.twig' %}
+{% extends 'itop-portal-base/portal/templates/bricks/manage/layout.html.twig' %}
{% block pPageBodyClass %}{{ parent() }} page_manage_brick{% endblock %}
@@ -8,7 +8,7 @@
{% block pMainContentHolder %}
- {% include 'itop-portal-base/portal/src/views/bricks/manage/mode-' ~ sDisplayMode ~ '.html.twig' with {'oBrick': oBrick, 'aColumns': aColumns, 'aNames': aNames, 'aUrls': aUrls, 'aDisplayValues': aDisplayValues} %}
+ {% include 'itop-portal-base/portal/templates/bricks/manage/mode-' ~ sDisplayMode ~ '.html.twig' with {'oBrick': oBrick, 'aColumns': aColumns, 'aNames': aNames, 'aUrls': aUrls, 'aDisplayValues': aDisplayValues} %}
{% endblock %}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/layout-table.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/layout-table.html.twig
similarity index 99%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/layout-table.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/layout-table.html.twig
index 80a14a764..dd2b3f9b6 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/layout-table.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/layout-table.html.twig
@@ -1,6 +1,6 @@
-{# itop-portal-base/portal/src/views/bricks/manage/layout-table.html.twig #}
+{# itop-portal-base/portal/templates/bricks/manage/layout-table.html.twig #}
{# Manage brick base layout #}
-{% extends 'itop-portal-base/portal/src/views/bricks/manage/layout.html.twig' %}
+{% extends 'itop-portal-base/portal/templates/bricks/manage/layout.html.twig' %}
{% block pPageBodyClass %}{{ parent() }} page_manage_brick{% endblock %}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/layout.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/layout.html.twig
similarity index 84%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/layout.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/layout.html.twig
index 08310935a..5b39f7ef1 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/layout.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/layout.html.twig
@@ -1,5 +1,5 @@
-{# itop-portal-base/portal/src/views/bricks/manage/layout.html.twig #}
-{% extends 'itop-portal-base/portal/src/views/bricks/layout.html.twig' %}
+{# itop-portal-base/portal/templates/bricks/manage/layout.html.twig #}
+{% extends 'itop-portal-base/portal/templates/bricks/layout.html.twig' %}
{% block pMainHeaderTitle %}{{ oBrick.GetTitle()|dict_s }} ({{ iCount }}) {% endblock %}
@@ -19,5 +19,5 @@
{% block pPageReadyScripts %}
{{ parent() }}
- {% include 'itop-portal-base/portal/src/views/helpers/tagset_clic_handler.js.twig' %}
+ {% include 'itop-portal-base/portal/templates/helpers/tagset_clic_handler.js.twig' %}
{% endblock %}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/mode-bar-chart.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/mode-bar-chart.html.twig
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/mode-bar-chart.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/mode-bar-chart.html.twig
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/mode-pie-chart.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/mode-pie-chart.html.twig
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/mode-pie-chart.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/mode-pie-chart.html.twig
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/popup-export-excel.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/popup-export-excel.html.twig
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/popup-export-excel.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/popup-export-excel.html.twig
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/tile-badge.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/tile-badge.html.twig
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/tile-badge.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/tile-badge.html.twig
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/tile-chart.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/tile-chart.html.twig
similarity index 95%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/tile-chart.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/tile-chart.html.twig
index 987522526..1e6b89245 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/tile-chart.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/tile-chart.html.twig
@@ -12,7 +12,7 @@
{{ oBrick.GetTitle()|dict_s }}
({{ iCount }})
- {% include 'itop-portal-base/portal/src/views/bricks/manage/mode-' ~ oBrick.GetTileMode ~ '.html.twig' with {'oBrick': oBrick, 'aColumns': aColumns, 'aNames': aNames, 'aUrls': aUrls, 'aDisplayValues': aDisplayValues} %}
+ {% include 'itop-portal-base/portal/templates/bricks/manage/mode-' ~ oBrick.GetTileMode ~ '.html.twig' with {'oBrick': oBrick, 'aColumns': aColumns, 'aNames': aNames, 'aUrls': aUrls, 'aDisplayValues': aDisplayValues} %}
{% endblock %}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/tile-default.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/tile-default.html.twig
similarity index 95%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/tile-default.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/tile-default.html.twig
index 91bfb4a24..3cd643366 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/tile-default.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/tile-default.html.twig
@@ -1,4 +1,4 @@
-{# itop-portal-base/portal/src/views/bricks/tile.html.twig #}
+{# itop-portal-base/portal/templates/bricks/tile.html.twig #}
{# Base brick tile layout #}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/tile-top-list.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/tile-top-list.html.twig
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/manage/tile-top-list.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/tile-top-list.html.twig
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/layout.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/layout.html.twig
similarity index 71%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/layout.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/object/layout.html.twig
index e0bb90c10..c9123d57a 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/layout.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/layout.html.twig
@@ -1,6 +1,6 @@
-{# itop-portal-base/portal/src/views/bricks/object/layout.html.twig #}
+{# itop-portal-base/portal/templates/bricks/object/layout.html.twig #}
{# Object brick base layout #}
-{% extends 'itop-portal-base/portal/src/views/bricks/layout.html.twig' %}
+{% extends 'itop-portal-base/portal/templates/bricks/layout.html.twig' %}
{% block pPageBodyClass %}{{ parent() }} page_object_brick page_object_brick_as_{{ sMode }}{% endblock %}
@@ -15,7 +15,7 @@
{% block pMainContentHolder%}
- {% include 'itop-portal-base/portal/src/views/bricks/object/mode_' ~ sMode ~ '.html.twig' %}
+ {% include 'itop-portal-base/portal/templates/bricks/object/mode_' ~ sMode ~ '.html.twig' %}
{% endblock %}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/modal.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/modal.html.twig
similarity index 55%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/modal.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/object/modal.html.twig
index 04fb7b9f7..5a2d41729 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/modal.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/modal.html.twig
@@ -1,6 +1,6 @@
-{# itop-portal-base/portal/src/views/bricks/object/modal.html.twig #}
+{# itop-portal-base/portal/templates/bricks/object/modal.html.twig #}
{# Object brick base layout #}
-{% extends 'itop-portal-base/portal/src/views/modal/layout.html.twig' %}
+{% extends 'itop-portal-base/portal/templates/modal/layout.html.twig' %}
{% block pModalTitle %}
{% if form.title is defined %}
@@ -9,5 +9,5 @@
{% endblock %}
{% block pModalBody %}
- {% include 'itop-portal-base/portal/src/views/bricks/object/mode_' ~ sMode ~ '.html.twig' with {tIsModal: true} %}
+ {% include 'itop-portal-base/portal/templates/bricks/object/mode_' ~ sMode ~ '.html.twig' with {tIsModal: true} %}
{% endblock %}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_apply_stimulus.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_apply_stimulus.html.twig
similarity index 62%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_apply_stimulus.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_apply_stimulus.html.twig
index aff1f18c9..918c75e22 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_apply_stimulus.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_apply_stimulus.html.twig
@@ -1,5 +1,5 @@
-{# itop-portal-base/portal/src/views/bricks/object/mode_apply_stimulus.html.twig #}
+{# itop-portal-base/portal/templates/bricks/object/mode_apply_stimulus.html.twig #}
{# Object brick apply stimulus layout #}
-{% extends 'itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig' %}
+{% extends 'itop-portal-base/portal/templates/bricks/object/mode_create.html.twig' %}
{# This layout is exactly the same as the mode_create.html.twig, we duplicated it in case we need to have some subtle differences #}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_create.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_create.html.twig
similarity index 98%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_create.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_create.html.twig
index 02451d0c3..22f0ccfb4 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_create.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_create.html.twig
@@ -1,4 +1,4 @@
-{# itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig #}
+{# itop-portal-base/portal/templates/bricks/object/mode_create.html.twig #}
{# Object brick create layout #}
{% set tIsModal = (tIsModal is defined and tIsModal == true) ? true : false %}
@@ -30,7 +30,7 @@
{# Misc. buttons #}
{% if form.buttons is defined and (form.buttons.actions is defined or form.buttons.links is defined) %}
- {% include 'itop-portal-base/portal/src/views/bricks/object/plugins_buttons.html.twig' with {'aButtons': form.buttons} %}
+ {% include 'itop-portal-base/portal/templates/bricks/object/plugins_buttons.html.twig' with {'aButtons': form.buttons} %}
{% endif %}
{# Transition buttons #}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_edit.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_edit.html.twig
similarity index 60%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_edit.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_edit.html.twig
index e68bf51f0..c04137e21 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_edit.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_edit.html.twig
@@ -1,5 +1,5 @@
-{# itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig #}
+{# itop-portal-base/portal/templates/bricks/object/mode_create.html.twig #}
{# Object brick edit layout #}
-{% extends 'itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig' %}
+{% extends 'itop-portal-base/portal/templates/bricks/object/mode_create.html.twig' %}
{# This layout is exactly the same as the mode_create.html.twig, we duplicated it in case we need to have some subtle differences #}
\ No newline at end of file
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_search_hierarchy.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_search_hierarchy.html.twig
similarity index 96%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_search_hierarchy.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_search_hierarchy.html.twig
index 53354deba..dde320ac3 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_search_hierarchy.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_search_hierarchy.html.twig
@@ -1,4 +1,4 @@
-{# itop-portal-base/portal/src/views/bricks/object/mode_search_hierarchy.html.twig #}
+{# itop-portal-base/portal/templates/bricks/object/mode_search_hierarchy.html.twig #}
{# Object brick hierarchy search layout #}
{% set sFormId = (form.id is defined and form.id is not null) ? form.id : 'object_search_form' %}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_search_regular.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_search_regular.html.twig
similarity index 99%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_search_regular.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_search_regular.html.twig
index 58d1005a3..414567b10 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_search_regular.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_search_regular.html.twig
@@ -1,4 +1,4 @@
-{# itop-portal-base/portal/src/views/bricks/object/mode_search_regular.html.twig #}
+{# itop-portal-base/portal/templates/bricks/object/mode_search_regular.html.twig #}
{# Object brick regular search layout #}
{% set sFormId = (form.id is defined and form.id is not null) ? form.id : 'object_search_form' %}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_view.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_view.html.twig
similarity index 79%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_view.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_view.html.twig
index d005a82e9..c737be88e 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/mode_view.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_view.html.twig
@@ -1,6 +1,6 @@
-{# itop-portal-base/portal/src/views/bricks/object/mode_view.html.twig #}
+{# itop-portal-base/portal/templates/bricks/object/mode_view.html.twig #}
{# Object brick view layout #}
-{% extends 'itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig' %}
+{% extends 'itop-portal-base/portal/templates/bricks/object/mode_create.html.twig' %}
{# This layout is exactly the same as the mode_create.html.twig, we duplicated it in case we need to have some subtle differences #}
@@ -8,7 +8,7 @@
{# Misc. buttons #}
{% if form.buttons is defined and (form.buttons.actions is defined or form.buttons.links is defined) %}
- {% include 'itop-portal-base/portal/src/views/bricks/object/plugins_buttons.html.twig' with {'aButtons': form.buttons} %}
+ {% include 'itop-portal-base/portal/templates/bricks/object/plugins_buttons.html.twig' with {'aButtons': form.buttons} %}
{% endif %}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/plugins_buttons.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/plugins_buttons.html.twig
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/object/plugins_buttons.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/object/plugins_buttons.html.twig
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/tile.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/tile.html.twig
similarity index 95%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/tile.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/tile.html.twig
index fc7a4db26..cb5fd9975 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/tile.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/tile.html.twig
@@ -1,4 +1,4 @@
-{# itop-portal-base/portal/src/views/bricks/tile.html.twig #}
+{# itop-portal-base/portal/templates/bricks/tile.html.twig #}
{# Base brick tile layout #}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/user-profile/layout.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/user-profile/layout.html.twig
similarity index 98%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/user-profile/layout.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/bricks/user-profile/layout.html.twig
index 14ba83a50..2a11d265f 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/bricks/user-profile/layout.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/user-profile/layout.html.twig
@@ -1,6 +1,6 @@
-{# itop-portal-base/portal/src/views/bricks/user-profile/layout.html.twig #}
+{# itop-portal-base/portal/templates/bricks/user-profile/layout.html.twig #}
{# User profile brick base layout #}
-{% extends 'itop-portal-base/portal/src/views/bricks/layout.html.twig' %}
+{% extends 'itop-portal-base/portal/templates/bricks/layout.html.twig' %}
{% set oContactForm = forms.contact %}
{% set oPreferencesForm = forms.preferences %}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/errors/layout.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/errors/layout.html.twig
similarity index 96%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/errors/layout.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/errors/layout.html.twig
index 415719be3..f4e9295cf 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/errors/layout.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/errors/layout.html.twig
@@ -1,6 +1,6 @@
{# errors/layout.html.twig #}
{# Base error layout #}
-{% extends 'itop-portal-base/portal/src/views/layout.html.twig' %}
+{% extends 'itop-portal-base/portal/templates/layout.html.twig' %}
{% block pNavigationWrapper %}
{% endblock %}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/helpers/loader.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/helpers/loader.html.twig
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/helpers/loader.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/helpers/loader.html.twig
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/helpers/tagset_clic_handler.js.twig b/datamodels/2.x/itop-portal-base/portal/templates/helpers/tagset_clic_handler.js.twig
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/helpers/tagset_clic_handler.js.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/helpers/tagset_clic_handler.js.twig
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/home/layout.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/home/layout.html.twig
similarity index 92%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/home/layout.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/home/layout.html.twig
index 60f6f2ae4..183b8d94a 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/home/layout.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/home/layout.html.twig
@@ -1,4 +1,4 @@
-{# itop-portal-base-base/portal/src/views/home/layout.html.twig #}
+{# itop-portal-base-base/portal/templates/home/layout.html.twig #}
{# Home layout #}
{% extends app['combodo.portal.instance.conf'].properties.templates.layout %}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/layout.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/layout.html.twig
similarity index 99%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/layout.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/layout.html.twig
index c09adb756..6b0b3feac 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/layout.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/layout.html.twig
@@ -354,7 +354,7 @@
- {% include 'itop-portal-base/portal/src/views/helpers/loader.html.twig' %}
+ {% include 'itop-portal-base/portal/templates/helpers/loader.html.twig' %}
@@ -382,7 +382,7 @@
{% block pPageOverlay %}
- {% include 'itop-portal-base/portal/src/views/helpers/loader.html.twig' %}
+ {% include 'itop-portal-base/portal/templates/helpers/loader.html.twig' %}
{% endblock %}
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/modal/layout.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/modal/layout.html.twig
similarity index 100%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/modal/layout.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/modal/layout.html.twig
diff --git a/datamodels/2.x/itop-portal-base/portal-silex/src/views/modal/mode_loader.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/modal/mode_loader.html.twig
similarity index 62%
rename from datamodels/2.x/itop-portal-base/portal-silex/src/views/modal/mode_loader.html.twig
rename to datamodels/2.x/itop-portal-base/portal/templates/modal/mode_loader.html.twig
index 7fa47ebe2..75c4afd6e 100644
--- a/datamodels/2.x/itop-portal-base/portal-silex/src/views/modal/mode_loader.html.twig
+++ b/datamodels/2.x/itop-portal-base/portal/templates/modal/mode_loader.html.twig
@@ -1,9 +1,9 @@
-{# itop-portal-base/portal/src/views/bricks/object/mode_loader.html.twig #}
+{# itop-portal-base/portal/templates/bricks/object/mode_loader.html.twig #}
{# Modal loader layout #}
-{% extends 'itop-portal-base/portal/src/views/modal/layout.html.twig' %}
+{% extends 'itop-portal-base/portal/templates/modal/layout.html.twig' %}
{% block pModalContent %}
- {% include 'itop-portal-base/portal/src/views/helpers/loader.html.twig' %}
+ {% include 'itop-portal-base/portal/templates/helpers/loader.html.twig' %}
{% if redirection is defined and redirection.url is defined %}