AjaxValidationController   A
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 326
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 20

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 20
dl 0
loc 326
rs 10
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A initializeAction() 0 6 2
A processRequest() 0 27 4
A initializeActionMethodValidators() 0 26 5
A runAction() 0 27 1
A getFieldValidation() 0 28 5
A injectResultInResponse() 0 20 2
A setUpResponseResult() 0 7 1
A formatMessages() 0 10 2
A processRequestParent() 0 4 1
A initializeActionMethodValidatorsParent() 0 4 1
A setProtectedRequestMode() 0 4 1
A getDebugMessageForException() 0 4 1
A getForm() 0 4 1
A getFormObject() 0 7 1
A getRequest() 0 4 1
1
<?php
2
/*
3
 * 2017 Romain CANON <[email protected]>
4
 *
5
 * This file is part of the TYPO3 FormZ project.
6
 * It is free software; you can redistribute it and/or modify it
7
 * under the terms of the GNU General Public License, either
8
 * version 3 of the License, or any later version.
9
 *
10
 * For the full copyright and license information, see:
11
 * http://www.gnu.org/licenses/gpl-3.0.html
12
 */
13
14
namespace Romm\Formz\Controller;
15
16
use Exception;
17
use Romm\Formz\Configuration\Form\Field\Validation\Validation;
18
use Romm\Formz\Core\Core;
19
use Romm\Formz\Error\AjaxResult;
20
use Romm\Formz\Error\FormzMessageInterface;
21
use Romm\Formz\Exceptions\ClassNotFoundException;
22
use Romm\Formz\Exceptions\EntryNotFoundException;
23
use Romm\Formz\Exceptions\InvalidArgumentTypeException;
24
use Romm\Formz\Exceptions\InvalidConfigurationException;
25
use Romm\Formz\Exceptions\MissingArgumentException;
26
use Romm\Formz\Form\FormInterface;
27
use Romm\Formz\Form\FormObject;
28
use Romm\Formz\Form\FormObjectFactory;
29
use Romm\Formz\Service\ContextService;
30
use Romm\Formz\Service\ExtensionService;
31
use Romm\Formz\Service\MessageService;
32
use Romm\Formz\Validation\DataObject\ValidatorDataObject;
33
use TYPO3\CMS\Extbase\Error\Error;
34
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
35
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
36
use TYPO3\CMS\Extbase\Mvc\ResponseInterface;
37
use TYPO3\CMS\Extbase\Mvc\Web\Request;
38
use TYPO3\CMS\Extbase\Mvc\Web\Response;
39
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
40
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
41
42
class AjaxValidationController extends ActionController
43
{
44
    const DEFAULT_ERROR_MESSAGE_KEY = 'default_error_message';
45
46
    /**
47
     * @var Request
48
     */
49
    protected $request;
50
51
    /**
52
     * @var Response
53
     */
54
    protected $response;
55
56
    /**
57
     * @var bool
58
     */
59
    protected $protectedRequestMode = true;
60
61
    /**
62
     * @var string
63
     */
64
    protected $formClassName;
65
66
    /**
67
     * @var string
68
     */
69
    protected $formName;
70
71
    /**
72
     * @var string
73
     */
74
    protected $fieldName;
75
76
    /**
77
     * @var string
78
     */
79
    protected $validatorName;
80
81
    /**
82
     * @var FormInterface
83
     */
84
    protected $form;
85
86
    /**
87
     * @var FormObject
88
     */
89
    protected $formObject;
90
91
    /**
92
     * @var AjaxResult
93
     */
94
    protected $result;
95
96
    /**
97
     * @var Validation
98
     */
99
    protected $validation;
100
101
    /**
102
     * The only accepted method for the request is `POST`.
103
     */
104
    public function initializeAction()
105
    {
106
        if ($this->request->getMethod() !== 'POST') {
107
            $this->throwStatus(400);
108
        }
109
    }
110
111
    /**
112
     * Will process the request, but also prevent any external message to be
113
     * displayed, and catch any exception that could occur during the
114
     * validation.
115
     *
116
     * @param RequestInterface  $request
117
     * @param ResponseInterface $response
118
     * @throws Exception
119
     */
120
    public function processRequest(RequestInterface $request, ResponseInterface $response)
121
    {
122
        $this->result = new AjaxResult;
123
124
        try {
125
            $this->processRequestParent($request, $response);
126
        } catch (Exception $exception) {
127
            if (false === $this->protectedRequestMode) {
128
                throw $exception;
129
            }
130
131
            $this->result->clear();
132
133
            $errorMessage = ExtensionService::get()->isInDebugMode()
134
                ? $this->getDebugMessageForException($exception)
135
                : ContextService::get()->translate(self::DEFAULT_ERROR_MESSAGE_KEY);
136
137
            $error = new Error($errorMessage, 1490176818);
138
            $this->result->addError($error);
139
            $this->result->setData('errorCode', $exception->getCode());
140
        }
141
142
        // Cleaning every external message.
143
        ob_clean();
144
145
        $this->injectResultInResponse();
146
    }
147
148
    /**
149
     * Will take care of adding a new argument to the request, based on the form
150
     * name and the form class name found in the request arguments.
151
     */
152
    protected function initializeActionMethodValidators()
153
    {
154
        $this->initializeActionMethodValidatorsParent();
155
156
        $request = $this->getRequest();
157
158
        if (false === $request->hasArgument('name')) {
159
            throw MissingArgumentException::ajaxControllerNameArgumentNotSet();
160
        }
161
162
        if (false === $request->hasArgument('className')) {
163
            throw MissingArgumentException::ajaxControllerClassNameArgumentNotSet();
164
        }
165
166
        $className = $request->getArgument('className');
167
168
        if (false === class_exists($className)) {
169
            throw ClassNotFoundException::ajaxControllerFormClassNameNotFound($className);
170
        }
171
172
        if (false === in_array(FormInterface::class, class_implements($className))) {
173
            throw InvalidArgumentTypeException::ajaxControllerWrongFormType($className);
174
        }
175
176
        $this->arguments->addNewArgument($request->getArgument('name'), $className, true);
177
    }
178
179
    /**
180
     * Main action that will process the field validation.
181
     *
182
     * @param string $name
183
     * @param string $className
184
     * @param string $fieldName
185
     * @param string $validatorName
186
     */
187
    public function runAction($name, $className, $fieldName, $validatorName)
188
    {
189
        $this->formName = $name;
190
        $this->formClassName = $className;
191
        $this->fieldName = $fieldName;
192
        $this->validatorName = $validatorName;
193
        $this->form = $this->getForm();
194
195
        $this->formObject = $this->getFormObject();
196
        $this->formObject->setForm($this->form);
197
198
        $this->validation = $this->getFieldValidation();
199
200
        $validatorDataObject = new ValidatorDataObject($this->formObject, $this->validation);
201
202
        /** @var ValidatorInterface $validator */
203
        $validator = Core::instantiate(
204
            $this->validation->getClassName(),
205
            $this->validation->getOptions(),
206
            $validatorDataObject
207
        );
208
209
        $fieldValue = ObjectAccess::getProperty($this->form, $this->fieldName);
210
        $result = $validator->validate($fieldValue);
211
212
        $this->result->merge($result);
213
    }
214
215
    /**
216
     * @return Validation
217
     * @throws EntryNotFoundException
218
     * @throws InvalidConfigurationException
219
     */
220
    protected function getFieldValidation()
221
    {
222
        $validationResult = $this->formObject->getConfigurationValidationResult();
223
224
        if (true === $validationResult->hasErrors()) {
225
            throw InvalidConfigurationException::ajaxControllerInvalidFormConfiguration();
226
        }
227
228
        $formConfiguration = $this->formObject->getConfiguration();
229
230
        if (false === $formConfiguration->hasField($this->fieldName)) {
231
            throw EntryNotFoundException::ajaxControllerFieldNotFound($this->fieldName, $this->formObject);
232
        }
233
234
        $field = $formConfiguration->getField($this->fieldName);
235
236
        if (false === $field->hasValidation($this->validatorName)) {
237
            throw EntryNotFoundException::ajaxControllerValidationNotFoundForField($this->validatorName, $this->fieldName);
238
        }
239
240
        $fieldValidationConfiguration = $field->getValidationByName($this->validatorName);
241
242
        if (false === $fieldValidationConfiguration->doesUseAjax()) {
243
            throw InvalidConfigurationException::ajaxControllerAjaxValidationNotActivated($this->validatorName, $this->fieldName);
244
        }
245
246
        return $fieldValidationConfiguration;
247
    }
248
249
    /**
250
     * Fetches errors/warnings/notices in the result, and put them in the JSON
251
     * response.
252
     */
253
    protected function injectResultInResponse()
254
    {
255
        $validationName = $this->validation instanceof Validation
256
            ? $this->validation->getName()
257
            : 'default';
258
259
        $validationResult = MessageService::get()->sanitizeValidatorResult($this->result, $validationName);
260
261
        $result = [
262
            'success'  => !$this->result->hasErrors(),
263
            'data'     => $this->result->getData(),
264
            'messages' => [
265
                'errors'   => $this->formatMessages($validationResult->getErrors()),
266
                'warnings' => $this->formatMessages($validationResult->getWarnings()),
267
                'notices'  => $this->formatMessages($validationResult->getNotices())
268
            ]
269
        ];
270
271
        $this->setUpResponseResult($result);
272
    }
273
274
    /**
275
     * @param array $result
276
     */
277
    protected function setUpResponseResult(array $result)
278
    {
279
        $this->response->setHeader('Content-Type', 'application/json');
280
        $this->response->setContent(json_encode($result));
281
282
        Core::get()->getPageController()->setContentType('application/json');
283
    }
284
285
    /**
286
     * @param FormzMessageInterface[] $messages
287
     * @return array
288
     */
289
    protected function formatMessages(array $messages)
290
    {
291
        $sortedMessages = [];
292
293
        foreach ($messages as $message) {
294
            $sortedMessages[$message->getMessageKey()] = $message->getMessage();
0 ignored issues
show
Bug introduced by
The method getMessage() does not exist on Romm\Formz\Error\FormzMessageInterface. Did you maybe mean getMessageKey()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
295
        }
296
297
        return $sortedMessages;
298
    }
299
300
    /**
301
     * Wrapper for unit tests.
302
     *
303
     * @param RequestInterface  $request
304
     * @param ResponseInterface $response
305
     */
306
    protected function processRequestParent(RequestInterface $request, ResponseInterface $response)
307
    {
308
        parent::processRequest($request, $response);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (processRequest() instead of processRequestParent()). Are you sure this is correct? If so, you might want to change this to $this->processRequest().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
309
    }
310
311
    /**
312
     * Wrapper for unit tests.
313
     */
314
    protected function initializeActionMethodValidatorsParent()
315
    {
316
        parent::initializeActionMethodValidators();
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (initializeActionMethodValidators() instead of initializeActionMethodValidatorsParent()). Are you sure this is correct? If so, you might want to change this to $this->initializeActionMethodValidators().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
317
    }
318
319
    /**
320
     * Used in unit testing.
321
     *
322
     * @param bool $flag
323
     */
324
    public function setProtectedRequestMode($flag)
325
    {
326
        $this->protectedRequestMode = (bool)$flag;
327
    }
328
329
    /**
330
     * @param Exception $exception
331
     * @return string
332
     */
333
    protected function getDebugMessageForException(Exception $exception)
334
    {
335
        return 'Debug mode – ' . $exception->getMessage();
336
    }
337
338
    /**
339
     * @return FormInterface
340
     * @throws MissingArgumentException
341
     */
342
    protected function getForm()
343
    {
344
        return $this->arguments->getArgument($this->formName)->getValue();
345
    }
346
347
    /**
348
     * @return FormObject
349
     */
350
    protected function getFormObject()
351
    {
352
        /** @var FormObjectFactory $formObjectFactory */
353
        $formObjectFactory = Core::instantiate(FormObjectFactory::class);
354
355
        return $formObjectFactory->getInstanceFromClassName($this->formClassName, $this->formName);
356
    }
357
358
    /**
359
     * Wrapper for unit tests.
360
     *
361
     * @return Request
362
     */
363
    protected function getRequest()
364
    {
365
        return $this->request;
366
    }
367
}
368