Passed
Push — master ( 2d425f...eb03d1 )
by Jan
04:57 queued 10s
created

BaseAdminController::__construct()   A

Complexity

Conditions 6
Paths 3

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 12
c 1
b 0
f 0
nc 3
nop 8
dl 0
loc 21
rs 9.2222

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\UserSystem\User;
49
use App\Exceptions\AttachmentDownloadException;
50
use App\Form\AdminPages\ImportType;
51
use App\Form\AdminPages\MassCreationForm;
52
use App\Services\Attachments\AttachmentManager;
53
use App\Services\Attachments\AttachmentSubmitHandler;
54
use App\Services\EntityExporter;
55
use App\Services\EntityImporter;
56
use App\Services\LogSystem\EventCommentHelper;
57
use App\Services\LogSystem\HistoryHelper;
58
use App\Services\LogSystem\TimeTravel;
59
use App\Services\StructuralElementRecursionHelper;
60
use Doctrine\ORM\EntityManagerInterface;
61
use InvalidArgumentException;
62
use Omines\DataTablesBundle\DataTableFactory;
63
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
64
use Symfony\Component\Form\FormInterface;
65
use Symfony\Component\HttpFoundation\File\UploadedFile;
66
use Symfony\Component\HttpFoundation\RedirectResponse;
67
use Symfony\Component\HttpFoundation\Request;
68
use Symfony\Component\HttpFoundation\Response;
69
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
70
use Symfony\Component\Validator\ConstraintViolationList;
71
use Symfony\Contracts\Translation\TranslatorInterface;
72
73
abstract class BaseAdminController extends AbstractController
74
{
75
    protected $entity_class = '';
76
    protected $form_class = '';
77
    protected $twig_template = '';
78
    protected $route_base = '';
79
    protected $attachment_class = '';
80
81
    protected $passwordEncoder;
82
    protected $translator;
83
    protected $attachmentHelper;
84
    protected $attachmentSubmitHandler;
85
    protected $commentHelper;
86
87
    protected $historyHelper;
88
    protected $timeTravel;
89
    protected $dataTableFactory;
90
91
    public function __construct(TranslatorInterface $translator, UserPasswordEncoderInterface $passwordEncoder,
92
        AttachmentManager $attachmentHelper, AttachmentSubmitHandler $attachmentSubmitHandler,
93
        EventCommentHelper $commentHelper, HistoryHelper $historyHelper, TimeTravel $timeTravel,
94
        DataTableFactory $dataTableFactory)
95
    {
96
        if ('' === $this->entity_class || '' === $this->form_class || '' === $this->twig_template || '' === $this->route_base) {
0 ignored issues
show
introduced by
The condition '' === $this->twig_template is always false.
Loading history...
introduced by
The condition '' === $this->form_class is always false.
Loading history...
introduced by
The condition '' === $this->route_base is always false.
Loading history...
97
            throw new InvalidArgumentException('You have to override the $entity_class, $form_class, $route_base and $twig_template value in your subclasss!');
98
        }
99
100
        if ('' === $this->attachment_class) {
0 ignored issues
show
introduced by
The condition '' === $this->attachment_class is always false.
Loading history...
101
            throw new InvalidArgumentException('You have to override the $attachment_class value in your subclass!');
102
        }
103
104
        $this->translator = $translator;
105
        $this->passwordEncoder = $passwordEncoder;
106
        $this->attachmentHelper = $attachmentHelper;
107
        $this->attachmentSubmitHandler = $attachmentSubmitHandler;
108
        $this->commentHelper = $commentHelper;
109
        $this->historyHelper = $historyHelper;
110
        $this->timeTravel = $timeTravel;
111
        $this->dataTableFactory = $dataTableFactory;
112
    }
113
114
115
    protected function _edit(AbstractNamedDBElement $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null) : Response
116
    {
117
        $this->denyAccessUnlessGranted('read', $entity);
118
119
120
        $timeTravel_timestamp = null;
121
        if ($timestamp !== null) {
122
            $this->denyAccessUnlessGranted('@tools.timeTravel');
123
            $this->denyAccessUnlessGranted('show_history', $entity);
124
            //If the timestamp only contains numbers interpret it as unix timestamp
125
            if (ctype_digit($timestamp)) {
126
                $timeTravel_timestamp = new \DateTime();
127
                $timeTravel_timestamp->setTimestamp((int) $timestamp);
128
            } else { //Try to parse it via DateTime
129
                $timeTravel_timestamp = new \DateTime($timestamp);
130
            }
131
            $this->timeTravel->revertEntityToTimestamp($entity, $timeTravel_timestamp);
132
        }
133
134
        if ($this->isGranted('show_history', $entity) ) {
135
            $table = $this->dataTableFactory->createFromType(
136
                LogDataTable::class,
137
                [
138
                    'filter_elements' => $this->historyHelper->getAssociatedElements($entity),
139
                    'mode' => 'element_history'
140
                ],
141
                ['pageLength' => 10]
142
            )
143
                ->handleRequest($request);
144
145
            if ($table->isCallback()) {
146
                return $table->getResponse();
147
            }
148
        } else {
149
            $table = null;
150
        }
151
152
        $form = $this->createForm($this->form_class, $entity, [
0 ignored issues
show
Bug introduced by
$this->form_class of type mixed is incompatible with the type string expected by parameter $type of Symfony\Bundle\Framework...ontroller::createForm(). ( Ignorable by Annotation )

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

152
        $form = $this->createForm(/** @scrutinizer ignore-type */ $this->form_class, $entity, [
Loading history...
153
            'attachment_class' => $this->attachment_class,
154
            'disabled' => $timeTravel_timestamp !== null ? true : null
155
        ]);
156
157
        $form->handleRequest($request);
158
        if ($form->isSubmitted() && $form->isValid()) {
159
            //Check if we editing a user and if we need to change the password of it
160
            if ($entity instanceof User && ! empty($form['new_password']->getData())) {
161
                $password = $this->passwordEncoder->encodePassword($entity, $form['new_password']->getData());
162
                $entity->setPassword($password);
163
                //By default the user must change the password afterwards
164
                $entity->setNeedPwChange(true);
165
            }
166
167
            //Upload passed files
168
            $attachments = $form['attachments'];
169
            foreach ($attachments as $attachment) {
170
                /** @var FormInterface $attachment */
171
                $options = [
172
                    'secure_attachment' => $attachment['secureFile']->getData(),
173
                    'download_url' => $attachment['downloadURL']->getData(),
174
                ];
175
176
                try {
177
                    $this->attachmentSubmitHandler->handleFormSubmit($attachment->getData(), $attachment['file']->getData(), $options);
178
                } catch (AttachmentDownloadException $attachmentDownloadException) {
179
                    $this->addFlash(
180
                        'error',
181
                        $this->translator->trans('attachment.download_failed').' '.$attachmentDownloadException->getMessage()
182
                    );
183
                }
184
            }
185
186
            $this->commentHelper->setMessage($form['log_comment']->getData());
187
188
            $em->persist($entity);
189
            $em->flush();
190
            $this->addFlash('success', 'entity.edit_flash');
191
192
            //Rebuild form, so it is based on the updated data. Important for the parent field!
193
            //We can not use dynamic form events here, because the parent entity list is build from database!
194
            $form = $this->createForm($this->form_class, $entity, ['attachment_class' => $this->attachment_class]);
195
        } elseif ($form->isSubmitted() && ! $form->isValid()) {
196
            $this->addFlash('error', 'entity.edit_flash.invalid');
197
        }
198
199
        return $this->render($this->twig_template, [
0 ignored issues
show
Bug introduced by
$this->twig_template of type mixed is incompatible with the type string expected by parameter $view of Symfony\Bundle\Framework...actController::render(). ( Ignorable by Annotation )

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

199
        return $this->render(/** @scrutinizer ignore-type */ $this->twig_template, [
Loading history...
200
            'entity' => $entity,
201
            'form' => $form->createView(),
202
            'attachment_helper' => $this->attachmentHelper,
203
            'route_base' => $this->route_base,
204
            'datatable' => $table,
205
            'timeTravel' => $timeTravel_timestamp
206
        ]);
207
    }
208
209
    protected function _new(Request $request, EntityManagerInterface $em, EntityImporter $importer)
210
    {
211
        /** @var AbstractStructuralDBElement|User $new_entity */
212
        $new_entity = new $this->entity_class();
213
214
        $this->denyAccessUnlessGranted('read', $new_entity);
215
216
        //Basic edit form
217
        $form = $this->createForm($this->form_class, $new_entity, ['attachment_class' => $this->attachment_class]);
0 ignored issues
show
Bug introduced by
$this->form_class of type mixed is incompatible with the type string expected by parameter $type of Symfony\Bundle\Framework...ontroller::createForm(). ( Ignorable by Annotation )

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

217
        $form = $this->createForm(/** @scrutinizer ignore-type */ $this->form_class, $new_entity, ['attachment_class' => $this->attachment_class]);
Loading history...
218
219
        $form->handleRequest($request);
220
221
        if ($form->isSubmitted() && $form->isValid()) {
222
            if ($new_entity instanceof User && ! empty($form['new_password']->getData())) {
223
                $password = $this->passwordEncoder->encodePassword($new_entity, $form['new_password']->getData());
224
                $new_entity->setPassword($password);
225
                //By default the user must change the password afterwards
226
                $new_entity->setNeedPwChange(true);
227
            }
228
229
            //Upload passed files
230
            $attachments = $form['attachments'];
231
            foreach ($attachments as $attachment) {
232
                /** @var FormInterface $attachment */
233
                $options = [
234
                    'secure_attachment' => $attachment['secureFile']->getData(),
235
                    'download_url' => $attachment['downloadURL']->getData(),
236
                ];
237
238
                try {
239
                    $this->attachmentSubmitHandler->handleFormSubmit($attachment->getData(), $attachment['file']->getData(), $options);
240
                } catch (AttachmentDownloadException $attachmentDownloadException) {
241
                    $this->addFlash(
242
                        'error',
243
                        $this->translator->trans('attachment.download_failed').' '.$attachmentDownloadException->getMessage()
244
                    );
245
                }
246
            }
247
248
            $this->commentHelper->setMessage($form['log_comment']->getData());
249
250
            $em->persist($new_entity);
251
            $em->flush();
252
            $this->addFlash('success', 'entity.created_flash');
253
254
            return $this->redirectToRoute($this->route_base.'_edit', ['id' => $new_entity->getID()]);
0 ignored issues
show
Bug introduced by
Are you sure $this->route_base of type mixed can be used in concatenation? ( Ignorable by Annotation )

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

254
            return $this->redirectToRoute(/** @scrutinizer ignore-type */ $this->route_base.'_edit', ['id' => $new_entity->getID()]);
Loading history...
255
        }
256
257
        if ($form->isSubmitted() && ! $form->isValid()) {
258
            $this->addFlash('error', 'entity.created_flash.invalid');
259
        }
260
261
        //Import form
262
        $import_form = $this->createForm(ImportType::class, ['entity_class' => $this->entity_class]);
263
        $import_form->handleRequest($request);
264
265
        if ($import_form->isSubmitted() && $import_form->isValid()) {
266
            /** @var UploadedFile $file */
267
            $file = $import_form['file']->getData();
268
            $data = $import_form->getData();
269
270
            $options = [
271
                'parent' => $data['parent'],
272
                'preserve_children' => $data['preserve_children'],
273
                'format' => $data['format'],
274
                'csv_separator' => $data['csv_separator'],
275
            ];
276
277
            $this->commentHelper->setMessage('Import ' . $file->getClientOriginalName());
278
279
280
281
            $errors = $importer->fileToDBEntities($file, $this->entity_class, $options);
0 ignored issues
show
Bug introduced by
$this->entity_class of type mixed is incompatible with the type string expected by parameter $class_name of App\Services\EntityImporter::fileToDBEntities(). ( Ignorable by Annotation )

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

281
            $errors = $importer->fileToDBEntities($file, /** @scrutinizer ignore-type */ $this->entity_class, $options);
Loading history...
282
283
            foreach ($errors as $name => $error) {
284
                /** @var ConstraintViolationList $error */
285
                $this->addFlash('error', $name.':'.$error);
286
            }
287
        }
288
289
        //Mass creation form
290
        $mass_creation_form = $this->createForm(MassCreationForm::class, ['entity_class' => $this->entity_class]);
291
        $mass_creation_form->handleRequest($request);
292
293
        if ($mass_creation_form->isSubmitted() && $mass_creation_form->isValid()) {
294
            $data = $mass_creation_form->getData();
295
296
            //Create entries based on input
297
            $errors = [];
298
            $results = $importer->massCreation($data['lines'], $this->entity_class, $data['parent'], $errors);
0 ignored issues
show
Bug introduced by
$this->entity_class of type mixed is incompatible with the type string expected by parameter $class_name of App\Services\EntityImporter::massCreation(). ( Ignorable by Annotation )

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

298
            $results = $importer->massCreation($data['lines'], /** @scrutinizer ignore-type */ $this->entity_class, $data['parent'], $errors);
Loading history...
299
300
            //Show errors to user:
301
            foreach ($errors as $error) {
302
                $this->addFlash('error', $error['entity']->getFullPath().':'.$error['violations']);
303
            }
304
305
            //Persist valid entities to DB
306
            foreach ($results as $result) {
307
                $em->persist($result);
308
            }
309
            $em->flush();
310
        }
311
312
        return $this->render($this->twig_template, [
0 ignored issues
show
Bug introduced by
$this->twig_template of type mixed is incompatible with the type string expected by parameter $view of Symfony\Bundle\Framework...actController::render(). ( Ignorable by Annotation )

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

312
        return $this->render(/** @scrutinizer ignore-type */ $this->twig_template, [
Loading history...
313
            'entity' => $new_entity,
314
            'form' => $form->createView(),
315
            'import_form' => $import_form->createView(),
316
            'mass_creation_form' => $mass_creation_form->createView(),
317
            'attachment_helper' => $this->attachmentHelper,
318
            'route_base' => $this->route_base,
319
        ]);
320
    }
321
322
    protected function _delete(Request $request, AbstractNamedDBElement $entity, StructuralElementRecursionHelper $recursionHelper) : RedirectResponse
323
    {
324
        $this->denyAccessUnlessGranted('delete', $entity);
325
326
        if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
327
            $entityManager = $this->getDoctrine()->getManager();
328
329
            //Check if we need to remove recursively
330
            if ($entity instanceof AbstractStructuralDBElement && $request->get('delete_recursive', false)) {
331
                $recursionHelper->delete($entity, false);
332
            } else {
333
                if ($entity instanceof AbstractStructuralDBElement) {
334
                    $parent = $entity->getParent();
335
336
                    //Move all sub entities to the current parent
337
                    foreach ($entity->getSubelements() as $subelement) {
338
                        $subelement->setParent($parent);
339
                        $entityManager->persist($subelement);
340
                    }
341
                }
342
343
                //Remove current element
344
                $entityManager->remove($entity);
345
            }
346
347
            $this->commentHelper->setMessage($request->request->get('log_comment', null));
348
349
            //Flush changes
350
            $entityManager->flush();
351
352
            $this->addFlash('success', 'attachment_type.deleted');
353
        } else {
354
            $this->addFlash('error', 'csfr_invalid');
355
        }
356
357
        return $this->redirectToRoute($this->route_base.'_new');
0 ignored issues
show
Bug introduced by
Are you sure $this->route_base of type mixed can be used in concatenation? ( Ignorable by Annotation )

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

357
        return $this->redirectToRoute(/** @scrutinizer ignore-type */ $this->route_base.'_new');
Loading history...
358
    }
359
360
    protected function _exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
361
    {
362
        $entity = new $this->entity_class();
363
364
        $this->denyAccessUnlessGranted('read', $entity);
365
366
        $entities = $em->getRepository($this->entity_class)->findAll();
0 ignored issues
show
Bug introduced by
$this->entity_class of type mixed is incompatible with the type string expected by parameter $className of Doctrine\Persistence\Obj...anager::getRepository(). ( Ignorable by Annotation )

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

366
        $entities = $em->getRepository(/** @scrutinizer ignore-type */ $this->entity_class)->findAll();
Loading history...
367
368
        return $exporter->exportEntityFromRequest($entities, $request);
369
    }
370
371
    protected function _exportEntity(AbstractNamedDBElement $entity, EntityExporter $exporter, Request $request): \Symfony\Component\HttpFoundation\Response
372
    {
373
        $this->denyAccessUnlessGranted('read', $entity);
374
375
        return $exporter->exportEntityFromRequest($entity, $request);
376
    }
377
}
378