TYPO3 Fluid Form returns old values - typo3

Am running a form using fluid with two fields, username and password. When first run, correct data is sent to the action. When different data is resubmitted, original data is sent to the controller action instead of the new values. How do I solve this problem?
EDIT: I've discovered that an array containing the user-submitted form fields and their values are obtained from the request, serialized as is and the string stored in a hidden input field called '__referrer[arguments]', which is then submitted with the form back to the user. When the user resubmits the form again with new values, the user doesn't realize that the old values are in the form, in the form of a serialized string, which is submitted together with the new values. Turns out, this is ok if no errors are reported by the validators. In that case the data is simply passed to the controller action and processing continues. But if errors are collected, processing is not sent to the controller action but is sent to the error action. The error action unserializes the old data and forwards that data (instead of the the new values) to the intended action controller. See \TYPO3\CMS\Extbase\Mvc\Controller\ActionController::forwardToReferringRequest().
EDIT: Steps to reproduce;
Create an action with one argument and create a form with one input element and a submit button. Create a validator for the element. Make it a string property. When all is said and done, start by sending a VALID value and let the form return. it will come back with no errors. but also if you look at it's hidden values, you find that the __referrer[arguments] has changes. That's because your previous values were serialized and are there. Now submit an INVALID value and check values entering your action. You'll be stunned they are the old ones.
This is weird. Currently if I disable the production of the __referrer[arguments] input field in the \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::renderHiddenReferrerFields() method, everything works properly. How do I solve this? Please help.

Where things go wrong:
Here: \TYPO3\CMS\Extbase\Mvc\Controller\ActionController::forwardToReferringRequest() . In this method, arguments are sought from __referrer internal arguments instead of the submitted values. Normally, if errors are found, only two elements are found in the arguments form variable: controller for controller name and action for action name. This is because they were submitted originally with the form and they will recycle without changing as long as the validator finds errors. Nothing else will be added to it. This is good... until it validates. When it validates, results are sent directly to the action controller and not through the error controller. When the process goes back to the form, the object or arguments submitted will be added to the form and returned to the user. Remember it validated. When you then send a wrong value next, it goes to the error controller, but the old values (that validated) and were serialized in the __referrer[arguments] are unserialized and forwarded to your action. And that's how you end up with old values in your action. This is because it assumes that the variable carries only two values inside it; the controller and action names only. It's wrong.
Assumption:
Form post values processing seem to be built under the presumption that once a form validates, you won't need it again.
News.
The values you send are sought from extbase arguments and serialized in \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::renderHiddenReferrerFields() method, added to the form just before presenting it to you. All TYPO3 has to do is skip your values and let the default values only be serialized. Just saw that the doc section of the function has this:
/**
* Renders hidden form fields for referrer information about
* the current controller and action.
*
* #return string Hidden fields with referrer information
* #todo filter out referrer information that is equal to the target (e.g. same packageKey)
*/
The #todo part is the news. Hope it's done in the next-patch since there are many scenarios where you want to resume the form such as in data entry.
Solution
Easiest solution: Initialize the empty argument in your action and hand the argument to the form. In my case, I use a DTO.
Example
public function accountLoginAction(DTO\Account\LoginDTO $accountLogin=null):ResponseInterface
{
if($accountLogin){
$this->repository->logon();
...
} else{
$accountLogin=DTO\Account\LoginDTO::getNewInstance();
}
$this->view->assign('object', $accountLogin);
return $this->response();
}
Hope it helps someone.

Related

TYPO3 How do i avoid form objectID manipulation?

I would like to avoid the manipulation of the hidden field (__identify) in a form. For example the edit form. If someone goes to the inspector and change the value to another uid then the update action will actually update the manipulated value instead of the original.
Now if someone changes this to 8 then the update action will update the object with the uid 8.
Is there a way to avoid such action?
TYPO3: v9
Mode: Composer Mode
Best regards
Thanks to #Daniel Siepmann (typo3.slack.com) for pointing me to the right direction. So the answer is simple and easy to implement.
TYPO3 uses hmac for internal purposes and has a static function
called hmac under the GeneralUtility class.
Concept:
We create a hidden field in the form with a hmac string based on the uid of the object and a word of your choice. (To make the decryption more difficult for the attacker). Then on the controller we regenerate the hmac with the uid that has been passed via the form arguments to the controller and the word we previously defined. If they match, then the object can be updated. If not, then we redirect the user to another page (Error or list view, it is up to you).
How to use it:
your_extension/Classes/Controller/YourController.php
public function editAction(Object $object)
{
$hmac = GeneralUtility::hmac($object->getUid(), 'yourWord');
$this->view->assign('hmac', $hmac);
$this->view->assign('object', $object);
}
Here we generate the hmac based on the object uid and a word that you can alone specify. Then we pass it to the FrontEnd in order to add it on the hidden field and later to compare it.
VERY IMPORTANT: I would strongly recommend to use a word as well. It must be the same everywhere you use it. For me now the word is yourWord.
your_extension/Resources/Private/Templates/Edit.html
<f:form action="update" name="object" object="{object}" extensionName="ExtensionName" pageUid="{settings.flexform.pages.update.pid}" enctype="multipart/form-data">
<f:form.hidden name="hmac" value="{hmac}" />
{...}
</f:form>
Here we define the hidden field with the hmac value. We are going to compare it in the controller.
your_extension/Classes/Controller/YourController.php
public function initializeUpdateAction() {
$args = $this->request->getArguments();
/*Check if the user has not deleted the hmac hidden field*/
if ($args['hmac']) {
/*Regenerate the hmac to compare it with the one from the $args variable*/
$hmac = GeneralUtility::hmac($args['object']['__identity'], 'yourWord');
if ($hmac !== $args['hmac']) {
$this->redirect('list', 'ControllerName', 'ExtensionName', null, $this->settings['global']['error']['pid']);
}
}
else {
$this->redirect('list', 'ControllerName', 'ExtensionName', null, $this->settings['global']['error']['pid']);
}
}
Here we first evaluate if the hmac exists. The user might have deleted the hidden field to avoid the comparisson. If TYPO3 does not find any hmac in the passed arguments ($args['hmac']) then it will redirect the user to the specified page and the object won't be updated.
If TYPO3 finds a hmac, then generates another hmac with the given uid ($args['object']['__identity']) and the word you generated the previous hmac. If it does not match, that means that the user has manipulated the uid. Then TYPO3 redirects the user to the specified page and the object won't be updated.
All this could be written more elegantly but for the sake of this answer, i tried to make it short.
Best regards

In SAP scripts how do you define which data is sent to an element

I need to make some changes to an SAPScript. I have the program and form name
Program: RBOSORDER01
Form: RBOSORDER02
I am looking to change some of the data shown in the form. I have debugged the program and I get see the call to write to the form, for example:
CALL FUNCTION 'WRITE_FORM'
EXPORTING
ELEMENT = 'ITEM_TEXT'
EXCEPTIONS
ELEMENT = 1
WINDOW = 2.
But how is the data passed between the program and the form. I cannot link between each. I was expecting to see a structure or a data element passed with 'ITEM_TEXT' and then this data is printed at this element "ITEM_TEXT" in the form but the link is not clear to me.
I have looked at the form also in SE71 and cannot see where you define this. Where is the link here, what am I missing?
This is in the form, so SE71 is what you need. You have to find the window first, where this element (ITEM_TEXT) is displayed, than look for the element and see what is displayed inside. The SAPSript form uses the global variables (structures, internal tables) of the print program directly by default (there are some other options as well, INCLUDE texts for example). So for example if a global variable gv_text is declared in the print program, and it is displayed in the SAPScript, than it will look like &GV_TEXT& in the form.
You can also debug the SAPScript if you switch on debugging in SE71 (can be painful, if the form is big).
Function 'WRITE_FORM' just calls the EntryPoint of the Form (SE71 / RBOSORDER02) in this case with ELEMENT='ITEM_TEXT'.
So you will end up in MAIN-Window at:
/E ITEM_TEXT
/: INCLUDE &VBDPA-TDNAME& OBJECT VBBP ID 0001 PARAGRAPH IT
In this case you have to debug what "VBDPA-TDNAME" is at this time and then you will find its value with transaction "SO10" (Standard-Text)
The INCLUDE can be a complex text and can have its own format strings.
As Jozsef said before, VBDPA-TDNAME is defined global in the print programm. (SE38n / RBOSORDER01)

MS Access - me.recordset not passing to sub

I have a form that loads a single record. The user does what they need to do on the form...in this case, they enter a date, and a button becomes available to click to advance the record to the next step in the process.
I have a public function that is logging the activity to tblActivity, and sets the record's new Status and Location. This Function takes 3 variables, and was working fine until today.
'I'm calling the function with this line from the button's Click event
LogActivity 15, Screen.ActiveForm, Me.Recordset
Public Function LogActivity(ByVal lSID As Long, Optional fForm As Form, Optional ByRef fRS As Recordset)
With fRS
Do Until .EOF
Debug.Print .Fields(5)
.MoveNext
Loop
End With
...
End Function
This should be printing the form's Status value, but fRS is passed in with no values. The form's recordset has values prior to being passed as the form has data. Some how it is getting lost in the pass. This was working fine, I have multiple buttons across 5 different forms that all call this same Function. Suddenly today, none of them can pass the recordset. I can think of nothing that was changed that would effect this. Most of the changes recently involved locking down fields and the appearance of buttons at the right time...nothing related to the recordset.
Naturally, this DB is supposed to go live on Monday.
Found the problem.
I had a backup from yesterday that was working fine.
One by one, I went through the changes I logged from yesterday and found that by changing some fields to .enabled = False and .locked = True is what was doing it. Apparently that was enough to clear all the values when passing.
Left the fields enabled, just locked them and it passes all values correctly.
Even though this was a failure on my part, I'll leave this up in case some one else makes the same mistake I made.
**** Update ****
I also found out that if I did a
fRS.movelast
fRS.movefirst
before anything else, it found the data. Not sure why it started happening, but these two things seem to have fixed it completely.

Manipulating form input values after submission causes multiple instances

I'm building a form with Yii that updates two models at once.
The form takes the inputs for each model as $modelA and $modelB and then handles them separately as described here http://www.yiiframework.com/wiki/19/how-to-use-a-single-form-to-collect-data-for-two-or-more-models/
This is all good. The difference I have to the example is that $modelA (documents) has to be saved and its ID retrieved and then $modelB has to be saved including the ID from $model A as they are related.
There's an additional twist that $modelB has a file which needs to be saved.
My action code is as follows:
if(isset($_POST['Documents'], $_POST['DocumentVersions']))
{
$modelA->attributes=$_POST['Documents'];
$modelB->attributes=$_POST['DocumentVersions'];
$valid=$modelA->validate();
$valid=$modelB->validate() && $valid;
if($valid)
{
$modelA->save(false); // don't validate as we validated above.
$newdoc = $modelA->primaryKey; // get the ID of the document just created
$modelB->document_id = $newdoc; // set the Document_id of the DocumentVersions to be $newdoc
// todo: set the filename to some long hash
$modelB->file=CUploadedFile::getInstance($modelB,'file');
// finish set filename
$modelB->save(false);
if($modelB->save()) {
$modelB->file->saveAs(Yii::getPathOfAlias('webroot').'/uploads/'.$modelB->file);
}
$this->redirect(array('projects/myprojects','id'=>$_POST['project_id']));
}
}
ELSE {
$this->render('create',array(
'modelA'=>$modelA,
'modelB'=>$modelB,
'parent'=>$id,
'userid'=>$userid,
'categories'=>$categoriesList
));
}
You can see that I push the new values for 'file' and 'document_id' into $modelB. What this all works no problem, but... each time I push one of these values into $modelB I seem to get an new instance of $modelA. So the net result, I get 3 new documents, and 1 new version. The new version is all linked up correctly, but the other two documents are just straight duplicates.
I've tested removing the $modelB update steps, and sure enough, for each one removed a copy of $modelA is removed (or at least the resulting database entry).
I've no idea how to prevent this.
UPDATE....
As I put in a comment below, further testing shows the number of instances of $modelA depends on how many times the form has been submitted. Even if other pages/views are accessed in the meantime, if the form is resubmitted within a short period of time, each time I get an extra entry in the database. If this was due to some form of persistence, then I'd expect to get an extra copy of the PREVIOUS model, not multiples of the current one. So I suspect something in the way its saving, like there is some counter that's incrementing, but I've no idea where to look for this, or how to zero it each time.
Some help would be much appreciated.
thanks
JMB
OK, I had Ajax validation set to true. This was calling the create action and inserting entries. I don't fully get this, or how I could use ajax validation if I really wanted to without this effect, but... at least the two model insert with relationship works.
Thanks for the comments.
cheers
JMB

Symfony2 error message in the input field

I would like to display error message in the field it is for. I know how to put the error message into the field, but I want to find a dynamic way to check after re-submittion, if the inserted value is not the error message itself. I use doctrine annotations.
For example if the field is "title", the error message would be "The title must be filled!".
So the title field is not empty anymore, I click submit again, and it is valid now. I don't want to check every single field like
if $entity->getTitle() == "The title must be filled" ...
I've managed to do this with not displayed error divs in the twig, and jquery, but I want to know if there is a better way to do this from the controller? Thanks
You're asking how to correctly do something the wrong way... If the input value is not the value you want processed, it should have never been the value to begin with. That being said, I'm sure you have your reasons...
You need to listen to FormEvents::BIND_CLIENT_DATA and clear the form data if it matches your error string.
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('title');
$builder->get('title')->addEventListener(
FormEvents::BIND_CLIENT_DATA,
function(FilterDataEvent $event)
{
if ('The title must be filled' == $event->getData()) {
$event->setData('');
}
},
);
If you want to apply this behavior globally you need to attach this listener using a form type extension that extends 'field'. You will also need to introspect all of the possible validation error messages for the current field using the validator and pass these through the translator, then compare the results with the event data.