diff --git a/datamodels/2.x/itop-portal-base/en.dict.itop-portal-base.php b/datamodels/2.x/itop-portal-base/en.dict.itop-portal-base.php index d7cc82ea2..89ef41ba4 100644 --- a/datamodels/2.x/itop-portal-base/en.dict.itop-portal-base.php +++ b/datamodels/2.x/itop-portal-base/en.dict.itop-portal-base.php @@ -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', )); diff --git a/datamodels/2.x/itop-portal-base/fr.dict.itop-portal-base.php b/datamodels/2.x/itop-portal-base/fr.dict.itop-portal-base.php index 982282fd4..5d6969039 100644 --- a/datamodels/2.x/itop-portal-base/fr.dict.itop-portal-base.php +++ b/datamodels/2.x/itop-portal-base/fr.dict.itop-portal-base.php @@ -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', )); diff --git a/datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php index 5ced83695..ffff48b8f 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php +++ b/datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php @@ -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; diff --git a/datamodels/2.x/itop-portal-base/portal/src/controllers/userprofilebrickcontroller.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/controllers/userprofilebrickcontroller.class.inc.php index 83cdbe816..3cd3f2932 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/controllers/userprofilebrickcontroller.class.inc.php +++ b/datamodels/2.x/itop-portal-base/portal/src/controllers/userprofilebrickcontroller.class.inc.php @@ -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; } } diff --git a/datamodels/2.x/itop-portal-base/portal/src/entities/userprofilebrick.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/entities/userprofilebrick.class.inc.php index 726bca926..8af0388a1 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/entities/userprofilebrick.class.inc.php +++ b/datamodels/2.x/itop-portal-base/portal/src/entities/userprofilebrick.class.inc.php @@ -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; + } + } ?> \ No newline at end of file diff --git a/datamodels/2.x/itop-portal-base/portal/src/forms/objectformmanager.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/forms/objectformmanager.class.inc.php index be88126ce..078b94be9 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/forms/objectformmanager.class.inc.php +++ b/datamodels/2.x/itop-portal-base/portal/src/forms/objectformmanager.class.inc.php @@ -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 '
'; +// print_r($aFormProperties); +// echo ''; +// 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(); } diff --git a/datamodels/2.x/itop-portal-base/portal/src/forms/passwordformmanager.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/forms/passwordformmanager.class.inc.php new file mode 100644 index 000000000..44abbe84c --- /dev/null +++ b/datamodels/2.x/itop-portal-base/portal/src/forms/passwordformmanager.class.inc.php @@ -0,0 +1,179 @@ + + +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