[BUGFIX] EXT:form - Error with multiple forms on one page
[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\Form\Domain\Model\ValidationElement;
19 use TYPO3\CMS\Form\Mvc\Controller\ControllerContext;
20 use TYPO3\CMS\Form\Domain\Builder\FormBuilder;
21 use TYPO3\CMS\Form\Domain\Builder\ValidationBuilder;
22 use TYPO3\CMS\Form\Domain\Model\Configuration;
23 use TYPO3\CMS\Form\Utility\FormUtility;
24 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
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 boolean
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 $this->sessionUtility = $sessionUtility;
81 }
82
83 /**
84 * initialize action
85 *
86 * @return void
87 */
88 protected function initializeAction() {
89 $this->configuration = Configuration::create()->setTypoScript($this->settings['typoscript']);
90 $this->formUtility = FormUtility::create($this->configuration);
91 $this->validationBuilder = ValidationBuilder::create($this->configuration);
92 $this->validationBuilder->setFormUtility($this->formUtility);
93 $this->formBuilder = FormBuilder::create($this->configuration);
94 $this->formBuilder->setValidationBuilder($this->validationBuilder);
95 $this->formBuilder->setFormUtility($this->formUtility);
96 $this->typoscript = $this->settings['typoscript'];
97
98 // uploaded file storage
99 $this->sessionUtility->initSession($this->configuration->getPrefix());
100 // move the incoming "formPrefix" data to the $model argument
101 // now we can validate the $model argument
102 if ($this->request->hasArgument($this->configuration->getPrefix())) {
103 $this->skipValidation = FALSE;
104 $argument = $this->request->getArgument($this->configuration->getPrefix());
105 $this->request->setArgument('model', $argument);
106 } else {
107 // If there are more forms at a page we have to skip
108 // the validation of not submitted forms
109 $this->skipValidation = TRUE;
110 $this->request->setArgument('model', array());
111 }
112 }
113
114 /**
115 * initialize show action
116 *
117 * @return void
118 */
119 protected function initializeShowAction() {
120 // set validation errors
121 $validationResults = $this->request->getOriginalRequestMappingResults()->forProperty('model');
122 if (!$validationResults->hasErrors()) {
123 // If there are errors, the rules already build
124 // but if there are errors, we need to build the rules here,
125 // because of the mandatory message rendering
126 $this->validationBuilder->buildRules();
127 return;
128 }
129 $this->formBuilder->setValidationErrors($validationResults);
130 }
131
132 /**
133 * initialize the confirmation action
134 *
135 * @return void
136 */
137 protected function initializeConfirmationAction() {
138 $this->prepareValidations();
139 }
140
141 /**
142 * initialize the process action
143 *
144 * @return void
145 */
146 protected function initializeProcessAction() {
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 $controllerContext = ControllerContext::extend(parent::buildControllerContext())
158 ->setConfiguration($this->configuration);
159 $this->formBuilder->setControllerContext($controllerContext);
160 return $controllerContext;
161 }
162
163 /**
164 * Handles show action, presenting the actual form.
165 *
166 * @param \TYPO3\CMS\Form\Domain\Model\ValidationElement $incomingData
167 * @dontvalidate $incomingData
168 * @return void
169 */
170 public function showAction(ValidationElement $incomingData = NULL) {
171 if ($incomingData !== NULL) {
172 $this->controllerContext->setValidationElement($incomingData);
173 }
174 $form = $this->formBuilder->buildModel();
175 if (
176 $this->typoscript['confirmation']
177 && (int)$this->typoscript['confirmation'] == 1
178 ) {
179 $form->setAdditionalArgument('action', 'confirmation');
180 } else {
181 $form->setAdditionalArgument('action', 'process');
182 }
183 $this->view->assign('model', $form);
184 }
185
186 /**
187 * Handles confirmation action, presenting the user submitted
188 * data again for final confirmation.
189 *
190 * @param \TYPO3\CMS\Form\Domain\Model\ValidationElement $model
191 * @return void
192 */
193 public function confirmationAction(ValidationElement $model) {
194 $this->skipForeignFormProcessing();
195
196 if (count($model->getIncomingFields()) === 0) {
197 $this->sessionUtility->destroySession();
198 $this->forward('show');
199 }
200 $this->controllerContext->setValidationElement($model);
201 $form = $this->formBuilder->buildModel();
202 // store uploaded files
203 $this->sessionUtility->storeSession();
204 $this->view->assign('model', $form);
205
206 $message = $this->formUtility->renderItem(
207 $this->typoscript['confirmation.']['message.'],
208 $this->typoscript['confirmation.']['message'],
209 LocalizationUtility::translate('tx_form_view_confirmation.message', 'form')
210 );
211 $this->view->assign('message', $message);
212 }
213
214 /**
215 * action dispatchConfirmationButtonClick
216 *
217 * @param \TYPO3\CMS\Form\Domain\Model\ValidationElement $model
218 * @return void
219 */
220 public function dispatchConfirmationButtonClickAction(ValidationElement $model) {
221 $this->skipForeignFormProcessing();
222
223 if ($this->request->hasArgument('confirmation-true')) {
224 $this->forward('process', NULL, NULL, array($this->configuration->getPrefix() => $this->request->getArgument('model')));
225 } else {
226 $this->sessionUtility->destroySession();
227 $this->forward('show', NULL, NULL, array('incomingData' => $this->request->getArgument('model')));
228 }
229 }
230
231 /**
232 * Handles process action, actually processing the user
233 * submitted data and forwarding it to post-processors
234 * (e.g. sending out mail messages).
235 *
236 * @param \TYPO3\CMS\Form\Domain\Model\ValidationElement $model
237 * @return void
238 */
239 public function processAction(ValidationElement $model) {
240 $this->skipForeignFormProcessing();
241
242 $this->controllerContext->setValidationElement($model);
243 $form = $this->formBuilder->buildModel();
244 $postProcessorTypoScript = array();
245 if (isset($this->typoscript['postProcessor.'])) {
246 $postProcessorTypoScript = $this->typoscript['postProcessor.'];
247 }
248
249 /** @var $postProcessor \TYPO3\CMS\Form\PostProcess\PostProcessor */
250 $postProcessor = $this->objectManager->get(
251 \TYPO3\CMS\Form\PostProcess\PostProcessor::class,
252 $form, $postProcessorTypoScript
253 );
254 $postProcessor->setControllerContext($this->controllerContext);
255
256 // @todo What is happening here?
257 $content = $postProcessor->process();
258 $this->sessionUtility->destroySession();
259 $this->forward('afterProcess', NULL, NULL, array('postProcessorContent' => $content));
260 }
261
262 /**
263 * action after process
264 *
265 * @param string $postProcessorContent
266 * @return void
267 */
268 public function afterProcessAction($postProcessorContent) {
269 $this->view->assign('postProcessorContent', $postProcessorContent);
270 }
271
272 /**
273 * Skip the processing of foreign forms.
274 * If there is more than one form on a page
275 * we have to be sure that only the submitted form will be
276 * processed. On data submission, the extbase action "confirmation" or
277 * "process" is called. The detection which form is submitted
278 * is done by the form prefix. All forms which do not have any
279 * submitted data are skipped and forwarded to the show action.
280 *
281 * @return void
282 */
283 protected function skipForeignFormProcessing() {
284 if (!$this->request->hasArgument($this->configuration->getPrefix())) {
285 $this->forward('show');
286 }
287 }
288
289 /**
290 * If the current form should be validated
291 * then set the dynamic validation
292 *
293 * @return void
294 */
295 protected function prepareValidations() {
296 if ($this->skipValidation || !$this->arguments->hasArgument('model')) {
297 return;
298 }
299
300 $this->validationBuilder->buildRules($this->request->getArgument('model'));
301 $this->setDynamicValidation($this->validationBuilder->getRules());
302 $this->skipValidation = FALSE;
303 }
304
305 /**
306 * Sets the dynamic validation rules.
307 *
308 * @param array $toValidate
309 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException
310 * @throws \TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException
311 */
312 protected function setDynamicValidation(array $toValidate = array()) {
313 // build custom validation chain
314 /** @var \TYPO3\CMS\Extbase\Validation\ValidatorResolver $validatorResolver */
315 $validatorResolver = $this->objectManager->get(\TYPO3\CMS\Extbase\Validation\ValidatorResolver::class);
316
317 /** @var \TYPO3\CMS\Form\Domain\Validator\ValidationElementValidator $modelValidator */
318 $modelValidator = $validatorResolver->createValidator(\TYPO3\CMS\Form\Domain\Validator\ValidationElementValidator::class);
319 foreach ($toValidate as $propertyName => $validations) {
320 foreach ($validations as $validation) {
321 if (empty($validation['validator'])) {
322 throw new \TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException('Invalid validate configuration for ' . $propertyName . ': Could not resolve class name for validator "' . $validation['validatorName'] . '".', 1441893777);
323 }
324 $modelValidator->addPropertyValidator($propertyName, $validation['validator']);
325 }
326 }
327
328 if ($modelValidator->countPropertyValidators()) {
329 /** @var \TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator $baseConjunctionValidator */
330 $baseConjunctionValidator = $this->arguments->getArgument('model')->getValidator();
331 if ($baseConjunctionValidator === NULL) {
332 $baseConjunctionValidator = $validatorResolver->createValidator(\TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator::class);
333 $this->arguments->getArgument('model')->setValidator($baseConjunctionValidator);
334 }
335 $baseConjunctionValidator->addValidator($modelValidator);
336 }
337 }
338 }