Passed
Push — master ( abb8c7...8eb58c )
by Jacques
05:19 queued 15s
created

FormController::submitAction()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 49
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 28
c 3
b 0
f 0
dl 0
loc 49
rs 9.472
cc 4
nc 4
nop 4
1
<?php
2
3
/*
4
 * This file is part of Monsieur Biz' Rich Editor plugin for Sylius.
5
 *
6
 * (c) Monsieur Biz <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace MonsieurBiz\SyliusRichEditorPlugin\Controller;
15
16
use MonsieurBiz\SyliusRichEditorPlugin\Exception\UiElementNotFoundException;
17
use MonsieurBiz\SyliusRichEditorPlugin\UiElement\RegistryInterface;
18
use MonsieurBiz\SyliusRichEditorPlugin\Uploader\FileUploader;
19
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
20
use Symfony\Component\Form\Extension\Core\Type\FileType as NativeFileType;
21
use Symfony\Component\Form\FormInterface;
22
use Symfony\Component\HttpFoundation\File\UploadedFile;
23
use Symfony\Component\HttpFoundation\JsonResponse;
24
use Symfony\Component\HttpFoundation\Request;
25
use Symfony\Component\HttpFoundation\Response;
26
27
class FormController extends AbstractController
28
{
29
    /**
30
     * @var RegistryInterface
31
     */
32
    private $uiElementRegistry;
33
34
    /**
35
     * FormController constructor.
36
     *
37
     * @param RegistryInterface $uiElementRegistry
38
     */
39
    public function __construct(RegistryInterface $uiElementRegistry)
40
    {
41
        $this->uiElementRegistry = $uiElementRegistry;
42
    }
43
44
    /**
45
     * Generate the form for an element.
46
     *
47
     * @param Request $request
48
     * @param string $code
49
     *
50
     * @return Response
51
     */
52
    public function viewAction(Request $request, string $code): Response
53
    {
54
        // Find UI Element from type
55
        try {
56
            $uiElement = $this->uiElementRegistry->getUiElement($code);
57
        } catch (UiElementNotFoundException $exception) {
58
            throw $this->createNotFoundException($exception->getMessage());
59
        }
60
61
        // Check data in post
62
        $data = [];
63
        $isEdition = $request->isMethod('post');
64
        if ($isEdition && ($data = $request->get('data'))) {
65
            $data = json_decode($data, true);
66
            if (!\is_array($data)) {
67
                throw $this->createNotFoundException();
68
            }
69
        }
70
71
        // Create form depending on UI Element with data
72
        $form = $this->createForm($uiElement->getFormClass(), $data);
73
74
        return new JsonResponse([
75
            'code' => $uiElement->getCode(),
76
            'form_html' => $this->renderView($uiElement->getAdminFormTemplate(), [
77
                'form' => $form->createView(),
78
                'uiElement' => $uiElement,
79
                'data' => $data,
80
                'isEdition' => (int) $isEdition,
81
            ]),
82
        ]);
83
    }
84
85
    /**
86
     * Render all UI elements in HTML.
87
     *
88
     * @param Request $request
89
     *
90
     * @return Response
91
     */
92
    public function renderElementsAction(Request $request): Response
93
    {
94
        if ($uiElements = $request->get('ui_elements')) {
95
            $uiElements = json_decode($uiElements, true);
96
            if (!\is_array($uiElements)) {
97
                throw $this->createNotFoundException();
98
            }
99
        }
100
101
        $result = [];
102
        /** @phpstan-ignore-next-line */
103
        foreach ($uiElements as $uiElementIndex => $uiElementData) {
104
            $result[$uiElementIndex] = '';
105
106
            if (!isset($uiElementData['code'])) {
107
                if (isset($uiElementData['type'], $uiElementData['fields'])) {
108
                    $uiElementData['code'] = $uiElementData['type'];
109
                    $uiElementData['data'] = $uiElementData['fields']; // @phpstan-ignore-line
110
                    unset($uiElementData['type'], $uiElementData['fields']); // @phpstan-ignore-line
111
                } else {
112
                    continue;
113
                }
114
            }
115
116
            try {
117
                $uiElement = $this->uiElementRegistry->getUiElement($uiElementData['code']);
118
            } catch (UiElementNotFoundException $exception) {
119
                continue;
120
            }
121
122
            $template = $uiElement->getAdminRenderTemplate();
123
124
            $result[$uiElementIndex] = $this->renderView($template, [
125
                'ui_element' => $uiElement,
126
                'element' => $uiElementData['data'],
127
            ]);
128
        }
129
130
        return new JsonResponse($result);
131
    }
132
133
    /**
134
     * Validate submitted data and return an UI Element JSON if everything is OK.
135
     *
136
     * @param Request $request
137
     * @param FileUploader $fileUploader
138
     * @param string $code
139
     * @param bool $isEdition
140
     *
141
     * @return Response
142
     */
143
    public function submitAction(Request $request, FileUploader $fileUploader, string $code, bool $isEdition): Response
144
    {
145
        // Find UI Element from type
146
        try {
147
            $uiElement = $this->uiElementRegistry->getUiElement($code);
148
        } catch (UiElementNotFoundException $exception) {
149
            throw $this->createNotFoundException($exception->getMessage());
150
        }
151
152
        // Create and validate form
153
        $form = $this->createForm($uiElement->getFormClass());
154
        $form->handleRequest($request);
155
        if (!$form->isSubmitted()) {
156
            throw $this->createNotFoundException();
157
        }
158
159
        // Convert uploaded files to string in form data if necessary, or retrieve current image path if edition
160
        $formData = $this->processFormData($form, $fileUploader, $request->request->get($form->getName()));
161
162
        // Generate form render with error display
163
        if (!$form->isValid()) {
164
            // Manage current uplodaded files to be sure the user will not loose it
165
            $request->request->add(['rich_editor_uploaded_files' => $this->convertFormDataForRequest(
166
                [$form->getName() => $formData]
167
            )]);
168
169
            return new JsonResponse([
170
                'error' => true,
171
                'code' => $uiElement->getCode(),
172
                'form_html' => $this->renderView($uiElement->getAdminFormTemplate(), [
173
                    'form' => $form->createView(),
174
                    'uiElement' => $uiElement,
175
                    'data' => $formData,
176
                    'isEdition' => (int) $isEdition,
177
                ]),
178
            ]);
179
        }
180
181
        $template = $uiElement->getAdminRenderTemplate();
182
183
        $previewHtml = $this->renderView($template, [
184
            'ui_element' => $uiElement,
185
            'element' => $formData,
186
        ]);
187
188
        return new JsonResponse([
189
            'code' => $uiElement->getCode(),
190
            'data' => $formData,
191
            'previewHtml' => $previewHtml,
192
        ]);
193
    }
194
195
    /**
196
     * Build a new form data array with the uploaded file path instead of files, or current filenames on edition.
197
     *
198
     * @param FormInterface $form
199
     * @param FileUploader $fileUploader
200
     * @param mixed $requestData
201
     *
202
     * @return array|mixed|string
203
     */
204
    private function processFormData(FormInterface $form, FileUploader $fileUploader, $requestData)
205
    {
206
        // No child, end of recursivity, return form value or uploaded file path
207
        if (!\count($form->all())) {
208
            return $this->processFormDataWithoutChild($form, $fileUploader, $requestData);
209
        }
210
211
        $processedData = [];
212
        foreach ($form as $child) {
213
            $formData = $this->processFormData($child, $fileUploader, $requestData[$child->getName()] ?? []);
214
            $processedData[$child->getName()] = $formData;
215
        }
216
217
        return $processedData;
218
    }
219
220
    /**
221
     * @param FormInterface $form
222
     * @param FileUploader $fileUploader
223
     * @param array|string $requestData
224
     *
225
     * @return array|mixed|string
226
     */
227
    private function processFormDataWithoutChild(FormInterface $form, FileUploader $fileUploader, $requestData)
228
    {
229
        if ($form->isValid() && $form->getData() instanceof UploadedFile) {
230
            // Upload image selected by user
231
            return $fileUploader->upload($form->getData(), $form->getConfig()->getOption('file-type'));
232
        }
233
        if ($form->getConfig()->getType()->getInnerType() instanceof NativeFileType && !empty($requestData)) {
234
            // Check if we have a string value for this fields which is the file path (During edition for example)
235
            return $requestData; // Will return the current filename string
236
        }
237
238
        return $form->getData();
239
    }
240
241
    /**
242
     * Recursively convert multidimensional array to one dimension
243
     * The key is the full input name (ex : `image_collection[images][0][image]`)
244
     * It is used in form with file inputs when the form is not valid to avoid to loose uploaded files.
245
     *
246
     * @param array $formData
247
     * @param string $prefix
248
     *
249
     * @return array
250
     */
251
    private function convertFormDataForRequest(array $formData, string $prefix = ''): array
252
    {
253
        $items = [];
254
255
        foreach ($formData as $key => $value) {
256
            if (\is_array($value)) {
257
                if (empty($prefix)) {
258
                    $items = array_merge($items, $this->convertFormDataForRequest($value, sprintf('%s', $key)));
259
                } else {
260
                    $items = array_merge($items, $this->convertFormDataForRequest($value, sprintf('%s[%s]', $prefix, $key)));
261
                }
262
            } else {
263
                $items[sprintf('%s[%s]', $prefix, $key)] = $value;
264
            }
265
        }
266
267
        return $items;
268
    }
269
}
270