N°7433 - Allow MFA configuration in the portal (#670)

* N°7433 - Extension for MFA

* Portal user profile brick

* fix mfa tab display

* Portal user profile brick

* Portal user profile brick

* mfa portal action

* extensibility on portal to make mfa recovery mode work

* Portal user profile brick extension

* Portal user profile brick extension

* display alert section in user portal profile layout

* protect mfa via transaction_id mecanism

* Fix transaction ids

* Fix when no MFA

* N°7433 - Allow MFA configuration in the portal (refactor interfaces)

* N°7433 - Allow MFA configuration in the portal (revert unnecessary changes)

* N°7433 - Allow MFA configuration in the portal (finer style)

* N°7433 - Allow MFA configuration in the portal (PHPDoc update)

* N°7433 - PR review

---------

Co-authored-by: Eric Espie <eric.espie@combodo.com>
This commit is contained in:
odain-cbd
2024-10-16 16:41:58 +02:00
committed by GitHub
parent 7a4d29d561
commit 7254bb7a2f
17 changed files with 1006 additions and 348 deletions

View File

@@ -19,33 +19,33 @@
namespace Combodo\iTop\Portal\Brick;
use DOMFormatException;
use Combodo\iTop\DesignElement;
use DOMFormatException;
/**
* Description of UserProfileBrick
*
* @package Combodo\iTop\Portal\Brick
* @since 2.7.0
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @since 2.7.0
* @package Combodo\iTop\Portal\Brick
*/
class UserProfileBrick extends PortalBrick
{
// Overloaded constants
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';
const DEFAULT_DECORATION_CLASS_HOME = 'glyphicon glyphicon-user';
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';
const DEFAULT_DECORATION_CLASS_HOME = 'glyphicon glyphicon-user';
const DEFAULT_DECORATION_CLASS_NAVIGATION_MENU = 'glyphicon glyphicon-user';
/** @var bool DEFAULT_SHOW_PICTURE_FORM */
const DEFAULT_SHOW_PICTURE_FORM = true;
/** @var bool DEFAULT_SHOW_PREFERENCES_FORM */
const DEFAULT_SHOW_PREFERENCES_FORM = true;
/** @var bool DEFAULT_SHOW_PASSWORD_FORM */
const DEFAULT_SHOW_PASSWORD_FORM = true;
const DEFAULT_SHOW_PREFERENCES_FORM = true;
/** @var bool DEFAULT_SHOW_PASSWORD_FORM */
const DEFAULT_SHOW_PASSWORD_FORM = true;
// Overloaded variables
static $sRouteName = 'p_user_profile_brick';
@@ -67,8 +67,8 @@ class UserProfileBrick extends PortalBrick
parent::__construct();
$this->aForm = array(
'id' => 'default-user-profile',
'type' => 'zlist',
'id' => 'default-user-profile',
'type' => 'zlist',
'fields' => 'details',
'layout' => null,
);
@@ -89,67 +89,75 @@ class UserProfileBrick extends PortalBrick
/**
*
* @param array $aForm
*
* @return \Combodo\iTop\Portal\Brick\UserProfileBrick
*/
public function SetForm($aForm)
{
$this->aForm = $aForm;
return $this;
}
/**
* @return bool
*/
public function GetShowPictureForm()
{
return $this->bShowPictureForm;
}
/**
* @return bool
*/
public function GetShowPictureForm()
{
return $this->bShowPictureForm;
}
/**
* @param $bShowPictureForm
* @return \Combodo\iTop\Portal\Brick\UserProfileBrick
*/
public function SetShowPictureForm($bShowPictureForm)
{
$this->bShowPictureForm = $bShowPictureForm;
return $this;
}
/**
* @param $bShowPictureForm
*
* @return \Combodo\iTop\Portal\Brick\UserProfileBrick
*/
public function SetShowPictureForm($bShowPictureForm)
{
$this->bShowPictureForm = $bShowPictureForm;
/**
* @return bool
*/
public function GetShowPreferencesForm()
{
return $this->bShowPreferencesForm;
}
return $this;
}
/**
* @param $bShowPreferencesForm
* @return \Combodo\iTop\Portal\Brick\UserProfileBrick
*/
public function SetShowPreferencesForm($bShowPreferencesForm)
{
$this->bShowPreferencesForm = $bShowPreferencesForm;
return $this;
}
/**
* @return bool
*/
public function GetShowPreferencesForm()
{
return $this->bShowPreferencesForm;
}
/**
* @return bool
*/
public function GetShowPasswordForm()
{
return $this->bShowPasswordForm;
}
/**
* @param $bShowPreferencesForm
*
* @return \Combodo\iTop\Portal\Brick\UserProfileBrick
*/
public function SetShowPreferencesForm($bShowPreferencesForm)
{
$this->bShowPreferencesForm = $bShowPreferencesForm;
/**
* @param $bShowPasswordForm
* @return \Combodo\iTop\Portal\Brick\UserProfileBrick
*/
public function SetShowPasswordForm($bShowPasswordForm)
{
$this->bShowPasswordForm = $bShowPasswordForm;
return $this;
}
return $this;
}
/**
* @return bool
*/
public function GetShowPasswordForm()
{
return $this->bShowPasswordForm;
}
/**
* @param $bShowPasswordForm
*
* @return \Combodo\iTop\Portal\Brick\UserProfileBrick
*/
public function SetShowPasswordForm($bShowPasswordForm)
{
$this->bShowPasswordForm = $bShowPasswordForm;
return $this;
}
/**
* Load the brick's data from the xml passed as a ModuleDesignElement.
@@ -167,50 +175,39 @@ class UserProfileBrick extends PortalBrick
// Checking specific elements
/** @var \Combodo\iTop\DesignElement $oBrickSubNode */
foreach ($oMDElement->GetNodes('./*') as $oBrickSubNode)
{
switch ($oBrickSubNode->nodeName)
{
foreach ($oMDElement->GetNodes('./*') as $oBrickSubNode) {
switch ($oBrickSubNode->nodeName) {
case 'form':
// Note : This is inspired by Combodo\iTop\Portal\Helper\ApplicationHelper::LoadFormsConfiguration()
// Enumerating fields
if ($oBrickSubNode->GetOptionalElement('fields') !== null)
{
if ($oBrickSubNode->GetOptionalElement('fields') !== null) {
$this->aForm['type'] = 'custom_list';
$this->aForm['fields'] = array();
/** @var \Combodo\iTop\DesignElement $oFieldNode */
foreach ($oBrickSubNode->GetOptionalElement('fields')->GetNodes('field') as $oFieldNode)
{
foreach ($oBrickSubNode->GetOptionalElement('fields')->GetNodes('field') as $oFieldNode) {
$sFieldId = $oFieldNode->getAttribute('id');
if ($sFieldId !== '')
{
if ($sFieldId !== '') {
$aField = array();
// Parsing field options like read_only, hidden and mandatory
if ($oFieldNode->GetOptionalElement('read_only'))
{
if ($oFieldNode->GetOptionalElement('read_only')) {
$aField['readonly'] = ($oFieldNode->GetOptionalElement('read_only')->GetText('true') === 'true') ? true : false;
}
if ($oFieldNode->GetOptionalElement('mandatory'))
{
if ($oFieldNode->GetOptionalElement('mandatory')) {
$aField['mandatory'] = ($oFieldNode->GetOptionalElement('mandatory')->GetText('true') === 'true') ? true : false;
}
if ($oFieldNode->GetOptionalElement('hidden'))
{
if ($oFieldNode->GetOptionalElement('hidden')) {
$aField['hidden'] = ($oFieldNode->GetOptionalElement('hidden')->GetText('true') === 'true') ? true : false;
}
$this->aForm['fields'][$sFieldId] = $aField;
}
else
{
} else {
throw new DOMFormatException('Field tag must have an id attribute', null, null, $oFieldNode);
}
}
}
// Parsing presentation
if ($oBrickSubNode->GetOptionalElement('twig') !== null)
{
if ($oBrickSubNode->GetOptionalElement('twig') !== null) {
// Extracting the twig template and removing the first and last lines (twig tags)
$sXml = $oBrickSubNode->GetOptionalElement('twig')->Dump(true);
//$sXml = $oMDElement->saveXML($oBrickSubNode->GetOptionalElement('twig'));
@@ -218,25 +215,24 @@ class UserProfileBrick extends PortalBrick
$sXml = preg_replace('/\n.+$/', '', $sXml);
$this->aForm['layout'] = array(
'type' => (preg_match('/\{\{|\{\#|\{\%/', $sXml) === 1) ? 'twig' : 'xhtml',
'type' => (preg_match('/\{\{|\{\#|\{\%/', $sXml) === 1) ? 'twig' : 'xhtml',
'content' => $sXml,
);
}
break;
case 'show_picture_form':
case 'show_preferences_form':
case 'show_password_form':
$sConstName = 'DEFAULT_'.strtoupper($oBrickSubNode->nodeName);
$sSetterName = 'Set'.str_replace('_', '', ucwords($oBrickSubNode->nodeName, '_'));
case 'show_picture_form':
case 'show_preferences_form':
case 'show_password_form':
$sConstName = 'DEFAULT_'.strtoupper($oBrickSubNode->nodeName);
$sSetterName = 'Set'.str_replace('_', '', ucwords($oBrickSubNode->nodeName, '_'));
$bNodeValue = ($oBrickSubNode->GetText(constant('static::'.$sConstName)) === 'true') ? true : false;
$this->$sSetterName($bNodeValue);
break;
$bNodeValue = ($oBrickSubNode->GetText(constant('static::'.$sConstName)) === 'true') ? true : false;
$this->$sSetterName($bNodeValue);
break;
}
}
return $this;
}
}

View File

@@ -24,8 +24,13 @@ use Combodo\iTop\Portal\Brick\BrickCollection;
use Combodo\iTop\Portal\Brick\UserProfileBrick;
use Combodo\iTop\Portal\Form\PasswordFormManager;
use Combodo\iTop\Portal\Form\PreferencesFormManager;
use Combodo\iTop\Portal\Helper\ExtensibilityHelper;
use Combodo\iTop\Portal\Helper\ObjectFormHandlerHelper;
use Combodo\iTop\Portal\Helper\RequestManipulatorHelper;
use Combodo\iTop\Portal\Hook\iAbstractPortalTabContentExtension;
use Combodo\iTop\Portal\Hook\iAbstractPortalTabExtension;
use Combodo\iTop\Portal\Hook\iUserProfileTabContentExtension;
use Combodo\iTop\Portal\Hook\iUserProfileTabExtension;
use Combodo\iTop\Portal\Routing\UrlGenerator;
use Combodo\iTop\Renderer\Bootstrap\BsFormRenderer;
use Exception;
@@ -106,56 +111,60 @@ class UserProfileBrickController extends BrickController
$oBrick = $this->oBrickCollection->GetBrickById($sBrickId);
}
$aData = array();
$aData = [];
// Setting form mode regarding the demo mode parameter
$bDemoMode = MetaModel::GetConfig()->Get('demo_mode');
$sFormMode = ($bDemoMode) ? ObjectFormHandlerHelper::ENUM_MODE_VIEW : ObjectFormHandlerHelper::ENUM_MODE_EDIT;
$sTab = $this->oRequestManipulatorHelper->ReadParam('sTab', 'user-info', FILTER_UNSAFE_RAW, FILTER_FLAG_EMPTY_STRING_NULL);
// If this is ajax call, we are just submitting preferences or password forms
if ($oRequest->isXmlHttpRequest())
{
$aCurrentValues = $this->oRequestManipulatorHelper->ReadParam('current_values', array(), FILTER_UNSAFE_RAW, FILTER_REQUIRE_ARRAY);
$sFormType = $aCurrentValues['form_type'];
if ($sFormType === PreferencesFormManager::FORM_TYPE)
{
$aData['form'] = $this->HandlePreferencesForm($oRequest, $sFormMode);
}
elseif ($sFormType === PasswordFormManager::FORM_TYPE)
{
$aData['form'] = $this->HandlePasswordForm($oRequest, $sFormMode);
}
elseif ($sFormType === static::ENUM_FORM_TYPE_PICTURE)
{
$aData['form'] = $this->HandlePictureForm($oRequest);
}
else
{
throw new Exception('Unknown form type.');
if ($sTab === "user-info") {
$aCurrentValues = $this->oRequestManipulatorHelper->ReadParam('current_values', array(), FILTER_UNSAFE_RAW,
FILTER_REQUIRE_ARRAY);
$sFormType = $aCurrentValues['form_type'];
if ($sFormType === PreferencesFormManager::FORM_TYPE) {
$aData['form'] = $this->HandlePreferencesForm($oRequest, $sFormMode);
} elseif ($sFormType === PasswordFormManager::FORM_TYPE) {
$aData['form'] = $this->HandlePasswordForm($oRequest, $sFormMode);
} elseif ($sFormType === static::ENUM_FORM_TYPE_PICTURE) {
$aData['form'] = $this->HandlePictureForm($oRequest);
} else {
throw new Exception('Unknown form type.');
}
}
$oResponse = new JsonResponse($aData);
}
// Else, we are displaying page for first time
else
{
// Retrieving current contact
/** @var \DBObject $oCurContact */
$oCurContact = UserRights::GetContactObject();
$sCurContactClass = get_class($oCurContact);
$sCurContactId = $oCurContact->GetKey();
if ($sTab === "user-info") {
// Retrieving current contact
/** @var \DBObject $oCurContact */
$oCurContact = UserRights::GetContactObject();
$sCurContactClass = get_class($oCurContact);
$sCurContactId = $oCurContact->GetKey();
// Preparing forms
$aData['forms']['contact'] = $this->ObjectFormHandlerHelper->HandleForm($oRequest, $sFormMode, $sCurContactClass, $sCurContactId,
$oBrick->GetForm());
$aData['forms']['preferences'] = $this->HandlePreferencesForm($oRequest, $sFormMode);
// - If user can change password, we display the form
$aData['forms']['password'] = (UserRights::CanChangePassword()) ? $this->HandlePasswordForm($oRequest, $sFormMode) : null;
// Preparing forms
$aData['forms']['contact'] = $this->ObjectFormHandlerHelper->HandleForm($oRequest, $sFormMode, $sCurContactClass,
$sCurContactId,
$oBrick->GetForm());
$aData['forms']['preferences'] = $this->HandlePreferencesForm($oRequest, $sFormMode);
// - If user can change password, we display the form
$aData['forms']['password'] = (UserRights::CanChangePassword()) ? $this->HandlePasswordForm($oRequest, $sFormMode) : null;
}
$aData = $aData + array(
$aData = $aData + [
'oBrick' => $oBrick,
'sFormMode' => $sFormMode,
'bDemoMode' => $bDemoMode,
);
];
$this->ManageUserProfileBrickExtensibility($sTab, $aData);
$oResponse = $this->render($oBrick->GetPageTemplatePath(), $aData);
}
@@ -163,6 +172,46 @@ class UserProfileBrickController extends BrickController
return $oResponse;
}
private function ManageUserProfileBrickExtensibility(string $sTab, array &$aData): void
{
$aData['sTab'] = $sTab;
// Read the tabs From iPortalTabExtension
$aTabExtensions = ExtensibilityHelper::GetInstance()->GetPortalTabExtensions(iUserProfileTabExtension::class);
/** @var iAbstractPortalTabExtension $oPortalTabExtension */
foreach ($aTabExtensions as $oPortalTabExtension) {
$aData['aTabsValues'][] = [
'code' => $oPortalTabExtension->GetTabCode(),
'label' => $oPortalTabExtension->GetTabLabel(),
];
}
// Read the current tab content From iPortalTabSectionExtension
$aTabSectionExtensions = ExtensibilityHelper::GetInstance()->GetPortalTabContentExtensions(iUserProfileTabContentExtension::class, $sTab);
if (count($aTabSectionExtensions) !== 0 && count($_POST) !== 0){
$sTransactionId = utils::ReadPostedParam('transaction_id', null, utils::ENUM_SANITIZATION_FILTER_TRANSACTION_ID);
IssueLog::Debug(__FUNCTION__.": transaction [$sTransactionId]");
if (utils::IsNullOrEmptyString($sTransactionId) || !utils::IsTransactionValid($sTransactionId, false)) {
throw new Exception(\Dict::S('iTopUpdate:Error:InvalidToken'));
}
}
$aData['sTransactionId'] = utils::GetNewTransactionId();
/** @var iAbstractPortalTabContentExtension $oPortalTabSectionExtension */
foreach ($aTabSectionExtensions as $oPortalTabSectionExtension) {
$oPortalTabSectionExtension->HandlePortalForm($aData);
}
$aData['aPluginFormData'] = [];
foreach ($aTabSectionExtensions as $oPortalTabSectionExtension) {
$aData['aPluginFormData'][] = $oPortalTabSectionExtension->GetPortalTabContentTwigs();
}
}
/**
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param string $sFormMode
@@ -173,8 +222,6 @@ class UserProfileBrickController extends BrickController
*/
public function HandlePreferencesForm(Request $oRequest, $sFormMode)
{
$aFormData = array();
// Handling form
@@ -393,5 +440,4 @@ class UserProfileBrickController extends BrickController
return $aFormData;
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* @copyright Copyright (C) 2010-2024 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Portal\Helper;
use Combodo\iTop\Portal\Hook\iAbstractPortalTabContentExtension;
use Combodo\iTop\Portal\Hook\iAbstractPortalTabExtension;
use Combodo\iTop\Service\InterfaceDiscovery\InterfaceDiscovery;
/**
* This helper is used by the bricks to manage the tab extensibility by retrieving
* the classes implementing the corresponding interfaces
*
* @api
* @see \Combodo\iTop\Portal\Hook\iAbstractPortalTabExtension
* @see \Combodo\iTop\Portal\Hook\iAbstractPortalTabContentExtension
* @since iTop 3.2.1
*/
class ExtensibilityHelper
{
private static ExtensibilityHelper $oInstance;
protected function __construct()
{
}
final public static function GetInstance(): ExtensibilityHelper
{
if (!isset(static::$oInstance)) {
static::$oInstance = new static();
}
return static::$oInstance;
}
/**
* Instantiate all the classes implementing the given interface
*
* @api
* @see \Combodo\iTop\Portal\Hook\iAbstractPortalTabExtension
*
* @param string $sPortalTabExtensionInterface Extensibility interface to search for (derived from iAbstractPortalTabExtension)
*
* @return array[iAbstractPortalTabExtension] array of objects implementing the given interface
*
* @since iTop 3.2.1
*/
public function GetPortalTabExtensions(string $sPortalTabExtensionInterface): array
{
$aTabExtensions = [];
foreach (InterfaceDiscovery::GetInstance()->FindItopClasses($sPortalTabExtensionInterface) as $sPortalTabExtension) {
$oPortalTabExtension = new $sPortalTabExtension();
if ($oPortalTabExtension->IsTabPresent()) {
$aTabExtensions[] = $oPortalTabExtension;
}
}
usort($aTabExtensions, function (iAbstractPortalTabExtension $a, iAbstractPortalTabExtension $b) {
return $a->GetTabRank() - $b->GetTabRank();
});
return $aTabExtensions;
}
/**
* Instantiate all the classes implementing the given interface for the given tab
*
* @param string $sPortalTabSectionExtensionInterface Extensibility interface to search for (derived from iAbstractPortalTabContentExtension)
* @param string $sTab Tab code
*
* @return array[iPortalTabContentExtension] array of objects implementing the given interface
*/
public function GetPortalTabContentExtensions(string $sPortalTabSectionExtensionInterface, string $sTab): array
{
$aTabSectionExtensions = [];
foreach (InterfaceDiscovery::GetInstance()->FindItopClasses($sPortalTabSectionExtensionInterface) as $sPortalTabSectionExtension) {
$oPortalTabSectionExtension = new $sPortalTabSectionExtension();
if (!$oPortalTabSectionExtension->IsActive()) {
continue;
}
if ($oPortalTabSectionExtension->GetTabCode() !== $sTab) {
continue;
}
$aTabSectionExtensions[] = $oPortalTabSectionExtension;
}
usort($aTabSectionExtensions, function (iAbstractPortalTabContentExtension $a, iAbstractPortalTabContentExtension $b) {
return $a->GetSectionRank() - $b->GetSectionRank();
});
return $aTabSectionExtensions;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* @copyright Copyright (C) 2010-2024 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Portal\Hook;
use Combodo\iTop\Portal\Twig\PortalTwigContext;
/**
* Interface to provide content to a portal brick allowing tab extensibility.
* This interface allows to provide content to existing tabs.
*
* This interface should not be used directly, bricks willing to provide extensibility
* should use an interface derived from this one.
*
* @api
* @since iTop 3.2.1
* @see \Combodo\iTop\Portal\Hook\iAbstractPortalTabExtension
*/
interface iAbstractPortalTabContentExtension
{
/**
* Indicates if the extension is active or not
*
* @return bool true if the content has to be displayed
*
* @api
* @since iTop 3.2.1
*/
public function IsActive(): bool;
/**
* Tab code name where to add the section
*
* @return string tab code (the code must contain at least one character, cannot start with a number, and must not contain whitespaces (spaces, tabs, etc.).)
*
* @api
* @since iTop 3.2.1
*/
public function GetTabCode(): string;
/**
* Handle actions based on posted vars
*
* @param array $aData variables to pass to the brick's twig to display the result of the action
*
* @api
* @since iTop 3.2.1
*/
public function HandlePortalForm(array &$aData): void;
/**
* List twigs and variables for the tab content per block
*
* @return PortalTwigContext containing twigs to display and associated variables to use
*
* @api
* @since iTop 3.2.1
*/
public function GetPortalTabContentTwigs(): PortalTwigContext;
/**
* Get the section rank in the tab (used to sort the contents of a tab)
*
* @return float rank order
*
* @api
* @since iTop 3.2.1
*/
public function GetSectionRank(): float;
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* @copyright Copyright (C) 2010-2024 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Portal\Hook;
/**
* Interface to provide content to a portal brick allowing tab extensibility.
* This interface allows to provide new tabs to bricks.
*
* This interface should not be used directly, bricks willing to provide extensibility
* should use an interface derived from this one.
*
* @api
* @since iTop 3.2.1
* @see \Combodo\iTop\Portal\Hook\iAbstractPortalTabContentExtension
*/
interface iAbstractPortalTabExtension
{
/**
* True if the tab must be displayed or false if the tab must be hidden.
* When the tab is not displayed the other methods are not called.
*
* @return bool
*
* @api
* @since iTop 3.2.1
*/
public function IsTabPresent(): bool;
/**
* Rank of the tab to allow sorting (ascending order)
*
* @return float order rank
*
* @api
* @since iTop 3.2.1
*/
public function GetTabRank(): float;
/**
* Unique code for the AjaxTab (in the brick page)
*
* @return string the code must contain at least one character, cannot start with a number, and must not contain whitespaces (spaces, tabs, etc.).
*
* @api
* @since iTop 3.2.1
*/
public function GetTabCode(): string;
/**
* Label of the tab
*
* @return string
*
* @api
* @since iTop 3.2.1
*/
public function GetTabLabel(): string;
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* @copyright Copyright (C) 2010-2024 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Portal\Hook;
/**
* This interface is used to add content to the user profile brick.
*
* @api
* @since iTop 3.2.1
*/
interface iUserProfileTabContentExtension extends iAbstractPortalTabContentExtension
{
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* @copyright Copyright (C) 2010-2024 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Portal\Hook;
/**
* This interface is used to add content to the user profile brick.
*
* @api
* @since iTop 3.2.1
*/
interface iUserProfileTabExtension extends iAbstractPortalTabExtension
{
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* @copyright Copyright (C) 2010-2024 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Portal\Twig;
use utils;
/**
* Entity used to store a twig template and the associated data
*
* @api
*
* @since iTop 3.2.1
*/
class PortalBlockExtension
{
private string $sTwig;
private array $aData;
/**
* Create a new twig extension block.
* The given twig template can be HTML, CSS or JavaScript.
* * CSS goes to the block named 'css' and is inline in the page.
* * JavaScript goes to the blocks named 'script' or 'ready_script' and are inline in the page.
* * HTML goes everywhere else.
*
* @api
*
* @param array $aData Data given to the twig template (into the variable {{ aData }})
* @param string $sTwig name of the twig file to the absolute path given to the PortalTwigContext
*
* @since iTop 3.2.1
*/
function __construct(string $sTwig, array $aData = [])
{
$this->sTwig = $sTwig;
$this->aData = $aData;
}
/**
* Used by twig templates to get the name of the template to render.
*
* @return string twig template to render
*
* @api
*
* @since iTop 3.2.1
*/
public function GetTwig(): string
{
return $this->sTwig;
}
/**
* Used by twig templates to get the data for the template to render.
*
* @return array Data used to render the template
*
* @api
*
* @since iTop 3.2.1
*/
public function GetData(): array
{
$this->aData['sTransactionId'] = utils::GetNewTransactionId();
return $this->aData;
}
}

View File

@@ -0,0 +1,61 @@
<?php
/**
* @copyright Copyright (C) 2010-2024 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Portal\Twig;
/**
* Twig context to add additional twigs and data to extensible twigs.
* The additional twigs are ailed at blocks defined in the extensible twig.
*
* @api
*
* @since iTop 3.2.1
*/
class PortalTwigContext
{
private array $aBlockExtension;
public function __construct()
{
$this->aBlockExtension = [];
}
/**
* Add a Twig block extension.
* This method is used by extensions to provide templates.
*
* @api
*
* @param PortalBlockExtension $oBlockExtension Entity containing a twig template and associated data
* @param string $sBlockName Name of the block where to add the twig
*
* @since iTop 3.2.1
*/
function AddBlockExtension(string $sBlockName, PortalBlockExtension $oBlockExtension): void
{
$this->aBlockExtension[$sBlockName] = $oBlockExtension;
}
/**
* Get all the templates to render for a given block.
* This method is used by twig templates to render extensions.
*
* @api
*
* @param string $sBlockName Name of the block currently rendered
*
* @return \Combodo\iTop\Portal\Twig\PortalBlockExtension|null
*
* @since iTop 3.2.1
*/
public function GetBlockExtension(string $sBlockName): ?PortalBlockExtension
{
/** @var PortalBlockExtension $oBlockExtension */
$oBlockExtension = $this->aBlockExtension[$sBlockName] ?? null;
return $oBlockExtension;
}
}

View File

@@ -2,9 +2,9 @@
{# User profile brick base layout #}
{% extends 'itop-portal-base/portal/templates/bricks/layout.html.twig' %}
{% set oContactForm = forms.contact %}
{% set oPreferencesForm = forms.preferences %}
{% set oPasswordForm = forms.password %}
{% if sTab == "" %}
{% set sTab = "user-info" %}
{% endif %}
{% block pPageBodyClass %}{{ parent() }} page_user-profile_brick{% endblock %}
@@ -20,228 +20,82 @@
</div>
{% endif %}
<div id="user-profile-wrapper">
{% block pUserProfileWrapper %}
<div class="row">
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ 'Brick:Portal:UserProfile:PersonalInformations:Title'|dict_s }}</h3>
</div>
<div class="panel-body">
<form id="{{ oContactForm.id }}" class="" method="POST" action="{{ oContactForm.renderer.GetEndpoint()|raw }}">
<input type="hidden" name="transaction_id" value="{{ oContactForm.transaction_id }}" />
<div class="form_alerts">
<div class="alert alert-success" role="alert" style="display: none;"></div>
<div class="alert alert-warning" role="alert" style="display: none;"></div>
<div class="alert alert-error alert-danger" role="alert" style="display: none;"></div>
</div>
<div class="form_fields">
{{ oContactForm.renderer.GetBaseLayout()|raw }}
</div>
</form>
</div>
</div>
</div>
<div class="col-sm-6">
{% if oBrick.GetShowPictureForm() %}
{% block pUserProfilePictureFormContainer %}
<div class="panel panel-default user_profile_picture">
<div class="panel-heading">
<h3 class="panel-title">{{ 'Brick:Portal:UserProfile:Photo:Title'|dict_s }}</h3>
</div>
<div class="panel-body" style="position: relative;">
<div class="form_alerts">
<div class="alert alert-success" role="alert" style="display: none;"></div>
<div class="alert alert-warning" role="alert" style="display: none;"></div>
<div class="alert alert-error alert-danger" role="alert" style="display: none;"></div>
</div>
<form id="picture-form" method="POST" action="{{ app['url_generator'].generate('p_user_profile_brick') }}">
<input type="hidden" name="current_values[form_type]" value="{{ constant('\\Combodo\\iTop\\Portal\\Controller\\UserProfileBrickController::ENUM_FORM_TYPE_PICTURE') }}" />
<input type="hidden" name="operation" value="submit" />
<div class="text-center">
<span class="preview">
<img src="{{ sUserPhotoUrl }}"/>
</span>
<span class="actions">
<span type="button" class="btn btn-default btn_edit">
<span class="fas fa-pencil-alt fa-fw"></span>
<input id="picture" type="file" name="picture" />
</span>
{#<button type="button" class="btn btn-default btn_undo" title="{{ 'UI:Button:ResetImage'|dict_s }}" disabled>
<span class="fas fa-undo fa-fw"></span>
</button>
<button type="button" class="btn btn-default btn_reset" title="{{ 'UI:Button:RemoveImage'|dict_s }}">
<span class="fas fa-trash-o fa-fw"></span>
</button>#}
</span>
</div>
</form>
</div>
</div>
{% endblock %}
{% endif %}
<div class="form_alerts">
{% if sMessage is defined %}
<div id="success-message-content" class="alert alert-success" role="alert">{{ sMessage }}</div>
{% else %}
<div id="success-message-content" style="display:none;" class="alert alert-success" role="alert"></div>
{% endif %}
{% if oBrick.GetShowPreferencesForm() %}
{% block pUserProfilePreferencesFormContainer %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ 'Class:appUserPreferences/Attribute:preferences'|dict_s }}</h3>
</div>
<div class="panel-body">
<form id="{{ oPreferencesForm.id }}" class="" method="POST" action="{{ oPreferencesForm.renderer.GetEndpoint()|raw }}">
<div class="form_alerts">
<div class="alert alert-success" role="alert" style="display: none;"></div>
<div class="alert alert-warning" role="alert" style="display: none;"></div>
<div class="alert alert-error alert-danger" role="alert" style="display: none;"></div>
</div>
<div class="form_fields">
{{ oPreferencesForm.renderer.GetBaseLayout()|raw }}
</div>
</form>
</div>
</div>
{% endblock %}
{% endif %}
{% if sError is defined %}
<div id="error-message-content" class="alert alert-error alert-danger" role="alert">{{ sError }}</div>
{% else %}
<div id="error-message-content" style="display:none;" class="alert alert-error alert-danger" role="alert"></div>
{% endif %}
</div>
<ul class="nav nav-pills _tabs">
{% if aTabsValues is defined and aTabsValues|length > 0 %}
<li{% if sTab == "user-info" %} class="active"{% endif %} data-id="user-info" data-label="{{ 'MyAccount:UserInfo:Tab:Title'|dict_s }}">
<a href="{{ app.url_generator.generate('p_user_profile_brick', {'sBrickId': oBrick.GetId(), 'sDisplayMode': '_self', 'sTab': "user-info"}) }}"
id="btn_tab_for_user-info">
{{ 'MyAccount:UserInfo:Tab:Title'|dict_s }}
</a>
</li>
{% for aTab in aTabsValues %}
<li{% if sTab == aTab.code %} class="active"{% endif %} data-id="{{ aTab.code }}" data-label="{{ aTab.label }}">
<a href="{{ app.url_generator.generate('p_user_profile_brick', {'sBrickId': oBrick.GetId(), 'sDisplayMode': '_self', 'sTab': aTab.code}) }}"
id="btn_tab_for_{{ aTab.code }}">
{{ aTab.label|raw }}
</a>
</li>
{% endfor %}
{% endif %}
</ul>
{% if sTab == "user-info" %}
{% set oContactForm = forms.contact %}
{% set oPreferencesForm = forms.preferences %}
{% set oPasswordForm = forms.password %}
{% include 'itop-portal-base/portal/templates/bricks/user-profile/user_info.html.twig' %}
{% else %}
<div id="user-profile-wrapper">
<div class="row">
{% import "itop-portal-base/portal/templates/helpers/macros.twig" as Macro %}
{{ Macro.BlockExtension(aPluginFormData, 'html', oBrick) }}
</div>
</div>
{% endif %}
{% if oBrick.GetShowPasswordForm() %}
{% block pUserProfilePasswordFormContainer %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ 'Brick:Portal:UserProfile:Password:Title'|dict_s }}</h3>
</div>
<div class="panel-body">
{% if oPasswordForm is not null %}
<form id="{{ oPasswordForm.id }}" class="" method="POST" action="{{ oPasswordForm.renderer.GetEndpoint()|raw }}" autocomplete="off">
<div class="form_alerts">
<div class="alert alert-success" role="alert" style="display: none;"></div>
<div class="alert alert-warning" role="alert" style="display: none;"></div>
<div class="alert alert-error alert-danger" role="alert" style="display: none;"></div>
</div>
<div class="form_fields">
{{ oPasswordForm.renderer.GetBaseLayout()|raw }}
</div>
</form>
{% else %}
{{ 'Brick:Portal:UserProfile:Password:CantChangeContactAdministrator'|dict_format(constant('ITOP_APPLICATION_SHORT')) }}
{% endif %}
</div>
</div>
{% endblock %}
{% endif %}
</div>
</div>
<div class="form_buttons">
{% block pUserProfileFormButtons %}
<div class="form_btn_regular">
{% if sFormMode == constant('\\Combodo\\iTop\\Portal\\Helper\\ObjectFormHandlerHelper::ENUM_MODE_EDIT') %}
<input class="btn btn-primary form_btn_submit" type="submit" value="{{ 'Portal:Button:Submit'|dict_s }}">
{% endif %}
</div>
{% endblock %}
</div>
{% endblock %}
</div>
{% endblock %}
{% block pPageReadyScripts %}
{{ parent() }}
{% import "itop-portal-base/portal/templates/helpers/macros.twig" as Macro %}
{{ Macro.BlockExtension(aPluginFormData, 'ready_script', oBrick) }}
// Personal informations form
var oContactFormFieldSet = $('#{{ oContactForm.id }} > .form_fields').field_set({{ oContactForm.fieldset|json_encode()|raw }});
$('#{{ oContactForm.id }}').portal_form_handler({
formmanager_class: "{{ oContactForm.formmanager_class|escape('js') }}",
formmanager_data: {{ oContactForm.formmanager_data|json_encode()|raw }},
field_set: oContactFormFieldSet,
endpoint: "{{ oContactForm.renderer.GetEndpoint()|raw }}"
});
// Preferences form
var oPreferencesFormFieldSet = $('#{{ oPreferencesForm.id }} > .form_fields').field_set({{ oPreferencesForm.fieldset|json_encode()|raw }});
$('#{{ oPreferencesForm.id }}').portal_form_handler({
formmanager_class: "{{ oPreferencesForm.formmanager_class|escape('js') }}",
formmanager_data: {{ oPreferencesForm.formmanager_data|json_encode()|raw }},
field_set: oPreferencesFormFieldSet,
endpoint: "{{ oPreferencesForm.renderer.GetEndpoint()|raw }}"
});
{% if oPasswordForm is not null %}
// Password form
var oPasswordFormFieldSet = $('#{{ oPasswordForm.id }} > .form_fields').field_set({{ oPasswordForm.fieldset|json_encode()|raw }});
$('#{{ oPasswordForm.id }}').portal_form_handler({
formmanager_class: "{{ oPasswordForm.formmanager_class|escape('js') }}",
formmanager_data: {{ oPasswordForm.formmanager_data|json_encode()|raw }},
field_set: oPasswordFormFieldSet,
endpoint: "{{ oPasswordForm.renderer.GetEndpoint()|raw }}"
});
{% if sTab == "" %}
{% set sTab = "user-info" %}
{% endif %}
// Picture form
// - JQuery upload widget
$('#picture-form #picture').fileupload({
dataType: 'json',
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
disableImageResize: /Android(?!.*Chrome)|Opera/.test(window.navigator.userAgent)
})
.on('fileuploadsend', function(oEvent, oData){
$('.user_profile_picture .form_alerts .alert-error').hide()
$('#page_overlay .overlay_content .content_loader').clone().prependTo('.user_profile_picture .panel-body');
})
.on('fileuploadalways', function(oEvent, oData){
$('.user_profile_picture .content_loader').remove();
})
.on('fileuploaddone', function(oEvent, oData){
if( (oData._response.result.form !== undefined) && (oData._response.result.form.validation.valid === true) )
{
// Retrieving picture url
var sPictureUrl = oData._response.result.form.picture_url;
// Replacing form preview image
$('#picture-form .preview img').attr('src', sPictureUrl);
// Replacing menu image
$('#topbar .user_photo, #sidebar .user_photo').css('background-image', 'url("' + sPictureUrl + '")');
}
})
.on('fileuploadfail', function(oEvent, oData){
if( (oData._response.jqXHR.responseJSON !== undefined) && (oData._response.jqXHR.responseJSON.error_message !== undefined) )
{
$('.user_profile_picture .form_alerts .alert-error').show().text(oData._response.jqXHR.responseJSON.error_message);
}
});
// - Undo button
/*$('#user-profile-wrapper .actions .btn_undo').on('click', function(oEvent){
//console.log('Picture undo trigger');
});*/
// - Reset button
$('#user-profile-wrapper .actions .btn_reset').on('click', function(oEvent){
//console.log('Picture reset trigger');
});
{% if sTab == "user-info" %}
{% set oContactForm = forms.contact %}
{% set oPreferencesForm = forms.preferences %}
{% set oPasswordForm = forms.password %}
{% include 'itop-portal-base/portal/templates/bricks/user-profile/user_info.ready.js.twig' %}
{% endif %}
{% endblock %}
// Submit button
$('#user-profile-wrapper .form_buttons .form_btn_submit').off('click').on('click', function(oEvent){
oEvent.preventDefault();
{% block pPageLiveScriptHelpers %}
{{ parent() }}
{% import "itop-portal-base/portal/templates/helpers/macros.twig" as Macro %}
{{ Macro.BlockExtension(aPluginFormData, 'script', oBrick) }}
{% endblock %}
// Resetting feedback
$('#user-profile-wrapper .form_alerts .alert').hide();
$('#user-profile-wrapper .form_alerts .alert > p').remove();
$('#user-profile-wrapper .form_field').removeClass('has-error');
$('#user-profile-wrapper .form_field .help-block > p').remove();
// Submiting contact form through AJAX
$('#{{ oContactForm.id }}').portal_form_handler('submit', oEvent);
// Submiting preferences form through AJAX
$('#{{ oPreferencesForm.id }}').portal_form_handler('submit', oEvent);
{% if oPasswordForm is not null %}
// Submiting password form through AJAX
// Only if fields are filled
$('#{{ oPasswordForm.id }} :password').each(function(iIndex, oElem){
if($(oElem).val() !== '')
{
$('#{{ oPasswordForm.id }}').portal_form_handler('submit', oEvent);
return false;
}
});
{% endif %}
});
{% endblock %}
{% block pStyleinline %}
{{ parent() }}
<style>
{% import "itop-portal-base/portal/templates/helpers/macros.twig" as Macro %}
{{ Macro.BlockExtension(aPluginFormData, 'css', oBrick) }}
</style>
{% endblock %}

View File

@@ -0,0 +1,119 @@
{# @copyright Copyright (C) 2010-2024 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
<div id="user-profile-wrapper">
{% block pUserProfileWrapper %}
<div class="row">
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ 'Brick:Portal:UserProfile:PersonalInformations:Title'|dict_s }}</h3>
</div>
<div class="panel-body">
<form id="{{ oContactForm.id }}" class="" method="POST" action="{{ oContactForm.renderer.GetEndpoint()|raw }}">
<input type="hidden" name="transaction_id" value="{{ oContactForm.transaction_id }}" />
<div class="form_alerts">
<div class="alert alert-success" role="alert" style="display: none;"></div>
<div class="alert alert-warning" role="alert" style="display: none;"></div>
<div class="alert alert-error alert-danger" role="alert" style="display: none;"></div>
</div>
<div class="form_fields">
{{ oContactForm.renderer.GetBaseLayout()|raw }}
</div>
</form>
</div>
</div>
</div>
<div class="col-sm-6">
{% if oBrick.GetShowPictureForm() %}
{% block pUserProfilePictureFormContainer %}
<div class="panel panel-default user_profile_picture">
<div class="panel-heading">
<h3 class="panel-title">{{ 'Brick:Portal:UserProfile:Photo:Title'|dict_s }}</h3>
</div>
<div class="panel-body" style="position: relative;">
<div class="form_alerts">
<div class="alert alert-success" role="alert" style="display: none;"></div>
<div class="alert alert-warning" role="alert" style="display: none;"></div>
<div class="alert alert-error alert-danger" role="alert" style="display: none;"></div>
</div>
<form id="picture-form" method="POST" action="{{ app['url_generator'].generate('p_user_profile_brick') }}">
<input type="hidden" name="current_values[form_type]" value="{{ constant('\\Combodo\\iTop\\Portal\\Controller\\UserProfileBrickController::ENUM_FORM_TYPE_PICTURE') }}" />
<input type="hidden" name="operation" value="submit" />
<div class="text-center">
<span class="preview">
<img src="{{ sUserPhotoUrl }}"/>
</span>
<span class="actions">
<span type="button" class="btn btn-default btn_edit">
<span class="fas fa-pencil-alt fa-fw"></span>
<input id="picture" type="file" name="picture" />
</span>
</span>
</div>
</form>
</div>
</div>
{% endblock %}
{% endif %}
{% if oBrick.GetShowPreferencesForm() %}
{% block pUserProfilePreferencesFormContainer %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ 'Class:appUserPreferences/Attribute:preferences'|dict_s }}</h3>
</div>
<div class="panel-body">
<form id="{{ oPreferencesForm.id }}" class="" method="POST" action="{{ oPreferencesForm.renderer.GetEndpoint()|raw }}">
<div class="form_alerts">
<div class="alert alert-success" role="alert" style="display: none;"></div>
<div class="alert alert-warning" role="alert" style="display: none;"></div>
<div class="alert alert-error alert-danger" role="alert" style="display: none;"></div>
</div>
<div class="form_fields">
{{ oPreferencesForm.renderer.GetBaseLayout()|raw }}
</div>
</form>
</div>
</div>
{% endblock %}
{% endif %}
{% if oBrick.GetShowPasswordForm() %}
{% block pUserProfilePasswordFormContainer %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ 'Brick:Portal:UserProfile:Password:Title'|dict_s }}</h3>
</div>
<div class="panel-body">
{% if oPasswordForm is not null %}
<form id="{{ oPasswordForm.id }}" class="" method="POST" action="{{ oPasswordForm.renderer.GetEndpoint()|raw }}" autocomplete="off">
<div class="form_alerts">
<div class="alert alert-success" role="alert" style="display: none;"></div>
<div class="alert alert-warning" role="alert" style="display: none;"></div>
<div class="alert alert-error alert-danger" role="alert" style="display: none;"></div>
</div>
<div class="form_fields">
{{ oPasswordForm.renderer.GetBaseLayout()|raw }}
</div>
</form>
{% else %}
{{ 'Brick:Portal:UserProfile:Password:CantChangeContactAdministrator'|dict_format(constant('ITOP_APPLICATION_SHORT')) }}
{% endif %}
</div>
</div>
{% endblock %}
{% endif %}
</div>
</div>
<div class="form_buttons">
{% block pUserProfileFormButtons %}
<div class="form_btn_regular">
{% if sFormMode == constant('\\Combodo\\iTop\\Portal\\Helper\\ObjectFormHandlerHelper::ENUM_MODE_EDIT') %}
<input class="btn btn-primary form_btn_submit" type="submit" value="{{ 'Portal:Button:Submit'|dict_s }}">
{% endif %}
</div>
{% endblock %}
</div>
{% endblock %}
</div>

View File

@@ -0,0 +1,100 @@
{# @copyright Copyright (C) 2010-2024 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
// Personal informations form
var oContactFormFieldSet = $('#{{ oContactForm.id }} > .form_fields').field_set({{ oContactForm.fieldset|json_encode()|raw }});
$('#{{ oContactForm.id }}').portal_form_handler({
formmanager_class: "{{ oContactForm.formmanager_class|escape('js') }}",
formmanager_data: {{ oContactForm.formmanager_data|json_encode()|raw }},
field_set: oContactFormFieldSet,
endpoint: "{{ oContactForm.renderer.GetEndpoint()|raw }}"
});
// Preferences form
var oPreferencesFormFieldSet = $('#{{ oPreferencesForm.id }} > .form_fields').field_set({{ oPreferencesForm.fieldset|json_encode()|raw }});
$('#{{ oPreferencesForm.id }}').portal_form_handler({
formmanager_class: "{{ oPreferencesForm.formmanager_class|escape('js') }}",
formmanager_data: {{ oPreferencesForm.formmanager_data|json_encode()|raw }},
field_set: oPreferencesFormFieldSet,
endpoint: "{{ oPreferencesForm.renderer.GetEndpoint()|raw }}"
});
{% if oPasswordForm is not null %}
// Password form
var oPasswordFormFieldSet = $('#{{ oPasswordForm.id }} > .form_fields').field_set({{ oPasswordForm.fieldset|json_encode()|raw }});
$('#{{ oPasswordForm.id }}').portal_form_handler({
formmanager_class: "{{ oPasswordForm.formmanager_class|escape('js') }}",
formmanager_data: {{ oPasswordForm.formmanager_data|json_encode()|raw }},
field_set: oPasswordFormFieldSet,
endpoint: "{{ oPasswordForm.renderer.GetEndpoint()|raw }}"
});
{% endif %}
// Picture form
// - JQuery upload widget
$('#picture-form #picture').fileupload({
dataType: 'json',
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
disableImageResize: /Android(?!.*Chrome)|Opera/.test(window.navigator.userAgent)
})
.on('fileuploadsend', function(oEvent, oData){
$('.user_profile_picture .form_alerts .alert-error').hide()
$('#page_overlay .overlay_content .content_loader').clone().prependTo('.user_profile_picture .panel-body');
})
.on('fileuploadalways', function(oEvent, oData){
$('.user_profile_picture .content_loader').remove();
})
.on('fileuploaddone', function(oEvent, oData){
if( (oData._response.result.form !== undefined) && (oData._response.result.form.validation.valid === true) )
{
// Retrieving picture url
var sPictureUrl = oData._response.result.form.picture_url;
// Replacing form preview image
$('#picture-form .preview img').attr('src', sPictureUrl);
// Replacing menu image
$('#topbar .user_photo, #sidebar .user_photo').css('background-image', 'url("' + sPictureUrl + '")');
}
})
.on('fileuploadfail', function(oEvent, oData){
if( (oData._response.jqXHR.responseJSON !== undefined) && (oData._response.jqXHR.responseJSON.error_message !== undefined) )
{
$('.user_profile_picture .form_alerts .alert-error').show().text(oData._response.jqXHR.responseJSON.error_message);
}
});
// - Undo button
/*$('#user-profile-wrapper .actions .btn_undo').on('click', function(oEvent){
//console.log('Picture undo trigger');
});*/
// - Reset button
$('#user-profile-wrapper .actions .btn_reset').on('click', function(oEvent){
//console.log('Picture reset trigger');
});
// Submit button
$('#user-profile-wrapper .form_buttons .form_btn_submit').off('click').on('click', function(oEvent){
oEvent.preventDefault();
// Resetting feedback
$('#user-profile-wrapper .form_alerts .alert').hide();
$('#user-profile-wrapper .form_alerts .alert > p').remove();
$('#user-profile-wrapper .form_field').removeClass('has-error');
$('#user-profile-wrapper .form_field .help-block > p').remove();
// Submiting contact form through AJAX
$('#{{ oContactForm.id }}').portal_form_handler('submit', oEvent);
// Submiting preferences form through AJAX
$('#{{ oPreferencesForm.id }}').portal_form_handler('submit', oEvent);
{% if oPasswordForm is not null %}
// Submiting password form through AJAX
// Only if fields are filled
$('#{{ oPasswordForm.id }} :password').each(function(iIndex, oElem){
if($(oElem).val() !== '')
{
$('#{{ oPasswordForm.id }}').portal_form_handler('submit', oEvent);
return false;
}
});
{% endif %}
});

View File

@@ -0,0 +1,13 @@
{# @copyright Copyright (C) 2010-2024 Combodo SAS #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% macro BlockExtension(aPluginFormData, sBlockName, oBrick) %}
{% for oPortalData in aPluginFormData %}
{% if (oPortalData is defined and oPortalData.GetBlockExtension(sBlockName)) %}
{% set oBlockExtension = oPortalData.GetBlockExtension(sBlockName) %}
{% set sTwig = oBlockExtension.GetTwig() %}
{% set aData = oBlockExtension.GetData() %}
{% include sTwig ignore missing %}
{% endif %}
{% endfor %}
{% endmacro %}

View File

@@ -78,9 +78,9 @@
{% block pStyleinline %}
{# UI Extensions inline CSS #}
{% if app['ui_extensions_helper'].css_inline is not null %}
<style>
{{ app['ui_extensions_helper'].css_inline|raw }}
</style>
<style>
{{ app['ui_extensions_helper'].css_inline|raw }}
</style>
{% endif %}
{% endblock %}

View File

@@ -6,5 +6,70 @@ $vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Combodo\\iTop\\Portal\\Brick\\AbstractBrick' => $baseDir . '/src/Brick/AbstractBrick.php',
'Combodo\\iTop\\Portal\\Brick\\AggregatePageBrick' => $baseDir . '/src/Brick/AggregatePageBrick.php',
'Combodo\\iTop\\Portal\\Brick\\BrickCollection' => $baseDir . '/src/Brick/BrickCollection.php',
'Combodo\\iTop\\Portal\\Brick\\BrickNotFoundException' => $baseDir . '/src/Brick/BrickNotFoundException.php',
'Combodo\\iTop\\Portal\\Brick\\BrowseBrick' => $baseDir . '/src/Brick/BrowseBrick.php',
'Combodo\\iTop\\Portal\\Brick\\CreateBrick' => $baseDir . '/src/Brick/CreateBrick.php',
'Combodo\\iTop\\Portal\\Brick\\FilterBrick' => $baseDir . '/src/Brick/FilterBrick.php',
'Combodo\\iTop\\Portal\\Brick\\ManageBrick' => $baseDir . '/src/Brick/ManageBrick.php',
'Combodo\\iTop\\Portal\\Brick\\PortalBrick' => $baseDir . '/src/Brick/PortalBrick.php',
'Combodo\\iTop\\Portal\\Brick\\PropertyNotFoundException' => $baseDir . '/src/Brick/PropertyNotFoundException.php',
'Combodo\\iTop\\Portal\\Brick\\UserProfileBrick' => $baseDir . '/src/Brick/UserProfileBrick.php',
'Combodo\\iTop\\Portal\\Controller\\AbstractController' => $baseDir . '/src/Controller/AbstractController.php',
'Combodo\\iTop\\Portal\\Controller\\AggregatePageBrickController' => $baseDir . '/src/Controller/AggregatePageBrickController.php',
'Combodo\\iTop\\Portal\\Controller\\BrickController' => $baseDir . '/src/Controller/BrickController.php',
'Combodo\\iTop\\Portal\\Controller\\BrowseBrickController' => $baseDir . '/src/Controller/BrowseBrickController.php',
'Combodo\\iTop\\Portal\\Controller\\CreateBrickController' => $baseDir . '/src/Controller/CreateBrickController.php',
'Combodo\\iTop\\Portal\\Controller\\DefaultController' => $baseDir . '/src/Controller/DefaultController.php',
'Combodo\\iTop\\Portal\\Controller\\ManageBrickController' => $baseDir . '/src/Controller/ManageBrickController.php',
'Combodo\\iTop\\Portal\\Controller\\ObjectController' => $baseDir . '/src/Controller/ObjectController.php',
'Combodo\\iTop\\Portal\\Controller\\SessionMessageController' => $baseDir . '/src/Controller/SessionMessageController.php',
'Combodo\\iTop\\Portal\\Controller\\UserProfileBrickController' => $baseDir . '/src/Controller/UserProfileBrickController.php',
'Combodo\\iTop\\Portal\\DependencyInjection\\SilexCompatBootstrap\\PortalXmlConfiguration\\AbstractConfiguration' => $baseDir . '/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/AbstractConfiguration.php',
'Combodo\\iTop\\Portal\\DependencyInjection\\SilexCompatBootstrap\\PortalXmlConfiguration\\Basic' => $baseDir . '/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Basic.php',
'Combodo\\iTop\\Portal\\DependencyInjection\\SilexCompatBootstrap\\PortalXmlConfiguration\\Forms' => $baseDir . '/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Forms.php',
'Combodo\\iTop\\Portal\\DependencyInjection\\SilexCompatBootstrap\\PortalXmlConfiguration\\Lists' => $baseDir . '/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Lists.php',
'Combodo\\iTop\\Portal\\EventListener\\ApplicationContextSetPluginPropertyClass' => $baseDir . '/src/EventListener/ApplicationContextSetPluginPropertyClass.php',
'Combodo\\iTop\\Portal\\EventListener\\ApplicationContextSetUrlMakerClass' => $baseDir . '/src/EventListener/ApplicationContextSetUrlMakerClass.php',
'Combodo\\iTop\\Portal\\EventListener\\CssFromSassCompiler' => $baseDir . '/src/EventListener/CssFromSassCompiler.php',
'Combodo\\iTop\\Portal\\EventListener\\ExceptionListener' => $baseDir . '/src/EventListener/ExceptionListener.php',
'Combodo\\iTop\\Portal\\EventListener\\UserProvider' => $baseDir . '/src/EventListener/UserProvider.php',
'Combodo\\iTop\\Portal\\Form\\ObjectFormManager' => $baseDir . '/src/Form/ObjectFormManager.php',
'Combodo\\iTop\\Portal\\Form\\PasswordFormManager' => $baseDir . '/src/Form/PasswordFormManager.php',
'Combodo\\iTop\\Portal\\Form\\PreferencesFormManager' => $baseDir . '/src/Form/PreferencesFormManager.php',
'Combodo\\iTop\\Portal\\Helper\\ApplicationHelper' => $baseDir . '/src/Helper/ApplicationHelper.php',
'Combodo\\iTop\\Portal\\Helper\\BrickControllerHelper' => $baseDir . '/src/Helper/BrickControllerHelper.php',
'Combodo\\iTop\\Portal\\Helper\\BrowseBrickHelper' => $baseDir . '/src/Helper/BrowseBrickHelper.php',
'Combodo\\iTop\\Portal\\Helper\\ContextManipulatorHelper' => $baseDir . '/src/Helper/ContextManipulatorHelper.php',
'Combodo\\iTop\\Portal\\Helper\\ExtensibilityHelper' => $baseDir . '/src/Helper/ExtensibilityHelper.php',
'Combodo\\iTop\\Portal\\Helper\\LifecycleValidatorHelper' => $baseDir . '/src/Helper/LifecycleValidatorHelper.php',
'Combodo\\iTop\\Portal\\Helper\\NavigationRuleHelper' => $baseDir . '/src/Helper/NavigationRuleHelper.php',
'Combodo\\iTop\\Portal\\Helper\\ObjectFormHandlerHelper' => $baseDir . '/src/Helper/ObjectFormHandlerHelper.php',
'Combodo\\iTop\\Portal\\Helper\\RequestManipulatorHelper' => $baseDir . '/src/Helper/RequestManipulatorHelper.php',
'Combodo\\iTop\\Portal\\Helper\\ScopeValidatorHelper' => $baseDir . '/src/Helper/ScopeValidatorHelper.php',
'Combodo\\iTop\\Portal\\Helper\\SecurityHelper' => $baseDir . '/src/Helper/SecurityHelper.php',
'Combodo\\iTop\\Portal\\Helper\\SessionMessageHelper' => $baseDir . '/src/Helper/SessionMessageHelper.php',
'Combodo\\iTop\\Portal\\Helper\\UIExtensionsHelper' => $baseDir . '/src/Helper/UIExtensionsHelper.php',
'Combodo\\iTop\\Portal\\Hook\\iAbstractPortalTabContentExtension' => $baseDir . '/src/Hook/iAbstractPortalTabContentExtension.php',
'Combodo\\iTop\\Portal\\Hook\\iAbstractPortalTabExtension' => $baseDir . '/src/Hook/iAbstractPortalTabExtension.php',
'Combodo\\iTop\\Portal\\Hook\\iUserProfileTabContentExtension' => $baseDir . '/src/Hook/iUserProfileTabContentExtension.php',
'Combodo\\iTop\\Portal\\Hook\\iUserProfileTabExtension' => $baseDir . '/src/Hook/iUserProfileTabExtension.php',
'Combodo\\iTop\\Portal\\Kernel' => $baseDir . '/src/Kernel.php',
'Combodo\\iTop\\Portal\\Routing\\ItopExtensionsExtraRoutes' => $baseDir . '/src/Routing/ItopExtensionsExtraRoutes.php',
'Combodo\\iTop\\Portal\\Routing\\UrlGenerator' => $baseDir . '/src/Routing/UrlGenerator.php',
'Combodo\\iTop\\Portal\\Twig\\AppExtension' => $baseDir . '/src/Twig/AppExtension.php',
'Combodo\\iTop\\Portal\\Twig\\AppGlobal' => $baseDir . '/src/Twig/AppGlobal.php',
'Combodo\\iTop\\Portal\\Twig\\AppVariable' => $baseDir . '/src/Twig/AppVariable.php',
'Combodo\\iTop\\Portal\\Twig\\CKEditorExtension' => $baseDir . '/src/Twig/CKEditorExtension.php',
'Combodo\\iTop\\Portal\\Twig\\CurrentUserAccessor' => $baseDir . '/src/Twig/CurrentUserAccessor.php',
'Combodo\\iTop\\Portal\\Twig\\PortalBlockExtension' => $baseDir . '/src/Twig/PortalBlockExtension.php',
'Combodo\\iTop\\Portal\\Twig\\PortalTwigContext' => $baseDir . '/src/Twig/PortalTwigContext.php',
'Combodo\\iTop\\Portal\\UrlMaker\\AbstractPortalUrlMaker' => $baseDir . '/src/UrlMaker/AbstractPortalUrlMaker.php',
'Combodo\\iTop\\Portal\\VariableAccessor\\AbstractStringVariableAccessor' => $baseDir . '/src/VariableAccessor/AbstractStringVariableAccessor.php',
'Combodo\\iTop\\Portal\\VariableAccessor\\AbstractVariableAccessor' => $baseDir . '/src/VariableAccessor/AbstractVariableAccessor.php',
'Combodo\\iTop\\Portal\\VariableAccessor\\CombodoCurrentContactPhotoUrl' => $baseDir . '/src/VariableAccessor/CombodoCurrentContactPhotoUrl.php',
'Combodo\\iTop\\Portal\\VariableAccessor\\CombodoPortalInstanceConf' => $baseDir . '/src/VariableAccessor/CombodoPortalInstanceConf.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);

View File

@@ -29,6 +29,7 @@ class ComposerAutoloaderInitdf408f3f8ea034d298269cdf7647358b
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitdf408f3f8ea034d298269cdf7647358b::getInitializer($loader));
$loader->setClassMapAuthoritative(true);
$loader->register(true);
return $loader;

View File

@@ -26,6 +26,71 @@ class ComposerStaticInitdf408f3f8ea034d298269cdf7647358b
);
public static $classMap = array (
'Combodo\\iTop\\Portal\\Brick\\AbstractBrick' => __DIR__ . '/../..' . '/src/Brick/AbstractBrick.php',
'Combodo\\iTop\\Portal\\Brick\\AggregatePageBrick' => __DIR__ . '/../..' . '/src/Brick/AggregatePageBrick.php',
'Combodo\\iTop\\Portal\\Brick\\BrickCollection' => __DIR__ . '/../..' . '/src/Brick/BrickCollection.php',
'Combodo\\iTop\\Portal\\Brick\\BrickNotFoundException' => __DIR__ . '/../..' . '/src/Brick/BrickNotFoundException.php',
'Combodo\\iTop\\Portal\\Brick\\BrowseBrick' => __DIR__ . '/../..' . '/src/Brick/BrowseBrick.php',
'Combodo\\iTop\\Portal\\Brick\\CreateBrick' => __DIR__ . '/../..' . '/src/Brick/CreateBrick.php',
'Combodo\\iTop\\Portal\\Brick\\FilterBrick' => __DIR__ . '/../..' . '/src/Brick/FilterBrick.php',
'Combodo\\iTop\\Portal\\Brick\\ManageBrick' => __DIR__ . '/../..' . '/src/Brick/ManageBrick.php',
'Combodo\\iTop\\Portal\\Brick\\PortalBrick' => __DIR__ . '/../..' . '/src/Brick/PortalBrick.php',
'Combodo\\iTop\\Portal\\Brick\\PropertyNotFoundException' => __DIR__ . '/../..' . '/src/Brick/PropertyNotFoundException.php',
'Combodo\\iTop\\Portal\\Brick\\UserProfileBrick' => __DIR__ . '/../..' . '/src/Brick/UserProfileBrick.php',
'Combodo\\iTop\\Portal\\Controller\\AbstractController' => __DIR__ . '/../..' . '/src/Controller/AbstractController.php',
'Combodo\\iTop\\Portal\\Controller\\AggregatePageBrickController' => __DIR__ . '/../..' . '/src/Controller/AggregatePageBrickController.php',
'Combodo\\iTop\\Portal\\Controller\\BrickController' => __DIR__ . '/../..' . '/src/Controller/BrickController.php',
'Combodo\\iTop\\Portal\\Controller\\BrowseBrickController' => __DIR__ . '/../..' . '/src/Controller/BrowseBrickController.php',
'Combodo\\iTop\\Portal\\Controller\\CreateBrickController' => __DIR__ . '/../..' . '/src/Controller/CreateBrickController.php',
'Combodo\\iTop\\Portal\\Controller\\DefaultController' => __DIR__ . '/../..' . '/src/Controller/DefaultController.php',
'Combodo\\iTop\\Portal\\Controller\\ManageBrickController' => __DIR__ . '/../..' . '/src/Controller/ManageBrickController.php',
'Combodo\\iTop\\Portal\\Controller\\ObjectController' => __DIR__ . '/../..' . '/src/Controller/ObjectController.php',
'Combodo\\iTop\\Portal\\Controller\\SessionMessageController' => __DIR__ . '/../..' . '/src/Controller/SessionMessageController.php',
'Combodo\\iTop\\Portal\\Controller\\UserProfileBrickController' => __DIR__ . '/../..' . '/src/Controller/UserProfileBrickController.php',
'Combodo\\iTop\\Portal\\DependencyInjection\\SilexCompatBootstrap\\PortalXmlConfiguration\\AbstractConfiguration' => __DIR__ . '/../..' . '/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/AbstractConfiguration.php',
'Combodo\\iTop\\Portal\\DependencyInjection\\SilexCompatBootstrap\\PortalXmlConfiguration\\Basic' => __DIR__ . '/../..' . '/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Basic.php',
'Combodo\\iTop\\Portal\\DependencyInjection\\SilexCompatBootstrap\\PortalXmlConfiguration\\Forms' => __DIR__ . '/../..' . '/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Forms.php',
'Combodo\\iTop\\Portal\\DependencyInjection\\SilexCompatBootstrap\\PortalXmlConfiguration\\Lists' => __DIR__ . '/../..' . '/src/DependencyInjection/SilexCompatBootstrap/PortalXmlConfiguration/Lists.php',
'Combodo\\iTop\\Portal\\EventListener\\ApplicationContextSetPluginPropertyClass' => __DIR__ . '/../..' . '/src/EventListener/ApplicationContextSetPluginPropertyClass.php',
'Combodo\\iTop\\Portal\\EventListener\\ApplicationContextSetUrlMakerClass' => __DIR__ . '/../..' . '/src/EventListener/ApplicationContextSetUrlMakerClass.php',
'Combodo\\iTop\\Portal\\EventListener\\CssFromSassCompiler' => __DIR__ . '/../..' . '/src/EventListener/CssFromSassCompiler.php',
'Combodo\\iTop\\Portal\\EventListener\\ExceptionListener' => __DIR__ . '/../..' . '/src/EventListener/ExceptionListener.php',
'Combodo\\iTop\\Portal\\EventListener\\UserProvider' => __DIR__ . '/../..' . '/src/EventListener/UserProvider.php',
'Combodo\\iTop\\Portal\\Form\\ObjectFormManager' => __DIR__ . '/../..' . '/src/Form/ObjectFormManager.php',
'Combodo\\iTop\\Portal\\Form\\PasswordFormManager' => __DIR__ . '/../..' . '/src/Form/PasswordFormManager.php',
'Combodo\\iTop\\Portal\\Form\\PreferencesFormManager' => __DIR__ . '/../..' . '/src/Form/PreferencesFormManager.php',
'Combodo\\iTop\\Portal\\Helper\\ApplicationHelper' => __DIR__ . '/../..' . '/src/Helper/ApplicationHelper.php',
'Combodo\\iTop\\Portal\\Helper\\BrickControllerHelper' => __DIR__ . '/../..' . '/src/Helper/BrickControllerHelper.php',
'Combodo\\iTop\\Portal\\Helper\\BrowseBrickHelper' => __DIR__ . '/../..' . '/src/Helper/BrowseBrickHelper.php',
'Combodo\\iTop\\Portal\\Helper\\ContextManipulatorHelper' => __DIR__ . '/../..' . '/src/Helper/ContextManipulatorHelper.php',
'Combodo\\iTop\\Portal\\Helper\\ExtensibilityHelper' => __DIR__ . '/../..' . '/src/Helper/ExtensibilityHelper.php',
'Combodo\\iTop\\Portal\\Helper\\LifecycleValidatorHelper' => __DIR__ . '/../..' . '/src/Helper/LifecycleValidatorHelper.php',
'Combodo\\iTop\\Portal\\Helper\\NavigationRuleHelper' => __DIR__ . '/../..' . '/src/Helper/NavigationRuleHelper.php',
'Combodo\\iTop\\Portal\\Helper\\ObjectFormHandlerHelper' => __DIR__ . '/../..' . '/src/Helper/ObjectFormHandlerHelper.php',
'Combodo\\iTop\\Portal\\Helper\\RequestManipulatorHelper' => __DIR__ . '/../..' . '/src/Helper/RequestManipulatorHelper.php',
'Combodo\\iTop\\Portal\\Helper\\ScopeValidatorHelper' => __DIR__ . '/../..' . '/src/Helper/ScopeValidatorHelper.php',
'Combodo\\iTop\\Portal\\Helper\\SecurityHelper' => __DIR__ . '/../..' . '/src/Helper/SecurityHelper.php',
'Combodo\\iTop\\Portal\\Helper\\SessionMessageHelper' => __DIR__ . '/../..' . '/src/Helper/SessionMessageHelper.php',
'Combodo\\iTop\\Portal\\Helper\\UIExtensionsHelper' => __DIR__ . '/../..' . '/src/Helper/UIExtensionsHelper.php',
'Combodo\\iTop\\Portal\\Hook\\iAbstractPortalTabContentExtension' => __DIR__ . '/../..' . '/src/Hook/iAbstractPortalTabContentExtension.php',
'Combodo\\iTop\\Portal\\Hook\\iAbstractPortalTabExtension' => __DIR__ . '/../..' . '/src/Hook/iAbstractPortalTabExtension.php',
'Combodo\\iTop\\Portal\\Hook\\iUserProfileTabContentExtension' => __DIR__ . '/../..' . '/src/Hook/iUserProfileTabContentExtension.php',
'Combodo\\iTop\\Portal\\Hook\\iUserProfileTabExtension' => __DIR__ . '/../..' . '/src/Hook/iUserProfileTabExtension.php',
'Combodo\\iTop\\Portal\\Kernel' => __DIR__ . '/../..' . '/src/Kernel.php',
'Combodo\\iTop\\Portal\\Routing\\ItopExtensionsExtraRoutes' => __DIR__ . '/../..' . '/src/Routing/ItopExtensionsExtraRoutes.php',
'Combodo\\iTop\\Portal\\Routing\\UrlGenerator' => __DIR__ . '/../..' . '/src/Routing/UrlGenerator.php',
'Combodo\\iTop\\Portal\\Twig\\AppExtension' => __DIR__ . '/../..' . '/src/Twig/AppExtension.php',
'Combodo\\iTop\\Portal\\Twig\\AppGlobal' => __DIR__ . '/../..' . '/src/Twig/AppGlobal.php',
'Combodo\\iTop\\Portal\\Twig\\AppVariable' => __DIR__ . '/../..' . '/src/Twig/AppVariable.php',
'Combodo\\iTop\\Portal\\Twig\\CKEditorExtension' => __DIR__ . '/../..' . '/src/Twig/CKEditorExtension.php',
'Combodo\\iTop\\Portal\\Twig\\CurrentUserAccessor' => __DIR__ . '/../..' . '/src/Twig/CurrentUserAccessor.php',
'Combodo\\iTop\\Portal\\Twig\\PortalBlockExtension' => __DIR__ . '/../..' . '/src/Twig/PortalBlockExtension.php',
'Combodo\\iTop\\Portal\\Twig\\PortalTwigContext' => __DIR__ . '/../..' . '/src/Twig/PortalTwigContext.php',
'Combodo\\iTop\\Portal\\UrlMaker\\AbstractPortalUrlMaker' => __DIR__ . '/../..' . '/src/UrlMaker/AbstractPortalUrlMaker.php',
'Combodo\\iTop\\Portal\\VariableAccessor\\AbstractStringVariableAccessor' => __DIR__ . '/../..' . '/src/VariableAccessor/AbstractStringVariableAccessor.php',
'Combodo\\iTop\\Portal\\VariableAccessor\\AbstractVariableAccessor' => __DIR__ . '/../..' . '/src/VariableAccessor/AbstractVariableAccessor.php',
'Combodo\\iTop\\Portal\\VariableAccessor\\CombodoCurrentContactPhotoUrl' => __DIR__ . '/../..' . '/src/VariableAccessor/CombodoCurrentContactPhotoUrl.php',
'Combodo\\iTop\\Portal\\VariableAccessor\\CombodoPortalInstanceConf' => __DIR__ . '/../..' . '/src/VariableAccessor/CombodoPortalInstanceConf.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);