Passed
Push — master ( be4768...895194 )
by Jan
09:55
created

BaseAdminController::__construct()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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

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