Passed
Push — master ( ec0d02...c0f595 )
by Jan
06:31 queued 10s
created

BaseAdminController::_edit()   D

Complexity

Conditions 18
Paths 195

Size

Total Lines 114
Code Lines 70

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 18
eloc 70
c 1
b 0
f 0
nc 195
nop 4
dl 0
loc 114
rs 4.075

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4
 *
5
 * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Affero General Public License as published
9
 * by the Free Software Foundation, either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License
18
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19
 */
20
21
declare(strict_types=1);
22
23
/**
24
 * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
25
 *
26
 * Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
27
 *
28
 * This program is free software; you can redistribute it and/or
29
 * modify it under the terms of the GNU General Public License
30
 * as published by the Free Software Foundation; either version 2
31
 * of the License, or (at your option) any later version.
32
 *
33
 * This program is distributed in the hope that it will be useful,
34
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
35
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
36
 * GNU General Public License for more details.
37
 *
38
 * You should have received a copy of the GNU General Public License
39
 * along with this program; if not, write to the Free Software
40
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
41
 */
42
43
namespace App\Controller\AdminPages;
44
45
use App\DataTables\LogDataTable;
46
use App\Entity\Base\AbstractNamedDBElement;
47
use App\Entity\Base\AbstractStructuralDBElement;
48
use App\Entity\LabelSystem\LabelProfile;
49
use App\Entity\Parts\PartLot;
50
use App\Entity\UserSystem\User;
51
use App\Events\SecurityEvent;
52
use App\Events\SecurityEvents;
53
use App\Exceptions\AttachmentDownloadException;
54
use App\Form\AdminPages\ImportType;
55
use App\Form\AdminPages\MassCreationForm;
56
use App\Services\Attachments\AttachmentSubmitHandler;
57
use App\Services\EntityExporter;
58
use App\Services\EntityImporter;
59
use App\Services\LabelSystem\Barcodes\BarcodeExampleElementsGenerator;
60
use App\Services\LabelSystem\LabelGenerator;
61
use App\Services\LogSystem\EventCommentHelper;
62
use App\Services\LogSystem\HistoryHelper;
63
use App\Services\LogSystem\TimeTravel;
64
use App\Services\StructuralElementRecursionHelper;
65
use Doctrine\ORM\EntityManagerInterface;
66
use InvalidArgumentException;
67
use Omines\DataTablesBundle\DataTableFactory;
68
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
69
use Symfony\Component\EventDispatcher\EventDispatcher;
70
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
71
use Symfony\Component\Form\FormInterface;
72
use Symfony\Component\HttpFoundation\File\UploadedFile;
73
use Symfony\Component\HttpFoundation\RedirectResponse;
74
use Symfony\Component\HttpFoundation\Request;
75
use Symfony\Component\HttpFoundation\Response;
76
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
77
use Symfony\Component\Validator\ConstraintViolationList;
78
use Symfony\Contracts\Translation\TranslatorInterface;
79
80
abstract class BaseAdminController extends AbstractController
81
{
82
    protected $entity_class = '';
83
    protected $form_class = '';
84
    protected $twig_template = '';
85
    protected $route_base = '';
86
    protected $attachment_class = '';
87
    protected $parameter_class = '';
88
89
    protected $passwordEncoder;
90
    protected $translator;
91
    protected $attachmentSubmitHandler;
92
    protected $commentHelper;
93
94
    protected $historyHelper;
95
    protected $timeTravel;
96
    protected $dataTableFactory;
97
    /** @var EventDispatcher */
98
    protected $eventDispatcher;
99
    protected $labelGenerator;
100
    protected $barcodeExampleGenerator;
101
102
    public function __construct(TranslatorInterface $translator, UserPasswordEncoderInterface $passwordEncoder,
103
        AttachmentSubmitHandler $attachmentSubmitHandler,
104
        EventCommentHelper $commentHelper, HistoryHelper $historyHelper, TimeTravel $timeTravel,
105
        DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher, BarcodeExampleElementsGenerator $barcodeExampleGenerator,
106
        LabelGenerator $labelGenerator)
107
    {
108
        if ('' === $this->entity_class || '' === $this->form_class || '' === $this->twig_template || '' === $this->route_base) {
0 ignored issues
show
introduced by
The condition '' === $this->route_base is always false.
Loading history...
introduced by
The condition '' === $this->twig_template is always false.
Loading history...
introduced by
The condition '' === $this->form_class is always false.
Loading history...
109
            throw new InvalidArgumentException('You have to override the $entity_class, $form_class, $route_base and $twig_template value in your subclasss!');
110
        }
111
112
        if ('' === $this->attachment_class) {
0 ignored issues
show
introduced by
The condition '' === $this->attachment_class is always false.
Loading history...
113
            throw new InvalidArgumentException('You have to override the $attachment_class value in your subclass!');
114
        }
115
116
        if ('' === $this->parameter_class) {
0 ignored issues
show
introduced by
The condition '' === $this->parameter_class is always false.
Loading history...
117
            throw new InvalidArgumentException('You have to override the $parameter_class value in your subclass!');
118
        }
119
120
        $this->translator = $translator;
121
        $this->passwordEncoder = $passwordEncoder;
122
        $this->attachmentSubmitHandler = $attachmentSubmitHandler;
123
        $this->commentHelper = $commentHelper;
124
        $this->historyHelper = $historyHelper;
125
        $this->timeTravel = $timeTravel;
126
        $this->dataTableFactory = $dataTableFactory;
127
        $this->eventDispatcher = $eventDispatcher;
128
        $this->barcodeExampleGenerator = $barcodeExampleGenerator;
129
        $this->labelGenerator = $labelGenerator;
130
    }
131
132
    protected function _edit(AbstractNamedDBElement $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
133
    {
134
        $this->denyAccessUnlessGranted('read', $entity);
135
136
        $timeTravel_timestamp = null;
137
        if (null !== $timestamp) {
138
            $this->denyAccessUnlessGranted('@tools.timetravel');
139
            $this->denyAccessUnlessGranted('show_history', $entity);
140
            //If the timestamp only contains numbers interpret it as unix timestamp
141
            if (ctype_digit($timestamp)) {
142
                $timeTravel_timestamp = new \DateTime();
143
                $timeTravel_timestamp->setTimestamp((int) $timestamp);
144
            } else { //Try to parse it via DateTime
145
                $timeTravel_timestamp = new \DateTime($timestamp);
146
            }
147
            $this->timeTravel->revertEntityToTimestamp($entity, $timeTravel_timestamp);
148
        }
149
150
        if ($this->isGranted('show_history', $entity)) {
151
            $table = $this->dataTableFactory->createFromType(
152
                LogDataTable::class,
153
                [
154
                    'filter_elements' => $this->historyHelper->getAssociatedElements($entity),
155
                    'mode' => 'element_history',
156
                ],
157
                ['pageLength' => 10]
158
            )
159
                ->handleRequest($request);
160
161
            if ($table->isCallback()) {
162
                return $table->getResponse();
163
            }
164
        } else {
165
            $table = null;
166
        }
167
168
        $form_options =  [
169
            'attachment_class' => $this->attachment_class,
170
            'parameter_class' => $this->parameter_class,
171
            'disabled' => null !== $timeTravel_timestamp ? true : null,
172
        ];
173
174
        //Disable editing of options, if user is not allowed to use twig...
175
        if (
176
            $entity instanceof LabelProfile
177
            && $entity->getOptions()->getLinesMode() === 'twig'
178
            && !$this->isGranted('@labels.use_twig')
179
        ) {
180
            $form_options['disable_options'] = true;
181
        }
182
183
        $form = $this->createForm($this->form_class, $entity, $form_options);
184
185
        $form->handleRequest($request);
186
        if ($form->isSubmitted() && $form->isValid()) {
187
            //Check if we editing a user and if we need to change the password of it
188
            if ($entity instanceof User && ! empty($form['new_password']->getData())) {
189
                $password = $this->passwordEncoder->encodePassword($entity, $form['new_password']->getData());
190
                $entity->setPassword($password);
191
                //By default the user must change the password afterwards
192
                $entity->setNeedPwChange(true);
193
194
                $event = new SecurityEvent($entity);
195
                $this->eventDispatcher->dispatch($event, SecurityEvents::PASSWORD_CHANGED);
196
            }
197
198
            //Upload passed files
199
            $attachments = $form['attachments'];
200
            foreach ($attachments as $attachment) {
201
                /** @var FormInterface $attachment */
202
                $options = [
203
                    'secure_attachment' => $attachment['secureFile']->getData(),
204
                    'download_url' => $attachment['downloadURL']->getData(),
205
                ];
206
207
                try {
208
                    $this->attachmentSubmitHandler->handleFormSubmit($attachment->getData(), $attachment['file']->getData(), $options);
209
                } catch (AttachmentDownloadException $attachmentDownloadException) {
210
                    $this->addFlash(
211
                        'error',
212
                        $this->translator->trans('attachment.download_failed').' '.$attachmentDownloadException->getMessage()
213
                    );
214
                }
215
            }
216
217
            $this->commentHelper->setMessage($form['log_comment']->getData());
218
219
            $em->persist($entity);
220
            $em->flush();
221
            $this->addFlash('success', 'entity.edit_flash');
222
223
            //Rebuild form, so it is based on the updated data. Important for the parent field!
224
            //We can not use dynamic form events here, because the parent entity list is build from database!
225
            $form = $this->createForm($this->form_class, $entity, [
226
                'attachment_class' => $this->attachment_class,
227
                'parameter_class' => $this->parameter_class,
228
            ]);
229
        } elseif ($form->isSubmitted() && ! $form->isValid()) {
230
            $this->addFlash('error', 'entity.edit_flash.invalid');
231
        }
232
233
        //Show preview for LabelProfile if needed.
234
        if ($entity instanceof LabelProfile) {
235
            $example = $this->barcodeExampleGenerator->getElement($entity->getOptions()->getSupportedElement());
236
            $pdf_data = $this->labelGenerator->generateLabel($entity->getOptions(), $example);
237
        }
238
239
        return $this->render($this->twig_template, [
240
            'entity' => $entity,
241
            'form' => $form->createView(),
242
            'route_base' => $this->route_base,
243
            'datatable' => $table,
244
            'pdf_data' => $pdf_data ?? null,
245
            'timeTravel' => $timeTravel_timestamp,
246
        ]);
247
    }
248
249
    protected function _new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AbstractNamedDBElement $entity = null)
250
    {
251
        $master_picture_backup = null;
252
        if (null === $entity) {
253
            /** @var AbstractStructuralDBElement|User $new_entity */
254
            $new_entity = new $this->entity_class();
255
        } else {
256
            /** @var AbstractStructuralDBElement|User $new_entity */
257
            $new_entity = clone $entity;
258
        }
259
260
        $this->denyAccessUnlessGranted('read', $new_entity);
261
262
        //Basic edit form
263
        $form = $this->createForm($this->form_class, $new_entity, [
264
            'attachment_class' => $this->attachment_class,
265
            'parameter_class' => $this->parameter_class,
266
        ]);
267
268
        $form->handleRequest($request);
269
270
        if ($form->isSubmitted() && $form->isValid()) {
271
            if ($new_entity instanceof User && ! empty($form['new_password']->getData())) {
272
                $password = $this->passwordEncoder->encodePassword($new_entity, $form['new_password']->getData());
273
                $new_entity->setPassword($password);
274
                //By default the user must change the password afterwards
275
                $new_entity->setNeedPwChange(true);
276
            }
277
278
            //Upload passed files
279
            $attachments = $form['attachments'];
280
            foreach ($attachments as $attachment) {
281
                /** @var FormInterface $attachment */
282
                $options = [
283
                    'secure_attachment' => $attachment['secureFile']->getData(),
284
                    'download_url' => $attachment['downloadURL']->getData(),
285
                ];
286
287
                try {
288
                    $this->attachmentSubmitHandler->handleFormSubmit($attachment->getData(), $attachment['file']->getData(), $options);
289
                } catch (AttachmentDownloadException $attachmentDownloadException) {
290
                    $this->addFlash(
291
                        'error',
292
                        $this->translator->trans('attachment.download_failed').' '.$attachmentDownloadException->getMessage()
293
                    );
294
                }
295
            }
296
297
            $this->commentHelper->setMessage($form['log_comment']->getData());
298
299
            $em->persist($new_entity);
300
            $em->flush();
301
            $this->addFlash('success', 'entity.created_flash');
302
303
            return $this->redirectToRoute($this->route_base.'_edit', ['id' => $new_entity->getID()]);
304
        }
305
306
        if ($form->isSubmitted() && ! $form->isValid()) {
307
            $this->addFlash('error', 'entity.created_flash.invalid');
308
        }
309
310
        //Import form
311
        $import_form = $this->createForm(ImportType::class, ['entity_class' => $this->entity_class]);
312
        $import_form->handleRequest($request);
313
314
        if ($import_form->isSubmitted() && $import_form->isValid()) {
315
            /** @var UploadedFile $file */
316
            $file = $import_form['file']->getData();
317
            $data = $import_form->getData();
318
319
            $options = [
320
                'parent' => $data['parent'],
321
                'preserve_children' => $data['preserve_children'],
322
                'format' => $data['format'],
323
                'csv_separator' => $data['csv_separator'],
324
            ];
325
326
            $this->commentHelper->setMessage('Import '.$file->getClientOriginalName());
327
328
            $errors = $importer->fileToDBEntities($file, $this->entity_class, $options);
329
330
            foreach ($errors as $name => $error) {
331
                /** @var ConstraintViolationList $error */
332
                $this->addFlash('error', $name.':'.$error);
333
            }
334
        }
335
336
        //Mass creation form
337
        $mass_creation_form = $this->createForm(MassCreationForm::class, ['entity_class' => $this->entity_class]);
338
        $mass_creation_form->handleRequest($request);
339
340
        if ($mass_creation_form->isSubmitted() && $mass_creation_form->isValid()) {
341
            $data = $mass_creation_form->getData();
342
343
            //Create entries based on input
344
            $errors = [];
345
            $results = $importer->massCreation($data['lines'], $this->entity_class, $data['parent'], $errors);
346
347
            //Show errors to user:
348
            foreach ($errors as $error) {
349
                $this->addFlash('error', $error['entity']->getFullPath().':'.$error['violations']);
350
            }
351
352
            //Persist valid entities to DB
353
            foreach ($results as $result) {
354
                $em->persist($result);
355
            }
356
            $em->flush();
357
        }
358
359
        return $this->render($this->twig_template, [
360
            'entity' => $new_entity,
361
            'form' => $form->createView(),
362
            'import_form' => $import_form->createView(),
363
            'mass_creation_form' => $mass_creation_form->createView(),
364
            'route_base' => $this->route_base,
365
        ]);
366
    }
367
368
    protected function _delete(Request $request, AbstractNamedDBElement $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
369
    {
370
        $this->denyAccessUnlessGranted('delete', $entity);
371
372
        if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
373
            $entityManager = $this->getDoctrine()->getManager();
374
375
            //Check if we need to remove recursively
376
            if ($entity instanceof AbstractStructuralDBElement && $request->get('delete_recursive', false)) {
377
                $recursionHelper->delete($entity, false);
378
            } else {
379
                if ($entity instanceof AbstractStructuralDBElement) {
380
                    $parent = $entity->getParent();
381
382
                    //Move all sub entities to the current parent
383
                    foreach ($entity->getSubelements() as $subelement) {
384
                        $subelement->setParent($parent);
385
                        $entityManager->persist($subelement);
386
                    }
387
                }
388
389
                //Remove current element
390
                $entityManager->remove($entity);
391
            }
392
393
            $this->commentHelper->setMessage($request->request->get('log_comment', null));
394
395
            //Flush changes
396
            $entityManager->flush();
397
398
            $this->addFlash('success', 'attachment_type.deleted');
399
        } else {
400
            $this->addFlash('error', 'csfr_invalid');
401
        }
402
403
        return $this->redirectToRoute($this->route_base.'_new');
404
    }
405
406
    protected function _exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
407
    {
408
        $entity = new $this->entity_class();
409
410
        $this->denyAccessUnlessGranted('read', $entity);
411
412
        $entities = $em->getRepository($this->entity_class)->findAll();
413
414
        return $exporter->exportEntityFromRequest($entities, $request);
415
    }
416
417
    protected function _exportEntity(AbstractNamedDBElement $entity, EntityExporter $exporter, Request $request): \Symfony\Component\HttpFoundation\Response
418
    {
419
        $this->denyAccessUnlessGranted('read', $entity);
420
421
        return $exporter->exportEntityFromRequest($entity, $request);
422
    }
423
}
424