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

BaseAdminController::__construct()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 16
c 1
b 0
f 0
nc 4
nop 10
dl 0
loc 28
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\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