Passed
Push — master ( 9cc210...167e46 )
by Jan
04:55
created

BaseAdminController::__construct()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 17
nc 4
nop 11
dl 0
loc 29
rs 8.8333
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\Attachments\AttachmentType;
47
use App\Entity\Base\AbstractDBElement;
48
use App\Entity\Base\AbstractNamedDBElement;
49
use App\Entity\Base\AbstractPartsContainingDBElement;
50
use App\Entity\Base\AbstractStructuralDBElement;
51
use App\Entity\Base\PartsContainingRepositoryInterface;
52
use App\Entity\LabelSystem\LabelProfile;
53
use App\Entity\PriceInformations\Currency;
54
use App\Entity\UserSystem\Group;
55
use App\Entity\UserSystem\User;
56
use App\Events\SecurityEvent;
57
use App\Events\SecurityEvents;
58
use App\Exceptions\AttachmentDownloadException;
59
use App\Form\AdminPages\ImportType;
60
use App\Form\AdminPages\MassCreationForm;
61
use App\Repository\AbstractPartsContainingRepository;
62
use App\Services\Attachments\AttachmentSubmitHandler;
63
use App\Services\EntityExporter;
64
use App\Services\EntityImporter;
65
use App\Services\LabelSystem\Barcodes\BarcodeExampleElementsGenerator;
66
use App\Services\LabelSystem\LabelGenerator;
67
use App\Services\LogSystem\EventCommentHelper;
68
use App\Services\LogSystem\HistoryHelper;
69
use App\Services\LogSystem\TimeTravel;
70
use App\Services\StructuralElementRecursionHelper;
71
use Doctrine\ORM\EntityManagerInterface;
72
use InvalidArgumentException;
73
use Omines\DataTablesBundle\DataTableFactory;
74
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
75
use Symfony\Component\EventDispatcher\EventDispatcher;
76
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
77
use Symfony\Component\Form\FormInterface;
78
use Symfony\Component\HttpFoundation\File\UploadedFile;
79
use Symfony\Component\HttpFoundation\RedirectResponse;
80
use Symfony\Component\HttpFoundation\Request;
81
use Symfony\Component\HttpFoundation\Response;
82
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
83
use Symfony\Component\Validator\ConstraintViolationList;
84
use Symfony\Contracts\Translation\TranslatorInterface;
85
86
abstract class BaseAdminController extends AbstractController
87
{
88
    protected $entity_class = '';
89
    protected $form_class = '';
90
    protected $twig_template = '';
91
    protected $route_base = '';
92
    protected $attachment_class = '';
93
    protected $parameter_class = '';
94
95
    protected $passwordEncoder;
96
    protected $translator;
97
    protected $attachmentSubmitHandler;
98
    protected $commentHelper;
99
100
    protected $historyHelper;
101
    protected $timeTravel;
102
    protected $dataTableFactory;
103
    /**
104
     * @var EventDispatcher
105
     */
106
    protected $eventDispatcher;
107
    protected $labelGenerator;
108
    protected $barcodeExampleGenerator;
109
110
    protected $entityManager;
111
112
    public function __construct(TranslatorInterface $translator, UserPasswordEncoderInterface $passwordEncoder,
113
        AttachmentSubmitHandler $attachmentSubmitHandler,
114
        EventCommentHelper $commentHelper, HistoryHelper $historyHelper, TimeTravel $timeTravel,
115
        DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher, BarcodeExampleElementsGenerator $barcodeExampleGenerator,
116
        LabelGenerator $labelGenerator, EntityManagerInterface $entityManager)
117
    {
118
        if ('' === $this->entity_class || '' === $this->form_class || '' === $this->twig_template || '' === $this->route_base) {
0 ignored issues
show
introduced by
The condition '' === $this->form_class is always false.
Loading history...
introduced by
The condition '' === $this->twig_template is always false.
Loading history...
introduced by
The condition '' === $this->route_base is always false.
Loading history...
119
            throw new InvalidArgumentException('You have to override the $entity_class, $form_class, $route_base and $twig_template value in your subclasss!');
120
        }
121
122
        if ('' === $this->attachment_class) {
0 ignored issues
show
introduced by
The condition '' === $this->attachment_class is always false.
Loading history...
123
            throw new InvalidArgumentException('You have to override the $attachment_class value in your subclass!');
124
        }
125
126
        if ('' === $this->parameter_class) {
0 ignored issues
show
introduced by
The condition '' === $this->parameter_class is always false.
Loading history...
127
            throw new InvalidArgumentException('You have to override the $parameter_class value in your subclass!');
128
        }
129
130
        $this->translator = $translator;
131
        $this->passwordEncoder = $passwordEncoder;
132
        $this->attachmentSubmitHandler = $attachmentSubmitHandler;
133
        $this->commentHelper = $commentHelper;
134
        $this->historyHelper = $historyHelper;
135
        $this->timeTravel = $timeTravel;
136
        $this->dataTableFactory = $dataTableFactory;
137
        $this->eventDispatcher = $eventDispatcher;
138
        $this->barcodeExampleGenerator = $barcodeExampleGenerator;
139
        $this->labelGenerator = $labelGenerator;
140
        $this->entityManager = $entityManager;
141
    }
142
143
    protected function revertElementIfNeeded(AbstractDBElement $entity, ?string $timestamp): ?\DateTime
144
    {
145
        if (null !== $timestamp) {
146
            $this->denyAccessUnlessGranted('@tools.timetravel');
147
            $this->denyAccessUnlessGranted('show_history', $entity);
148
            //If the timestamp only contains numbers interpret it as unix timestamp
149
            if (ctype_digit($timestamp)) {
150
                $timeTravel_timestamp = new \DateTime();
151
                $timeTravel_timestamp->setTimestamp((int) $timestamp);
152
            } else { //Try to parse it via DateTime
153
                $timeTravel_timestamp = new \DateTime($timestamp);
154
            }
155
            $this->timeTravel->revertEntityToTimestamp($entity, $timeTravel_timestamp);
156
157
            return $timeTravel_timestamp;
158
        }
159
        return null;
160
    }
161
162
    /**
163
     * Perform some additional actions, when the form was valid, but before the entity is saved.
164
     * @return bool Return true, to save entity normally, return false, to abort saving.
165
     */
166
    protected function additionalActionEdit(FormInterface $form, AbstractNamedDBElement $entity): bool
167
    {
168
        return true;
169
    }
170
171
    protected function _edit(AbstractNamedDBElement $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
172
    {
173
        $this->denyAccessUnlessGranted('read', $entity);
174
175
        $timeTravel_timestamp = $this->revertElementIfNeeded($entity, $timestamp);
176
177
178
        if ($this->isGranted('show_history', $entity)) {
179
            $table = $this->dataTableFactory->createFromType(
180
                LogDataTable::class,
181
                [
182
                    'filter_elements' => $this->historyHelper->getAssociatedElements($entity),
183
                    'mode' => 'element_history',
184
                ],
185
                ['pageLength' => 10]
186
            )
187
                ->handleRequest($request);
188
189
            if ($table->isCallback()) {
190
                return $table->getResponse();
191
            }
192
        } else {
193
            $table = null;
194
        }
195
196
        $form_options = [
197
            'attachment_class' => $this->attachment_class,
198
            'parameter_class' => $this->parameter_class,
199
            'disabled' => null !== $timeTravel_timestamp ? true : false,
200
        ];
201
202
        //Disable editing of options, if user is not allowed to use twig...
203
        if (
204
            $entity instanceof LabelProfile
205
            && 'twig' === $entity->getOptions()->getLinesMode()
206
            && ! $this->isGranted('@labels.use_twig')
207
        ) {
208
            $form_options['disable_options'] = true;
209
        }
210
211
        $form = $this->createForm($this->form_class, $entity, $form_options);
212
213
        $form->handleRequest($request);
214
        if ($form->isSubmitted() && $form->isValid()) {
215
            if ($this->additionalActionEdit($form, $entity)) {
216
                //Upload passed files
217
                $attachments = $form['attachments'];
218
                foreach ($attachments as $attachment) {
219
                    /** @var FormInterface $attachment */
220
                    $options = [
221
                        'secure_attachment' => $attachment['secureFile']->getData(),
222
                        'download_url' => $attachment['downloadURL']->getData(),
223
                    ];
224
225
                    try {
226
                        $this->attachmentSubmitHandler->handleFormSubmit(
227
                            $attachment->getData(),
228
                            $attachment['file']->getData(),
229
                            $options
230
                        );
231
                    } catch (AttachmentDownloadException $attachmentDownloadException) {
232
                        $this->addFlash(
233
                            'error',
234
                            $this->translator->trans(
235
                                'attachment.download_failed'
236
                            ).' '.$attachmentDownloadException->getMessage()
237
                        );
238
                    }
239
                }
240
241
                $this->commentHelper->setMessage($form['log_comment']->getData());
242
243
                $em->persist($entity);
244
                $em->flush();
245
                $this->addFlash('success', 'entity.edit_flash');
246
            }
247
248
            //Rebuild form, so it is based on the updated data. Important for the parent field!
249
            //We can not use dynamic form events here, because the parent entity list is build from database!
250
            $form = $this->createForm($this->form_class, $entity, [
251
                'attachment_class' => $this->attachment_class,
252
                'parameter_class' => $this->parameter_class,
253
            ]);
254
        } elseif ($form->isSubmitted() && ! $form->isValid()) {
255
            $this->addFlash('error', 'entity.edit_flash.invalid');
256
        }
257
258
        //Show preview for LabelProfile if needed.
259
        if ($entity instanceof LabelProfile) {
260
            $example = $this->barcodeExampleGenerator->getElement($entity->getOptions()->getSupportedElement());
261
            $pdf_data = $this->labelGenerator->generateLabel($entity->getOptions(), $example);
262
        }
263
264
        /** @var AbstractPartsContainingRepository $repo */
265
        $repo = $this->entityManager->getRepository($this->entity_class);
266
267
268
        return $this->render($this->twig_template, [
269
            'entity' => $entity,
270
            'form' => $form->createView(),
271
            'route_base' => $this->route_base,
272
            'datatable' => $table,
273
            'pdf_data' => $pdf_data ?? null,
274
            'timeTravel' => $timeTravel_timestamp,
275
            'repo' => $repo,
276
            'partsContainingElement' => $repo instanceof PartsContainingRepositoryInterface,
277
        ]);
278
    }
279
280
    /**
281
     * Perform some additional actions, when the form was valid, but before the entity is saved.
282
     * @return bool Return true, to save entity normally, return false, to abort saving.
283
     */
284
    protected function additionalActionNew(FormInterface $form, AbstractNamedDBElement $entity): bool
285
    {
286
        return true;
287
    }
288
289
    protected function _new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AbstractNamedDBElement $entity = null)
290
    {
291
        $master_picture_backup = null;
292
        if (null === $entity) {
293
            /** @var AbstractStructuralDBElement|User $new_entity */
294
            $new_entity = new $this->entity_class();
295
        } else {
296
            /** @var AbstractStructuralDBElement|User $new_entity */
297
            $new_entity = clone $entity;
298
        }
299
300
        $this->denyAccessUnlessGranted('read', $new_entity);
301
302
        //Basic edit form
303
        $form = $this->createForm($this->form_class, $new_entity, [
304
            'attachment_class' => $this->attachment_class,
305
            'parameter_class' => $this->parameter_class,
306
        ]);
307
308
        $form->handleRequest($request);
309
310
        if ($form->isSubmitted() && $form->isValid()) {
311
312
            //Perform additional actions
313
            if ($this->additionalActionNew($form, $new_entity)) {
314
                //Upload passed files
315
                $attachments = $form['attachments'];
316
                foreach ($attachments as $attachment) {
317
                    /** @var FormInterface $attachment */
318
                    $options = [
319
                        'secure_attachment' => $attachment['secureFile']->getData(),
320
                        'download_url' => $attachment['downloadURL']->getData(),
321
                    ];
322
323
                    try {
324
                        $this->attachmentSubmitHandler->handleFormSubmit(
325
                            $attachment->getData(),
326
                            $attachment['file']->getData(),
327
                            $options
328
                        );
329
                    } catch (AttachmentDownloadException $attachmentDownloadException) {
330
                        $this->addFlash(
331
                            'error',
332
                            $this->translator->trans(
333
                                'attachment.download_failed'
334
                            ).' '.$attachmentDownloadException->getMessage()
335
                        );
336
                    }
337
                }
338
339
                $this->commentHelper->setMessage($form['log_comment']->getData());
340
341
                $em->persist($new_entity);
342
                $em->flush();
343
                $this->addFlash('success', 'entity.created_flash');
344
345
                return $this->redirectToRoute($this->route_base.'_edit', ['id' => $new_entity->getID()]);
346
            }
347
        }
348
349
        if ($form->isSubmitted() && ! $form->isValid()) {
350
            $this->addFlash('error', 'entity.created_flash.invalid');
351
        }
352
353
        //Import form
354
        $import_form = $this->createForm(ImportType::class, ['entity_class' => $this->entity_class]);
355
        $import_form->handleRequest($request);
356
357
        if ($import_form->isSubmitted() && $import_form->isValid()) {
358
            /** @var UploadedFile $file */
359
            $file = $import_form['file']->getData();
360
            $data = $import_form->getData();
361
362
            $options = [
363
                'parent' => $data['parent'],
364
                'preserve_children' => $data['preserve_children'],
365
                'format' => $data['format'],
366
                'csv_separator' => $data['csv_separator'],
367
            ];
368
369
            $this->commentHelper->setMessage('Import '.$file->getClientOriginalName());
370
371
            $errors = $importer->fileToDBEntities($file, $this->entity_class, $options);
372
373
            foreach ($errors as $name => $error) {
374
                /** @var ConstraintViolationList $error */
375
                $this->addFlash('error', $name.':'.$error);
376
            }
377
        }
378
379
        //Mass creation form
380
        $mass_creation_form = $this->createForm(MassCreationForm::class, ['entity_class' => $this->entity_class]);
381
        $mass_creation_form->handleRequest($request);
382
383
        if ($mass_creation_form->isSubmitted() && $mass_creation_form->isValid()) {
384
            $data = $mass_creation_form->getData();
385
386
            //Create entries based on input
387
            $errors = [];
388
            $results = $importer->massCreation($data['lines'], $this->entity_class, $data['parent'], $errors);
389
390
            //Show errors to user:
391
            foreach ($errors as $error) {
392
                $this->addFlash('error', $error['entity']->getFullPath().':'.$error['violations']);
393
            }
394
395
            //Persist valid entities to DB
396
            foreach ($results as $result) {
397
                $em->persist($result);
398
            }
399
            $em->flush();
400
        }
401
402
403
        return $this->render($this->twig_template, [
404
            'entity' => $new_entity,
405
            'form' => $form->createView(),
406
            'import_form' => $import_form->createView(),
407
            'mass_creation_form' => $mass_creation_form->createView(),
408
            'route_base' => $this->route_base,
409
        ]);
410
    }
411
412
    /**
413
     * Performs checks if the element can be deleted safely. Otherwise an flash message is added.
414
     * @param  AbstractNamedDBElement  $entity The element that should be checked.
415
     * @return bool True if the the element can be deleted, false if not
416
     */
417
    protected function deleteCheck(AbstractNamedDBElement $entity): bool
418
    {
419
        if ($entity instanceof AbstractPartsContainingDBElement) {
420
            /** @var AbstractPartsContainingRepository $repo */
421
            $repo = $this->entityManager->getRepository($this->entity_class);
422
            if ($repo->getPartsCount($entity) > 0) {
423
                $this->addFlash('error', 'entity.delete.must_not_contain_parts');
424
                return false;
425
            }
426
        }
427
        return true;
428
    }
429
430
    protected function _delete(Request $request, AbstractNamedDBElement $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
431
    {
432
        $this->denyAccessUnlessGranted('delete', $entity);
433
434
        if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
435
            $entityManager = $this->getDoctrine()->getManager();
436
437
            if (!$this->deleteCheck($entity)) {
438
                return $this->redirectToRoute($this->route_base.'_edit', ['id' => $entity->getID()]);
439
            }
440
441
            //Check if we need to remove recursively
442
            if ($entity instanceof AbstractStructuralDBElement && $request->get('delete_recursive', false)) {
443
                $recursionHelper->delete($entity, false);
444
            } else {
445
                if ($entity instanceof AbstractStructuralDBElement) {
446
                    $parent = $entity->getParent();
447
448
                    //Move all sub entities to the current parent
449
                    foreach ($entity->getSubelements() as $subelement) {
450
                        $subelement->setParent($parent);
451
                        $entityManager->persist($subelement);
452
                    }
453
                }
454
455
                //Remove current element
456
                $entityManager->remove($entity);
457
            }
458
459
            $this->commentHelper->setMessage($request->request->get('log_comment', null));
460
461
            //Flush changes
462
            $entityManager->flush();
463
464
            $this->addFlash('success', 'attachment_type.deleted');
465
        } else {
466
            $this->addFlash('error', 'csfr_invalid');
467
        }
468
469
        return $this->redirectToRoute($this->route_base.'_new');
470
    }
471
472
    protected function _exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
473
    {
474
        $entity = new $this->entity_class();
475
        $this->denyAccessUnlessGranted('read', $entity);
476
        $entities = $em->getRepository($this->entity_class)->findAll();
477
        return $exporter->exportEntityFromRequest($entities, $request);
478
    }
479
480
    protected function _exportEntity(AbstractNamedDBElement $entity, EntityExporter $exporter, Request $request): \Symfony\Component\HttpFoundation\Response
481
    {
482
        $this->denyAccessUnlessGranted('read', $entity);
483
        return $exporter->exportEntityFromRequest($entity, $request);
484
    }
485
}
486