Customer portal : User Profile brick that allows basic Contact informations edition, password / preferences change from the portal

SVN:trunk[4068]
This commit is contained in:
Guillaume Lajarige
2016-05-12 10:22:23 +00:00
parent 6540c547a4
commit 6297809716
14 changed files with 812 additions and 83 deletions

View File

@@ -67,6 +67,8 @@ Dict::Add('EN US', 'English', 'English', array(
'Brick:Portal:UserProfile:Password:Title' => 'Password',
'Brick:Portal:UserProfile:Password:ChoosePassword' => 'Choose password',
'Brick:Portal:UserProfile:Password:ConfirmPassword' => 'Confirm password',
'Brick:Portal:UserProfile:Password:CantChangeContactAdministrator' => 'To change your password, please contact your iTop administrator',
'Brick:Portal:UserProfile:Password:CantChangeForUnknownReason' => 'Can\'t change password, please contact your iTop administrator',
'Brick:Portal:UserProfile:PersonalInformations:Title' => 'Personal informations',
'Brick:Portal:UserProfile:Photo:Title' => 'Photo',
));

View File

@@ -67,6 +67,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Brick:Portal:UserProfile:Password:Title' => 'Mot de passe',
'Brick:Portal:UserProfile:Password:ChoosePassword' => 'Choisissez un mot de passe',
'Brick:Portal:UserProfile:Password:ConfirmPassword' => 'Confirmer le mot de passe',
'Brick:Portal:UserProfile:Password:CantChangeContactAdministrator' => 'Veuillez vous adresser à votre administrateur iTop pour changer votre mot de passe',
'Brick:Portal:UserProfile:Password:CantChangeForUnknownReason' => 'Impossible de modifier votre mot de passe, veuillez contacter votre administrateur iTop',
'Brick:Portal:UserProfile:PersonalInformations:Title' => 'Informations personnelles',
'Brick:Portal:UserProfile:Photo:Title' => 'Photo',
));

View File

@@ -25,7 +25,6 @@ use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\RedirectResponse;
use \Symfony\Component\HttpKernel\HttpKernelInterface;
use \Exception;
use \SecurityException;
use \FileUploadException;
use \utils;
use \Dict;
@@ -36,10 +35,8 @@ use \BinaryExpression;
use \FieldExpression;
use \VariableExpression;
use \DBObjectSet;
use \CMDBObject;
use \cmdbAbstractObject;
use \UserRights;
use \Combodo\iTop\Portal\Brick\BrowseBrick;
use \Combodo\iTop\Portal\Helper\ApplicationHelper;
use \Combodo\iTop\Portal\Helper\SecurityHelper;
use \Combodo\iTop\Portal\Helper\ContextManipulatorHelper;
@@ -133,7 +130,9 @@ class ObjectController extends AbstractController
}
// Checking security layers
if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_MODIFY, $sObjectClass, $sObjectId))
// Warning : This is a dirty quick fix to allow editing its own contact information
$bAllowWrite = ($sObjectClass === 'Person' && $sObjectId == UserRights::GetContactId());
if (!SecurityHelper::IsActionAllowed($oApp, UR_ACTION_MODIFY, $sObjectClass, $sObjectId) && !$bAllowWrite)
{
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
@@ -432,7 +431,6 @@ class ObjectController extends AbstractController
$aCallbackUrls = $oApp['context_manipulator']->GetCallbackUrls($oApp, $aActionRules, $oObject, $bModal);
$aFormData['submit_callback'] = $aCallbackUrls['submit'];
$aFormData['cancel_callback'] = $aCallbackUrls['cancel'];
//var_dump($aFormData);
// Preparing renderer
// Note : We might need to distinguish form & renderer endpoints
@@ -501,9 +499,9 @@ class ObjectController extends AbstractController
// Otherwise, we show the object if there is no default
else
{
$aFormData['validation']['redirection'] = array(
'alternative_url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oFormManager->GetObject()->GetKey()))
);
// $aFormData['validation']['redirection'] = array(
// 'alternative_url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oFormManager->GetObject()->GetKey()))
// );
}
}
break;

View File

@@ -19,12 +19,16 @@
namespace Combodo\iTop\Portal\Controller;
use \Exception;
use \UserRights;
use \Silex\Application;
use \Symfony\Component\HttpFoundation\Request;
use \Combodo\iTop\Portal\Helper\ApplicationHelper;
use \Combodo\iTop\Portal\Brick\UserProfileBrick;
use \Combodo\iTop\Portal\Controller\ObjectController;
use \Combodo\iTop\Portal\Form\PreferencesFormManager;
use \Combodo\iTop\Portal\Form\PasswordFormManager;
use \Combodo\iTop\Renderer\Bootstrap\BsFormRenderer;
class UserProfileBrickController extends BrickController
{
@@ -55,22 +59,165 @@ class UserProfileBrickController extends BrickController
}
$aData = array();
// Retrieving current contact
$oCurContact = UserRights::GetContactObject();
$sCurContactClass = get_class($oCurContact);
$sCurContactId = $oCurContact->GetKey();
// Preparing contact form
$aData['forms']['contact'] = ObjectController::HandleForm($oRequest, $oApp, ObjectController::ENUM_MODE_EDIT, $sCurContactClass, $sCurContactId);
// var_dump($aData['forms']['contact']);
// die();
// If this is ajax call, we are just submiting preferences or password forms
if ($oRequest->isXmlHttpRequest())
{
$aCurrentValues = $oRequest->request->get('current_values');
$sFormType = $aCurrentValues['form_type'];
if ($sFormType === PreferencesFormManager::FORM_TYPE)
{
$aData['form'] = $this->HandlePreferencesForm($oRequest, $oApp);
}
elseif ($sFormType === PasswordFormManager::FORM_TYPE)
{
$aData['form'] = $this->HandlePasswordForm($oRequest, $oApp);
}
else
{
throw new Exception('Unknown form type.');
}
$oResponse = $oApp->json($aData);
}
// Else, we are displaying page for first time
else
{
// Retrieving current contact
$oCurContact = UserRights::GetContactObject();
$sCurContactClass = get_class($oCurContact);
$sCurContactId = $oCurContact->GetKey();
$aData = $aData + array(
'oBrick' => $oBrick
// Preparing forms
$aData['forms']['contact'] = ObjectController::HandleForm($oRequest, $oApp, ObjectController::ENUM_MODE_EDIT, $sCurContactClass, $sCurContactId, $oBrick->GetForm());
$aData['forms']['preferences'] = $this->HandlePreferencesForm($oRequest, $oApp);
// - If user can change password, we display the form
$aData['forms']['password'] = (UserRights::CanChangePassword()) ? $this->HandlePasswordForm($oRequest, $oApp) : null;
$aData = $aData + array(
'oBrick' => $oBrick
);
$oResponse = $oApp['twig']->render($oBrick->GetPageTemplatePath(), $aData);
}
return $oResponse;
}
public function HandlePreferencesForm(Request $oRequest, Application $oApp)
{
$aFormData = array();
$oRequestParams = $oRequest->request;
// Handling form
$sOperation = $oRequestParams->get('operation');
// - Create
if ($sOperation === null)
{
// - Creating renderer
$oFormRenderer = new BsFormRenderer();
$oFormRenderer->SetEndpoint($_SERVER['REQUEST_URI']);
// - Creating manager
$oFormManager = new PreferencesFormManager();
$oFormManager->SetRenderer($oFormRenderer)
->Build();
}
// - Submit
else if ($sOperation === 'submit')
{
$sFormManagerClass = $oRequestParams->get('formmanager_class');
$sFormManagerData = $oRequestParams->get('formmanager_data');
if ($sFormManagerClass === null || $sFormManagerData === null)
{
$oApp->abort(500, 'Parameters formmanager_class and formmanager_data must be defined.');
}
// Rebuilding manager from json
$oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
// Applying modification to object
$aFormData['validation'] = $oFormManager->OnSubmit(array('currentValues' => $oRequestParams->get('current_values')));
// Reloading page only if preferences were changed
if (($aFormData['validation']['valid'] === true) && !empty($aFormData['validation']['messages']['success']))
{
$aFormData['validation']['redirection'] = array(
'url' => $oApp['url_generator']->generate('p_user_profile_brick'),
);
}
}
else
{
// Else, submit from another form
}
// Preparing field_set data
$aFieldSetData = array(
'fields_list' => $oFormManager->GetRenderer()->Render(),
'fields_impacts' => $oFormManager->GetForm()->GetFieldsImpacts(),
'form_path' => $oFormManager->GetForm()->GetId()
);
return $oApp['twig']->render($oBrick->GetPageTemplatePath(), $aData);
// Preparing form data
$aFormData['id'] = $oFormManager->GetForm()->GetId();
$aFormData['formmanager_class'] = $oFormManager->GetClass();
$aFormData['formmanager_data'] = $oFormManager->ToJSON();
$aFormData['renderer'] = $oFormManager->GetRenderer();
$aFormData['fieldset'] = $aFieldSetData;
return $aFormData;
}
public function HandlePasswordForm(Request $oRequest, Application $oApp)
{
$aFormData = array();
$oRequestParams = $oRequest->request;
// Handling form
$sOperation = $oRequestParams->get('operation');
// - Create
if ($sOperation === null)
{
// - Creating renderer
$oFormRenderer = new BsFormRenderer();
$oFormRenderer->SetEndpoint($_SERVER['REQUEST_URI']);
// - Creating manager
$oFormManager = new PasswordFormManager();
$oFormManager->SetRenderer($oFormRenderer)
->Build();
}
// - Submit
else if ($sOperation === 'submit')
{
$sFormManagerClass = $oRequestParams->get('formmanager_class');
$sFormManagerData = $oRequestParams->get('formmanager_data');
if ($sFormManagerClass === null || $sFormManagerData === null)
{
$oApp->abort(500, 'Parameters formmanager_class and formmanager_data must be defined.');
}
// Rebuilding manager from json
$oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
// Applying modification to object
$aFormData['validation'] = $oFormManager->OnSubmit(array('currentValues' => $oRequestParams->get('current_values')));
}
else
{
// Else, submit from another form
}
// Preparing field_set data
$aFieldSetData = array(
'fields_list' => $oFormManager->GetRenderer()->Render(),
'fields_impacts' => $oFormManager->GetForm()->GetFieldsImpacts(),
'form_path' => $oFormManager->GetForm()->GetId()
);
// Preparing form data
$aFormData['id'] = $oFormManager->GetForm()->GetId();
$aFormData['formmanager_class'] = $oFormManager->GetClass();
$aFormData['formmanager_data'] = $oFormManager->ToJSON();
$aFormData['renderer'] = $oFormManager->GetRenderer();
$aFormData['fieldset'] = $aFieldSetData;
return $aFormData;
}
}

View File

@@ -18,6 +18,8 @@
namespace Combodo\iTop\Portal\Brick;
use \DOMFormatException;
use \Combodo\iTop\DesignElement;
use \Combodo\iTop\Portal\Brick\PortalBrick;
/**
@@ -34,6 +36,114 @@ class UserProfileBrick extends PortalBrick
const DEFAUT_TITLE = 'Brick:Portal:UserProfile:Title';
static $sRouteName = 'p_user_profile_brick';
protected $aForm;
public function __construct()
{
parent::__construct();
$this->aForm = array(
'id' => 'default-user-profile',
'type' => 'zlist',
'fields' => 'details',
'layout' => null
);
}
/**
*
* @return array
*/
public function GetForm()
{
return $this->aForm;
}
/**
*
* @param array $aForm
* @return \Combodo\iTop\Portal\Brick\UserProfileBrick
*/
public function SetForm($aForm)
{
$this->aForm = $aForm;
return $this;
}
/**
* Load the brick's data from the xml passed as a ModuleDesignElement.
* This is used to set all the brick attributes at once.
*
* @param \Combodo\iTop\DesignElement $oMDElement
* @return UserProfileBrick
* @throws DOMFormatException
*/
public function LoadFromXml(DesignElement $oMDElement)
{
parent::LoadFromXml($oMDElement);
// Checking specific elements
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)
{
$this->aForm['type'] = 'custom_list';
$this->aForm['fields'] = array();
foreach ($oBrickSubNode->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;
}
$this->aForm['fields'][$sFieldId] = $aField;
}
else
{
throw new DOMFormatException('Field tag must have an id attribute', null, null, $oFormNode);
}
}
}
// Parsing presentation
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'));
$sXml = preg_replace('/^.+\n/', '', $sXml);
$sXml = preg_replace('/\n.+$/', '', $sXml);
$this->aForm['layout'] = array(
'type' => (preg_match('/\{\{|\{\#|\{\%/', $sXml) === 1) ? 'twig' : 'xhtml',
'content' => $sXml
);
}
break;
}
}
return $this;
}
}
?>

View File

@@ -20,10 +20,10 @@
namespace Combodo\iTop\Portal\Form;
use \Exception;
use \DOMFormatException;
use \Silex\Application;
use \utils;
use \Dict;
use \UserRights;
use \MetaModel;
use \CMDBSource;
use \DBObject;
@@ -31,21 +31,11 @@ use \DBObjectSet;
use \DBObjectSearch;
use \DBObjectSetComparator;
use \InlineImage;
use \UserRights;
use \AttributeDateTime;
use \Combodo\iTop\Form\FormManager;
use \Combodo\iTop\Form\Form;
use \Combodo\iTop\Form\Field\FileUploadField;
use \Combodo\iTop\Form\Field\HiddenField;
use \Combodo\iTop\Form\Field\LabelField;
use \Combodo\iTop\Form\Field\StringField;
use \Combodo\iTop\Form\Field\TextAreaField;
use \Combodo\iTop\Form\Field\SelectField;
use \Combodo\iTop\Form\Field\RadioField;
use \Combodo\iTop\Form\Field\CheckboxField;
use \Combodo\iTop\Form\Validator\IntegerValidator;
use \Combodo\iTop\Form\Validator\NotEmptyValidator;
use \Combodo\iTop\Renderer\Bootstrap\BsFormRenderer;
/**
* Description of objectformmanager
@@ -110,6 +100,12 @@ class ObjectFormManager extends FormManager
$oFormManager->SetActionRulesToken($aJson['formactionrulestoken']);
}
// Retrieving form properties
if (isset($aJson['formproperties']))
{
$oFormManager->SetFormProperties($aJson['formproperties']);
}
// Retrieving callback urls
if (!isset($aJson['formcallbacks']))
{
@@ -215,6 +211,10 @@ class ObjectFormManager extends FormManager
*/
public function SetFormProperties($aFormProperties)
{
// echo '<pre>';
// print_r($aFormProperties);
// echo '</pre>';
// die();
$this->aFormProperties = $aFormProperties;
return $this;
}
@@ -256,6 +256,7 @@ class ObjectFormManager extends FormManager
$aJson['formobject_id'] = $this->oObject->GetKey();
$aJson['formmode'] = $this->sMode;
$aJson['formactionrulestoken'] = $this->sActionRulesToken;
$aJson['formproperties'] = $this->aFormProperties;
return $aJson;
}
@@ -433,7 +434,7 @@ class ObjectFormManager extends FormManager
$oAttDef = MetaModel::GetAttributeDef(get_class($this->oObject), $sAttCode);
// TODO : Make AttributeDefinition::MakeFormField() for all kind of fields
if (in_array(get_class($oAttDef), array('AttributeString', 'AttributeText', 'AttributeLongText', 'AttributeCaseLog', 'AttributeHTML', 'AttributeFriendlyName', 'AttributeEnum', 'AttributeExternalKey', 'AttributeCustomFields', 'AttributeLinkedSet', 'AttributeLinkedSetIndirect', 'AttributeDate', 'AttributeDateTime')))
if (in_array(get_class($oAttDef), array('AttributeString', 'AttributeEmailAddress', 'AttributeText', 'AttributeLongText', 'AttributeCaseLog', 'AttributeHTML', 'AttributeFriendlyName', 'AttributeEnum', 'AttributeExternalKey', 'AttributeCustomFields', 'AttributeLinkedSet', 'AttributeLinkedSetIndirect', 'AttributeDate', 'AttributeDateTime')))
{
$oField = $oAttDef->MakeFormField($this->oObject);
@@ -631,10 +632,20 @@ class ObjectFormManager extends FormManager
// The try catch is essentially to start a MySQL transaction in order to ensure that all or none objects are persisted when creating an object with links
try
{
$sObjectClass = get_class($this->oObject);
// Starting transaction
CMDBSource::Query('START TRANSACTION');
// Forcing allowed writing on the object if necessary. This is used in some particular cases.
$bAllowWrite = ($sObjectClass === 'Person' && $this->oObject->GetKey() == UserRights::GetContactId());
if ($bAllowWrite)
{
$this->oObject->AllowWrite(true);
}
// Writing object to DB
$bActivateTriggers = (!$this->oObject->IsNew() && $this->oObject->IsModified());
$bWasModified = $this->oObject->IsModified();
$this->oObject->DBWrite();
// Finalizing images link to object, otherwise it will be cleaned by the GC
InlineImage::FinalizeInlineImages($this->oObject);
@@ -655,7 +666,7 @@ class ObjectFormManager extends FormManager
$sTriggersQuery = $this->oApp['combodo.portal.instance.conf']['properties']['triggers_query'];
if ($sTriggersQuery !== null)
{
$aParentClasses = MetaModel::EnumParentClasses(get_class($this->oObject), ENUM_PARENT_CLASSES_ALL);
$aParentClasses = MetaModel::EnumParentClasses($sObjectClass, ENUM_PARENT_CLASSES_ALL);
$oTriggerSet = new DBObjectSet(DBObjectSearch::FromOQL($sTriggersQuery), array(), array('parent_classes' => $aParentClasses));
while ($oTrigger = $oTriggerSet->Fetch())
{
@@ -668,7 +679,10 @@ class ObjectFormManager extends FormManager
// Ending transaction with a commit as everything was fine
CMDBSource::Query('COMMIT');
$aData['messages']['success'] += array('_main' => array(Dict::S('Brick:Portal:Object:Form:Message:Saved')));
if ($bWasModified)
{
$aData['messages']['success'] += array('_main' => array(Dict::S('Brick:Portal:Object:Form:Message:Saved')));
}
}
catch (Exception $e)
{
@@ -810,7 +824,11 @@ class ObjectFormManager extends FormManager
}
}
// Then we build and update form
$this->SetFormProperties($aFormProperties);
// - We update form properties only we don't have any yet. This is a fallback for cases when form properties where not among the JSON data
if ($this->GetFormProperties() === null)
{
$this->SetFormProperties($aFormProperties);
}
$this->Build();
}

View File

@@ -0,0 +1,179 @@
<?php
// Copyright (C) 2010-2016 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 <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Form;
use \Exception;
use \CMDBSource;
use \Dict;
use \UserRights;
use \Combodo\iTop\Form\FormManager;
use \Combodo\iTop\Form\Form;
use \Combodo\iTop\Form\Field\HiddenField;
use \Combodo\iTop\Form\Field\PasswordField;
/**
* Description of passwordformmanager
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
*/
class PasswordFormManager extends FormManager
{
const FORM_TYPE = 'change_password';
public function Build()
{
// Building the form
$oForm = new Form('change_password');
// Adding hidden field with form type
$oField = new HiddenField('form_type');
$oField->SetCurrentValue('change_password');
$oForm->AddField($oField);
// Adding old password field
$oField = new PasswordField('old_password');
$oField->SetMandatory(true)
->SetLabel(Dict::S('UI:Login:OldPasswordPrompt'));
$oForm->AddField($oField);
// Adding new password field
$oField = new PasswordField('new_password');
$oField->SetMandatory(true)
->SetLabel(Dict::S('Brick:Portal:UserProfile:Password:ChoosePassword'));
$oForm->AddField($oField);
// Adding confirm password field
$oField = new PasswordField('confirm_password');
$oField->SetMandatory(true)
->SetLabel(Dict::S('Brick:Portal:UserProfile:Password:ConfirmPassword'));
$oForm->AddField($oField);
$oForm->Finalize();
$this->oForm = $oForm;
$this->oRenderer->SetForm($this->oForm);
}
/**
* Validates the form and returns an array with the validation status and the messages.
* If the form is valid, creates/updates the object.
*
* eg :
* array(
* 'status' => true|false
* 'messages' => array(
* 'errors' => array()
* )
*
* @param array $aArgs
* @return array
*/
public function OnSubmit($aArgs = null)
{
$aData = array(
'valid' => true,
'messages' => array(
'success' => array(),
'warnings' => array(), // Not used as of today, just to show that the structure is ready for change like this.
'error' => array()
)
);
// Update object and form
$this->OnUpdate($aArgs);
// Check if form valid
if ($this->oForm->Validate())
{
// The try catch is essentially to start a MySQL transaction
try
{
// Updating password
$sAuthUser = $_SESSION['auth_user'];
$sOldPassword = $this->oForm->GetField('old_password')->GetCurrentValue();
$sNewPassword = $this->oForm->GetField('new_password')->GetCurrentValue();
$sConfirmPassword = $this->oForm->GetField('confirm_password')->GetCurrentValue();
if ($sOldPassword !== '' && $sNewPassword !== '' && $sConfirmPassword !== '')
{
if (!UserRights::CanChangePassword())
{
$aData['valid'] = false;
$aData['messages']['error'] += array('_main' => array(Dict::S('Brick:Portal:UserProfile:Password:CantChangeContactAdministrator')));
}
else if (!UserRights::CheckCredentials($sAuthUser, $sOldPassword))
{
$aData['valid'] = false;
$aData['messages']['error'] += array('old_password' => array(Dict::S('UI:Login:IncorrectOldPassword')));
}
else if ($sNewPassword !== $sConfirmPassword)
{
$aData['valid'] = false;
$aData['messages']['error'] += array('confirm_password' => array(Dict::S('UI:Login:RetypePwdDoesNotMatch')));
}
else if (!UserRights::ChangePassword($sOldPassword, $sNewPassword))
{
$aData['valid'] = false;
$aData['messages']['error'] += array('confirm_password' => array(Dict::S('Brick:Portal:UserProfile:Password:CantChangeForUnknownReason')));
}
else
{
$aData['messages']['success'] += array('_main' => array(Dict::S('Brick:Portal:Object:Form:Message:Saved')));
}
}
}
catch (Exception $e)
{
$aData['valid'] = false;
$aData['messages']['error'] += array('_main' => array($e->getMessage()));
}
}
else
{
// Handle errors
$aData['valid'] = false;
$aData['messages']['error'] += $this->oForm->GetErrorMessages();
}
return $aData;
}
public function OnUpdate($aArgs = null)
{
// We build the form
$this->Build();
// Then we update it with new values
if (is_array($aArgs))
{
if (isset($aArgs['currentValues']))
{
foreach ($aArgs['currentValues'] as $sPreferenceName => $value)
{
$this->oForm->GetField($sPreferenceName)->SetCurrentValue($value);
}
}
}
}
public function OnCancel($aArgs = null)
{
}
}

View File

@@ -0,0 +1,169 @@
<?php
// Copyright (C) 2010-2016 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 <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Form;
use \Exception;
use \CMDBSource;
use \Dict;
use \UserRights;
use \Combodo\iTop\Form\FormManager;
use \Combodo\iTop\Form\Form;
use \Combodo\iTop\Form\Field\HiddenField;
use \Combodo\iTop\Form\Field\SelectField;
/**
* Description of preferencesformmanager
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
*/
class PreferencesFormManager extends FormManager
{
const FORM_TYPE = 'preferences';
public function Build()
{
// Building the form
$oForm = new Form('preferences');
// Adding hidden field with form type
$oField = new HiddenField('form_type');
$oField->SetCurrentValue('preferences');
$oForm->AddField($oField);
// Adding language field
$oField = new SelectField('language');
$oField->SetMandatory(true)
->SetLabel(Dict::S('UI:Favorites:SelectYourLanguage'))
->SetCurrentValue(Dict::GetUserLanguage())
->SetStartsWithNullChoice(false);
// - Preparing choices
foreach (Dict::GetLanguages() as $sCode => $aLanguage)
{
$oField->AddChoice($sCode, $aLanguage['description'] . ' (' . $aLanguage['localized_description'] . ')');
}
// - Adding to form
$oForm->AddField($oField);
$oForm->Finalize();
$this->oForm = $oForm;
$this->oRenderer->SetForm($this->oForm);
}
/**
* Validates the form and returns an array with the validation status and the messages.
* If the form is valid, creates/updates the object.
*
* eg :
* array(
* 'status' => true|false
* 'messages' => array(
* 'errors' => array()
* )
*
* @param array $aArgs
* @return array
*/
public function OnSubmit($aArgs = null)
{
$aData = array(
'valid' => true,
'messages' => array(
'success' => array(),
'warnings' => array(), // Not used as of today, just to show that the structure is ready for change like this.
'error' => array()
)
);
// Update object and form
$this->OnUpdate($aArgs);
// Check if form valid
if ($this->oForm->Validate())
{
// The try catch is essentially to start a MySQL transaction
try
{
// Starting transaction
CMDBSource::Query('START TRANSACTION');
$iFieldChanged = 0;
// Updating user
$oCurUser = UserRights::GetUserObject();
// - Language
$sLanguage = $this->oForm->GetField('language')->GetCurrentValue();
if (($sLanguage !== null) && ($oCurUser->Get('language') !== $sLanguage))
{
$oCurUser->Set('language', $sLanguage);
$iFieldChanged++;
}
// Updating only if preferences changed
if ($iFieldChanged > 0)
{
$oCurUser->DBUpdate();
$aData['messages']['success'] += array('_main' => array(Dict::S('Brick:Portal:Object:Form:Message:Saved')));
}
// Ending transaction with a commit as everything was fine
CMDBSource::Query('COMMIT');
}
catch (Exception $e)
{
// End transaction with a rollback as something failed
CMDBSource::Query('ROLLBACK');
$aData['valid'] = false;
$aData['messages']['error'] += array('_main' => array($e->getMessage()));
}
}
else
{
// Handle errors
$aData['valid'] = false;
$aData['messages']['error'] += $this->oForm->GetErrorMessages();
}
return $aData;
}
public function OnUpdate($aArgs = null)
{
// We build the form
$this->Build();
// Then we update it with new values
if (is_array($aArgs))
{
if (isset($aArgs['currentValues']))
{
foreach ($aArgs['currentValues'] as $sPreferenceName => $value)
{
$this->oForm->GetField($sPreferenceName)->SetCurrentValue($value);
}
}
}
}
public function OnCancel($aArgs = null)
{
}
}

View File

@@ -122,6 +122,23 @@ class ApplicationHelper
self::LoadClasses($sScannedDir, 'brick.class.inc.php', 'brick');
}
/**
* Loads form managers from the base portal
*
* @param string $sScannedDir Directory to load the managers from
* @throws \Exception
*/
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
*
@@ -581,7 +598,7 @@ class ApplicationHelper
// $oApp['combodo.portal.instance.routes'] = $aRoutes;
// }
// Checking brick security
if ($oBrick->IsGrantedForProfiles(UserRights::ListProfiles()))
if ($oBrick->GetActive() && $oBrick->IsGrantedForProfiles(UserRights::ListProfiles()))
{
$aPortalConf['bricks'][] = $oBrick;
$aPortalConf['bricks_total_width'] += $oBrick->GetWidth();

View File

@@ -2,13 +2,16 @@
{# Browse brick base layout #}
{% extends 'itop-portal-base/portal/src/views/bricks/layout.html.twig' %}
{% set oContactForm = forms.contact %}
{% set oPreferencesForm = forms.preferences %}
{% set oPasswordForm = forms.password %}
{% block pMainHeaderTitle %}
{{ oBrick.GetTitle()|dict_s }}
{% endblock %}
{% block pMainContentHolder%}
<form class="">
<div id="user-profile-wrapper">
<div class="row">
<div class="col-sm-6">
<div class="panel panel-default">
@@ -16,30 +19,17 @@
<h3 class="panel-title">{{ 'Brick:Portal:UserProfile:PersonalInformations:Title'|dict_s }}</h3>
</div>
<div class="panel-body">
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Nom</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="Lajarige" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Prénom</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="Guillaume" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Email</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="guillaume.lajarige@combodo.com" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Téléphone</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="0625540067" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Organisation</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="Combodo" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Site</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="Siège" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Fonction</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="Ingénieur R&D" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Manager</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="John Doe" class="form-control" maxlength="255" /></div>
</div>
<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>
@@ -60,9 +50,16 @@
<h3 class="panel-title">{{ 'Class:appUserPreferences/Attribute:preferences'|dict_s }}</h3>
</div>
<div class="panel-body">
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">{{ 'UI:FavoriteLanguage'|dict_s }}</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="Français" class="form-control" maxlength="255" /></div>
</div>
<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>
<div class="panel panel-default">
@@ -70,15 +67,96 @@
<h3 class="panel-title">{{ 'Brick:Portal:UserProfile:Password:Title'|dict_s }}</h3>
</div>
<div class="panel-body">
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">{{ 'Brick:Portal:UserProfile:Password:ChoosePassword'|dict_s }}</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">{{ 'Brick:Portal:UserProfile:Password:ConfirmPassword'|dict_s }}</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="" class="form-control" maxlength="255" /></div>
</div>
{% if oPasswordForm is not null %}
<form id="{{ oPasswordForm.id }}" class="" method="POST" action="{{ oPasswordForm.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">
{{ oPasswordForm.renderer.GetBaseLayout()|raw }}
</div>
</form>
{% else %}
pas le droit
{% endif %}
</div>
</div>
</div>
</div>
<div class="form_buttons">
<div class="form_btn_regular">
<input class="btn btn-primary form_btn_submit" type="submit" value="{{ 'Portal:Button:Submit'|dict_s }}">
</div>
</div>
</form>
</div>
{% endblock %}
{% block pPageReadyScripts %}
{{ parent() }}
// 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 %}
// 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
//if($('#{{ oContactForm.id }} .field_set').field_set('hasTouchedFields'))
//{
$('#{{ oContactForm.id }}').portal_form_handler('submit', oEvent);
//}
// Submiting preferences form through AJAX
//if($('#{{ oPreferencesForm.id }} .field_set').field_set('hasTouchedFields'))
//{
$('#{{ 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 %}

View File

@@ -39,15 +39,15 @@ require_once __DIR__ . '/../src/providers/scopevalidatorserviceprovider.class.in
require_once __DIR__ . '/../src/helpers/scopevalidatorhelper.class.inc.php';
require_once __DIR__ . '/../src/helpers/securityhelper.class.inc.php';
require_once __DIR__ . '/../src/helpers/applicationhelper.class.inc.php';
// Forms
require_once __DIR__ . '/../src/forms/objectformmanager.class.inc.php';
use \Exception;
use \Symfony\Component\HttpFoundation\Response;
use \Combodo\iTop\Portal\Helper\ApplicationHelper;
// Checking user rights and prompt if needed
LoginWebPage::DoLoginEx(PORTAL_ID);
if (UserRights::GetContactId() == 0)
{
die(Dict::S('Portal:ErrorNoContactForThisUser'));
}
// Initializing Silex framework
$oApp = new Silex\Application();
@@ -81,6 +81,7 @@ ApplicationHelper::LoadControllers();
ApplicationHelper::LoadRouters();
ApplicationHelper::RegisterRoutes($oApp);
ApplicationHelper::LoadBricks();
ApplicationHelper::LoadFormManagers();
ApplicationHelper::RegisterTwigExtensions($oApp);
// Loading portal configuration from the module design

View File

@@ -81,7 +81,7 @@ $(function()
var oValidation = oData.form.validation;
// First we build the form
me.options.field_set.field_set('option', 'fields_list', oData.form.fields_list);
me.options.field_set.field_set('option', 'fields_list', oData.form.fieldset.fields_list);
me.options.field_set.field_set('option', 'is_valid', oValidation.valid);
me.options.field_set.field_set('buildForm');
@@ -299,6 +299,10 @@ $(function()
{
$('#page_overlay').fadeOut(200);
},
submit: function(oEvent)
{
this._onSubmitClick(oEvent);
},
getAttachmentIds: function()
{
var me = this;

View File

@@ -231,11 +231,6 @@ $(function()
}
return this.options.is_valid;
},
// Debug helper
showOptions: function()
{
return this.options;
},
_loadCssFile: function(url)
{
if (!$('link[href="' + url + '"]').length)
@@ -322,6 +317,15 @@ $(function()
this.options.style_element.text(this.buildData.style_code);
eval(this.options.script_element.text());
}
},
hasTouchedFields: function()
{
return (this.options.touched_fields.length > 0);
},
// Debug helper
showOptions: function()
{
return this.options;
},
});
});

View File

@@ -78,7 +78,7 @@ $(function()
var value = null;
this.element.find(':input').each(function(iIndex, oElem){
if($(oElem).is(':hidden') || $(oElem).is(':text') || $(oElem).is('textarea'))
if($(oElem).is(':hidden') || $(oElem).is(':text') || $(oElem).is(':password') || $(oElem).is('textarea'))
{
value = $(oElem).val();
}