Issues (281)

Branch: master

src/Common/Form/FileType.php (1 issue)

1
<?php
2
3
namespace Common\Form;
4
5
use Common\Core\Model;
6
use Common\Doctrine\ValueObject\AbstractFile;
7
use stdClass;
8
use Symfony\Component\Form\AbstractType;
9
use Symfony\Component\Form\CallbackTransformer;
10
use Symfony\Component\Form\Exception\TransformationFailedException;
11
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
12
use Symfony\Component\Form\Extension\Core\Type\FileType as SymfonyFileType;
13
use Symfony\Component\Form\FormBuilderInterface;
14
use Symfony\Component\Form\FormEvent;
15
use Symfony\Component\Form\FormEvents;
16
use Symfony\Component\Form\FormInterface;
17
use Symfony\Component\Form\FormView;
18
use Symfony\Component\HttpFoundation\File\UploadedFile;
19
use Symfony\Component\OptionsResolver\Options;
20
use Symfony\Component\OptionsResolver\OptionsResolver;
21
use Symfony\Component\Validator\Constraints\File;
22
use Symfony\Component\Validator\Constraints\NotBlank;
23
use Symfony\Component\Validator\Constraints\Valid;
24
use Symfony\Component\Validator\Mapping\ClassMetadata;
25
use Symfony\Component\Validator\Validator\ValidatorInterface;
26
27
class FileType extends AbstractType
28
{
29
    /**
30
     * @var ValidatorInterface
31
     */
32
    private $validator;
33
34
    public function __construct(ValidatorInterface $validator)
35
    {
36
        $this->validator = $validator;
37
    }
38
39
    public function buildForm(FormBuilderInterface $builder, array $options): void
40
    {
41
        if ($options['show_remove_file']) {
42
            $builder->add(
43
                'remove',
44
                CheckboxType::class,
45
                [
46
                    'required' => false,
47
                    'label' => $options['remove_file_label'],
48
                    'property_path' => 'pendingDeletion',
49
                ]
50
            );
51
        }
52
53
        $builder
54
            ->addEventListener(
55
                FormEvents::PRE_SET_DATA,
56
                function (FormEvent $event) use ($options) {
57
                    $fileIsEmpty = ($event->getData() === null || empty($event->getData()->getFileName()));
58
                    $required = $fileIsEmpty && $options['required'];
59
                    $fileFieldOptions = [
60
                        'label' => false,
61
                        'required' => $required,
62
                        'attr' => ['accept' => $options['accept']],
63
                    ];
64
                    if ($required) {
65
                        $fileFieldOptions['constraints'] = [
66
                            new NotBlank(
67
                                ['message' => $options['required_file_error']]
68
                            ),
69
                        ];
70
                    }
71
                    $event->getForm()->add('file', SymfonyFileType::class, $fileFieldOptions);
72
                }
73
            )
74
            ->addModelTransformer(
75
                new CallbackTransformer(
76
                    function (AbstractFile $file = null) {
77
                        return $file;
78
                    },
79
                    function ($file) use ($options) {
80
                        if (!$file instanceof AbstractFile && !$file instanceof stdClass) {
81
                            throw new TransformationFailedException('Invalid class for the file');
82
                        }
83
84
                        $fileClass = $options['file_class'];
85
86
                        if (!$file instanceof AbstractFile) {
87
                            $file = $fileClass::fromUploadedFile($file->getFile());
88
                        }
89
90
                        // return a clone to make sure that doctrine will do the lifecycle callbacks
91
                        return clone $file;
92
                    }
93
                )
94
            );
95
    }
96
97
    public function configureOptions(OptionsResolver $resolver): void
98
    {
99
        $resolver->setRequired(
100
            [
101
                'file_class',
102
                'show_preview',
103
                'preview_label',
104
                'show_remove_file',
105
                'remove_file_label',
106
                'required_file_error',
107
            ]
108
        );
109
110
        $resolver->setDefaults(
111
            [
112
                'data_class' => AbstractFile::class,
113
                'empty_data' => function () {
114
                    return new class extends stdClass {
115
                        /** @var UploadedFile */
116
                        protected $file;
117
118
                        /** @var bool */
119
                        protected $pendingDeletion = false;
120
121
                        public function setFile(UploadedFile $file = null)
122
                        {
123
                            $this->file = $file;
124
                        }
125
126
                        public function getFile(): ?UploadedFile
127
                        {
128
                            return $this->file;
129
                        }
130
131
                        public function getPendingDeletion(): bool
132
                        {
133
                            return $this->pendingDeletion;
134
                        }
135
136
                        public function setPendingDeletion(bool $pendingDeletion)
137
                        {
138
                            $this->pendingDeletion = $pendingDeletion;
139
                        }
140
                    };
141
                },
142
                'preview_label' => 'lbl.ViewCurrentFile',
143
                'show_preview' => true,
144
                'show_remove_file' => true,
145
                'required_file_error' => 'err.FieldIsRequired',
146
                'remove_file_label' => 'lbl.Delete',
147
                'accept' => null,
148
                'constraints' => array(new Valid()),
149
                'error_bubbling' => false,
150
                'help_text_message' => 'msg.HelpMaxFileSize',
151
                'help_text_argument' => function (Options $options) {
152
                    return $this->getUploadMaxFileSize($options['file_class']);
153
                },
154
            ]
155
        );
156
    }
157
158
    public function getBlockPrefix(): string
159
    {
160
        return 'fork_file';
161
    }
162
163
    public function buildView(FormView $view, FormInterface $form, array $options): void
164
    {
165
        $view->vars['show_preview'] = $options['show_preview'];
166
        $view->vars['show_remove_file'] = $options['show_remove_file'] && $form->getData() !== null
167
                                          && !empty($form->getData()->getFileName());
168
        // if you need to have an file you shouldn't be allowed to remove it
169
        if ($options['required']) {
170
            $view->vars['show_remove_file'] = false;
171
        }
172
        $imageIsEmpty = ($form->getData() === null || empty($form->getData()->getFileName()));
173
        $view->vars['required'] = $imageIsEmpty && $options['required'];
174
175
        $view->vars['preview_url'] = false;
176
        if ($form->getData() instanceof AbstractFile) {
177
            $view->vars['preview_url'] = $form->getData()->getWebPath();
178
        }
179
        array_map(
180
            function ($optionName) use ($options, &$view) {
181
                if (array_key_exists($optionName, $options) && !empty($options[$optionName])) {
182
                    $view->vars[$optionName] = $options[$optionName];
183
                }
184
            },
185
            [
186
                'preview_label',
187
                'help_text_message',
188
                'help_text_argument',
189
            ]
190
        );
191
    }
192
193
    private function getMaxFileSizeServerValue(): ?int
194
    {
195
        $uploadMaxFileSize = ini_get('upload_max_filesize');
196
197
        if ($uploadMaxFileSize === false) {
198
            return null;
199
        }
200
201
        // reformat if defined as an integer
202
        if (is_numeric($uploadMaxFileSize)) {
203
            return $uploadMaxFileSize;
204
        }
205
206
        // reformat if specified in kB
207
        if (mb_strtoupper(mb_substr($uploadMaxFileSize, -1)) === 'K') {
208
            return mb_substr($uploadMaxFileSize, 0, -1) * 1000;
209
        }
210
211
        // reformat if specified in MB
212
        if (mb_strtoupper(mb_substr($uploadMaxFileSize, -1)) === 'M') {
213
            return mb_substr($uploadMaxFileSize, 0, -1) * 1000 * 1000;
214
        }
215
216
        // reformat if specified in GB
217
        if (mb_strtoupper(mb_substr($uploadMaxFileSize, -1)) === 'G') {
218
            return mb_substr($uploadMaxFileSize, 0, -1) * 1000 * 1000 * 1000;
219
        }
220
221
        return null;
222
    }
223
224
    private function getMaxFileSizeConstraintValue(string $fileClass): ?int
225
    {
226
        // use the metaData to find the file data
227
        /** @var ClassMetadata $metaData */
228
        $metaData = $this->validator->getMetadataFor($fileClass);
229
        $members = $metaData->getPropertyMetadata('file');
230
231
        // the constraints can be found in the property meta data members
232
        foreach ($members as $member) {
233
            $constraints = $member->getConstraints();
234
235
            if (count($constraints) === 1) {
236
                /** @var File $fileConstraint */
237
                $fileConstraint = $constraints[0];
238
239
                if ($fileConstraint instanceof File) {
240
                    return $fileConstraint->maxSize;
241
                }
242
            }
243
        }
244
245
        return null;
246
    }
247
248
    private function getUploadMaxFileSize(string $fileClass): ?string
249
    {
250
        $constraintValue = $this->getMaxFileSizeConstraintValue($fileClass);
251
        $serverValue = $this->getMaxFileSizeServerValue();
252
253
        if (!is_numeric($constraintValue) && !is_numeric($serverValue)) {
254
            return null;
255
        }
256
257
        // return the server value if the constraint value is to high
258
        if (is_numeric($constraintValue) && is_numeric($serverValue)) {
259
            if ($constraintValue < $serverValue) {
260
                return Model::prettyPrintFileSize($constraintValue);
261
            }
262
263
            return Model::prettyPrintFileSize($serverValue);
264
        }
265
266
        if (is_numeric($constraintValue)) {
267
            return Model::prettyPrintFileSize($constraintValue);
268
        }
269
270
        return Model::prettyPrintFileSize($serverValue);
0 ignored issues
show
It seems like $serverValue can also be of type null; however, parameter $fileSize of Common\Core\Model::prettyPrintFileSize() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

270
        return Model::prettyPrintFileSize(/** @scrutinizer ignore-type */ $serverValue);
Loading history...
271
    }
272
}
273