[BUGFIX] EXT:form - mandatoryValidationMessages unset after submit
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Classes / Controller / FrontendController.php
1 <?php
2 namespace TYPO3\CMS\Form\Controller;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
18 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
19 use TYPO3\CMS\Form\Domain\Builder\FormBuilder;
20 use TYPO3\CMS\Form\Domain\Builder\ValidationBuilder;
21 use TYPO3\CMS\Form\Domain\Model\Configuration;
22 use TYPO3\CMS\Form\Domain\Model\ValidationElement;
23 use TYPO3\CMS\Form\Mvc\Controller\ControllerContext;
24 use TYPO3\CMS\Form\Utility\FormUtility;
25
26 /**
27 * The form wizard controller
28 */
29 class FrontendController extends ActionController
30 {
31 /**
32 * @var FormBuilder
33 */
34 protected $formBuilder;
35
36 /**
37 * @var ValidationBuilder
38 */
39 protected $validationBuilder;
40
41 /**
42 * @var \TYPO3\CMS\Form\Utility\SessionUtility
43 */
44 protected $sessionUtility;
45
46 /**
47 * @var FormUtility
48 */
49 protected $formUtility;
50
51 /**
52 * The TypoScript array
53 *
54 * @var array
55 */
56 protected $typoscript = array();
57
58 /**
59 * TRUE if the validation of the form should be skipped
60 *
61 * @var bool
62 */
63 protected $skipValidation = false;
64
65 /**
66 * @var ControllerContext
67 */
68 protected $controllerContext;
69
70 /**
71 * @var Configuration
72 */
73 protected $configuration;
74
75 /**
76 * @param \TYPO3\CMS\Form\Utility\SessionUtility $sessionUtility
77 * @return void
78 */
79 public function injectSessionUtility(\TYPO3\CMS\Form\Utility\SessionUtility $sessionUtility)
80 {
81 $this->sessionUtility = $sessionUtility;
82 }
83
84 /**
85 * initialize action
86 *
87 * @return void
88 */
89 protected function initializeAction()
90 {
91 $this->configuration = Configuration::create()->setTypoScript($this->settings['typoscript']);
92 $this->formUtility = FormUtility::create($this->configuration);
93 $this->validationBuilder = ValidationBuilder::create($this->configuration);
94 $this->validationBuilder->setFormUtility($this->formUtility);
95 $this->formBuilder = FormBuilder::create($this->configuration);
96 $this->formBuilder->setValidationBuilder($this->validationBuilder);
97 $this->formBuilder->setFormUtility($this->formUtility);
98 $this->typoscript = $this->settings['typoscript'];
99
100 // uploaded file storage
101 $this->sessionUtility->initSession($this->configuration->getPrefix());
102 // move the incoming "formPrefix" data to the $model argument
103 // now we can validate the $model argument
104 if ($this->request->hasArgument($this->configuration->getPrefix())) {
105 $this->skipValidation = false;
106 $argument = $this->request->getArgument($this->configuration->getPrefix());
107 $this->request->setArgument('model', $argument);
108 } else {
109 // If there are more forms at a page we have to skip
110 // the validation of not submitted forms
111 $this->skipValidation = true;
112 $this->request->setArgument('model', array());
113 }
114 }
115
116 /**
117 * initialize show action
118 *
119 * @return void
120 */
121 protected function initializeShowAction()
122 {
123 $validationResults = $this->request->getOriginalRequestMappingResults()->forProperty('model');
124 $this->validationBuilder->buildRules();
125 if ($validationResults->hasErrors()) {
126 $this->formBuilder->setValidationErrors($validationResults);
127 }
128 }
129
130 /**
131 * initialize the confirmation action
132 *
133 * @return void
134 */
135 protected function initializeConfirmationAction()
136 {
137 $this->prepareValidations();
138 }
139
140 /**
141 * initialize the process action
142 *
143 * @return void
144 */
145 protected function initializeProcessAction()
146 {
147 $this->prepareValidations();
148 }
149
150 /**
151 * Builds the controller context by extending
152 * the Extbase context with custom additions.
153 *
154 * @return ControllerContext
155 */
156 protected function buildControllerContext()
157 {
158 $controllerContext = ControllerContext::extend(parent::buildControllerContext())
159 ->setConfiguration($this->configuration);
160 $this->formBuilder->setControllerContext($controllerContext);
161 return $controllerContext;
162 }
163
164 /**
165 * Handles show action, presenting the actual form.
166 *
167 * @param \TYPO3\CMS\Form\Domain\Model\ValidationElement $incomingData
168 * @ignorevalidation $incomingData
169 * @return void
170 */
171 public function showAction(ValidationElement $incomingData = null)
172 {
173 if ($incomingData !== null) {
174 $this->controllerContext->setValidationElement($incomingData);
175 }
176 $form = $this->formBuilder->buildModel();
177 $this->view->assign('model', $form);
178 }
179
180 /**
181 * Handles confirmation action, presenting the user submitted
182 * data again for final confirmation.
183 *
184 * @param \TYPO3\CMS\Form\Domain\Model\ValidationElement $model
185 * @return void
186 */
187 public function confirmationAction(ValidationElement $model)
188 {
189 $this->skipForeignFormProcessing();
190
191 if (count($model->getIncomingFields()) === 0) {
192 $this->sessionUtility->destroySession();
193 $this->forward('show');
194 }
195 $this->controllerContext->setValidationElement($model);
196 $form = $this->formBuilder->buildModel();
197 // store uploaded files
198 $this->sessionUtility->storeSession();
199 $this->view->assign('model', $form);
200
201 $message = $this->formUtility->renderItem(
202 $this->typoscript['confirmation.']['message.'],
203 $this->typoscript['confirmation.']['message'],
204 LocalizationUtility::translate('tx_form_view_confirmation.message', 'form')
205 );
206 $this->view->assign('message', $message);
207 }
208
209 /**
210 * action dispatchConfirmationButtonClick
211 *
212 * @param \TYPO3\CMS\Form\Domain\Model\ValidationElement $model
213 * @return void
214 */
215 public function dispatchConfirmationButtonClickAction(ValidationElement $model)
216 {
217 $this->skipForeignFormProcessing();
218
219 if ($this->request->hasArgument('confirmation-true')) {
220 $this->forward('process', null, null, array($this->configuration->getPrefix() => $this->request->getArgument('model')));
221 } else {
222 $this->sessionUtility->destroySession();
223 $this->forward('show', null, null, array('incomingData' => $this->request->getArgument('model')));
224 }
225 }
226
227 /**
228 * Handles process action, actually processing the user
229 * submitted data and forwarding it to post-processors
230 * (e.g. sending out mail messages).
231 *
232 * @param \TYPO3\CMS\Form\Domain\Model\ValidationElement $model
233 * @return void
234 */
235 public function processAction(ValidationElement $model)
236 {
237 $this->skipForeignFormProcessing();
238
239 $this->controllerContext->setValidationElement($model);
240 $form = $this->formBuilder->buildModel();
241 $postProcessorTypoScript = array();
242 if (isset($this->typoscript['postProcessor.'])) {
243 $postProcessorTypoScript = $this->typoscript['postProcessor.'];
244 }
245
246 /** @var $postProcessor \TYPO3\CMS\Form\PostProcess\PostProcessor */
247 $postProcessor = $this->objectManager->get(
248 \TYPO3\CMS\Form\PostProcess\PostProcessor::class,
249 $form, $postProcessorTypoScript
250 );
251 $postProcessor->setControllerContext($this->controllerContext);
252
253 // @todo What is happening here?
254 $content = $postProcessor->process();
255 $this->sessionUtility->destroySession();
256 $this->forward('afterProcess', null, null, array('postProcessorContent' => $content));
257 }
258
259 /**
260 * action after process
261 *
262 * @param string $postProcessorContent
263 * @return void
264 */
265 public function afterProcessAction($postProcessorContent)
266 {
267 $this->view->assign('postProcessorContent', $postProcessorContent);
268 }
269
270 /**
271 * Skip the processing of foreign forms.
272 * If there is more than one form on a page
273 * we have to be sure that only the submitted form will be
274 * processed. On data submission, the extbase action "confirmation" or
275 * "process" is called. The detection which form is submitted
276 * is done by the form prefix. All forms which do not have any
277 * submitted data are skipped and forwarded to the show action.
278 *
279 * @return void
280 */
281 protected function skipForeignFormProcessing()
282 {
283 if (
284 !$this->request->hasArgument($this->configuration->getPrefix())
285 && !$this->sessionUtility->getSessionData()
286 ) {
287 $this->forward('show');
288 }
289 }
290
291 /**
292 * If the current form should be validated
293 * then set the dynamic validation
294 *
295 * @return void
296 */
297 protected function prepareValidations()
298 {
299 if ($this->skipValidation || !$this->arguments->hasArgument('model')) {
300 return;
301 }
302
303 $this->validationBuilder->buildRules($this->request->getArgument('model'));
304 $this->setDynamicValidation($this->validationBuilder->getRules());
305 $this->skipValidation = false;
306 }
307
308 /**
309 * Sets the dynamic validation rules.
310 *
311 * @param array $toValidate
312 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException
313 * @throws \TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException
314 */
315 protected function setDynamicValidation(array $toValidate = array())
316 {
317 // build custom validation chain
318 /** @var \TYPO3\CMS\Extbase\Validation\ValidatorResolver $validatorResolver */
319 $validatorResolver = $this->objectManager->get(\TYPO3\CMS\Extbase\Validation\ValidatorResolver::class);
320
321 /** @var \TYPO3\CMS\Form\Domain\Validator\ValidationElementValidator $modelValidator */
322 $modelValidator = $validatorResolver->createValidator(\TYPO3\CMS\Form\Domain\Validator\ValidationElementValidator::class);
323 foreach ($toValidate as $propertyName => $validations) {
324 foreach ($validations as $validation) {
325 if (empty($validation['validator'])) {
326 throw new \TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException('Invalid validate configuration for ' . $propertyName . ': Could not resolve class name for validator "' . $validation['validatorName'] . '".', 1441893777);
327 }
328 $modelValidator->addPropertyValidator($propertyName, $validation['validator']);
329 }
330 }
331
332 if ($modelValidator->countPropertyValidators()) {
333 /** @var \TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator $baseConjunctionValidator */
334 $baseConjunctionValidator = $this->arguments->getArgument('model')->getValidator();
335 if ($baseConjunctionValidator === null) {
336 $baseConjunctionValidator = $validatorResolver->createValidator(\TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator::class);
337 $this->arguments->getArgument('model')->setValidator($baseConjunctionValidator);
338 }
339 $baseConjunctionValidator->addValidator($modelValidator);
340 }
341 }
342 }