Passed
Push — master ( d7dee9...483d68 )
by Jan
04:53 queued 10s
created

BaseAdminController::_new()   C

Complexity

Conditions 16
Paths 14

Size

Total Lines 111
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 16
eloc 61
c 1
b 0
f 0
nc 14
nop 3
dl 0
loc 111
rs 5.5666

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\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
    protected $parameter_class = '';
81
82
    protected $passwordEncoder;
83
    protected $translator;
84
    protected $attachmentHelper;
85
    protected $attachmentSubmitHandler;
86
    protected $commentHelper;
87
88
    protected $historyHelper;
89
    protected $timeTravel;
90
    protected $dataTableFactory;
91
92
    public function __construct(TranslatorInterface $translator, UserPasswordEncoderInterface $passwordEncoder,
93
        AttachmentManager $attachmentHelper, AttachmentSubmitHandler $attachmentSubmitHandler,
94
        EventCommentHelper $commentHelper, HistoryHelper $historyHelper, TimeTravel $timeTravel,
95
        DataTableFactory $dataTableFactory)
96
    {
97
        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...
98
            throw new InvalidArgumentException('You have to override the $entity_class, $form_class, $route_base and $twig_template value in your subclasss!');
99
        }
100
101
        if ('' === $this->attachment_class) {
0 ignored issues
show
introduced by
The condition '' === $this->attachment_class is always false.
Loading history...
102
            throw new InvalidArgumentException('You have to override the $attachment_class value in your subclass!');
103
        }
104
105
        if ('' === $this->parameter_class) {
0 ignored issues
show
introduced by
The condition '' === $this->parameter_class is always false.
Loading history...
106
            throw new InvalidArgumentException('You have to override the $parameter_class value in your subclass!');
107
        }
108
109
        $this->translator = $translator;
110
        $this->passwordEncoder = $passwordEncoder;
111
        $this->attachmentHelper = $attachmentHelper;
112
        $this->attachmentSubmitHandler = $attachmentSubmitHandler;
113
        $this->commentHelper = $commentHelper;
114
        $this->historyHelper = $historyHelper;
115
        $this->timeTravel = $timeTravel;
116
        $this->dataTableFactory = $dataTableFactory;
117
    }
118
119
    protected function _edit(AbstractNamedDBElement $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
120
    {
121
        $this->denyAccessUnlessGranted('read', $entity);
122
123
        $timeTravel_timestamp = null;
124
        if (null !== $timestamp) {
125
            $this->denyAccessUnlessGranted('@tools.timetravel');
126
            $this->denyAccessUnlessGranted('show_history', $entity);
127
            //If the timestamp only contains numbers interpret it as unix timestamp
128
            if (ctype_digit($timestamp)) {
129
                $timeTravel_timestamp = new \DateTime();
130
                $timeTravel_timestamp->setTimestamp((int) $timestamp);
131
            } else { //Try to parse it via DateTime
132
                $timeTravel_timestamp = new \DateTime($timestamp);
133
            }
134
            $this->timeTravel->revertEntityToTimestamp($entity, $timeTravel_timestamp);
135
        }
136
137
        if ($this->isGranted('show_history', $entity)) {
138
            $table = $this->dataTableFactory->createFromType(
139
                LogDataTable::class,
140
                [
141
                    'filter_elements' => $this->historyHelper->getAssociatedElements($entity),
142
                    'mode' => 'element_history',
143
                ],
144
                ['pageLength' => 10]
145
            )
146
                ->handleRequest($request);
147
148
            if ($table->isCallback()) {
149
                return $table->getResponse();
150
            }
151
        } else {
152
            $table = null;
153
        }
154
155
        $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

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

206
        return $this->render(/** @scrutinizer ignore-type */ $this->twig_template, [
Loading history...
207
            'entity' => $entity,
208
            'form' => $form->createView(),
209
            'attachment_helper' => $this->attachmentHelper,
210
            'route_base' => $this->route_base,
211
            'datatable' => $table,
212
            'timeTravel' => $timeTravel_timestamp,
213
        ]);
214
    }
215
216
    protected function _new(Request $request, EntityManagerInterface $em, EntityImporter $importer)
217
    {
218
        /** @var AbstractStructuralDBElement|User $new_entity */
219
        $new_entity = new $this->entity_class();
220
221
        $this->denyAccessUnlessGranted('read', $new_entity);
222
223
        //Basic edit form
224
        $form = $this->createForm($this->form_class, $new_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

224
        $form = $this->createForm(/** @scrutinizer ignore-type */ $this->form_class, $new_entity, [
Loading history...
225
            'attachment_class' => $this->attachment_class,
226
            'parameter_class' => $this->parameter_class,
227
        ]);
228
229
        $form->handleRequest($request);
230
231
        if ($form->isSubmitted() && $form->isValid()) {
232
            if ($new_entity instanceof User && ! empty($form['new_password']->getData())) {
233
                $password = $this->passwordEncoder->encodePassword($new_entity, $form['new_password']->getData());
234
                $new_entity->setPassword($password);
235
                //By default the user must change the password afterwards
236
                $new_entity->setNeedPwChange(true);
237
            }
238
239
            //Upload passed files
240
            $attachments = $form['attachments'];
241
            foreach ($attachments as $attachment) {
242
                /** @var FormInterface $attachment */
243
                $options = [
244
                    'secure_attachment' => $attachment['secureFile']->getData(),
245
                    'download_url' => $attachment['downloadURL']->getData(),
246
                ];
247
248
                try {
249
                    $this->attachmentSubmitHandler->handleFormSubmit($attachment->getData(), $attachment['file']->getData(), $options);
250
                } catch (AttachmentDownloadException $attachmentDownloadException) {
251
                    $this->addFlash(
252
                        'error',
253
                        $this->translator->trans('attachment.download_failed').' '.$attachmentDownloadException->getMessage()
254
                    );
255
                }
256
            }
257
258
            $this->commentHelper->setMessage($form['log_comment']->getData());
259
260
            $em->persist($new_entity);
261
            $em->flush();
262
            $this->addFlash('success', 'entity.created_flash');
263
264
            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

264
            return $this->redirectToRoute(/** @scrutinizer ignore-type */ $this->route_base.'_edit', ['id' => $new_entity->getID()]);
Loading history...
265
        }
266
267
        if ($form->isSubmitted() && ! $form->isValid()) {
268
            $this->addFlash('error', 'entity.created_flash.invalid');
269
        }
270
271
        //Import form
272
        $import_form = $this->createForm(ImportType::class, ['entity_class' => $this->entity_class]);
273
        $import_form->handleRequest($request);
274
275
        if ($import_form->isSubmitted() && $import_form->isValid()) {
276
            /** @var UploadedFile $file */
277
            $file = $import_form['file']->getData();
278
            $data = $import_form->getData();
279
280
            $options = [
281
                'parent' => $data['parent'],
282
                'preserve_children' => $data['preserve_children'],
283
                'format' => $data['format'],
284
                'csv_separator' => $data['csv_separator'],
285
            ];
286
287
            $this->commentHelper->setMessage('Import '.$file->getClientOriginalName());
288
289
            $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

289
            $errors = $importer->fileToDBEntities($file, /** @scrutinizer ignore-type */ $this->entity_class, $options);
Loading history...
290
291
            foreach ($errors as $name => $error) {
292
                /** @var ConstraintViolationList $error */
293
                $this->addFlash('error', $name.':'.$error);
294
            }
295
        }
296
297
        //Mass creation form
298
        $mass_creation_form = $this->createForm(MassCreationForm::class, ['entity_class' => $this->entity_class]);
299
        $mass_creation_form->handleRequest($request);
300
301
        if ($mass_creation_form->isSubmitted() && $mass_creation_form->isValid()) {
302
            $data = $mass_creation_form->getData();
303
304
            //Create entries based on input
305
            $errors = [];
306
            $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

306
            $results = $importer->massCreation($data['lines'], /** @scrutinizer ignore-type */ $this->entity_class, $data['parent'], $errors);
Loading history...
307
308
            //Show errors to user:
309
            foreach ($errors as $error) {
310
                $this->addFlash('error', $error['entity']->getFullPath().':'.$error['violations']);
311
            }
312
313
            //Persist valid entities to DB
314
            foreach ($results as $result) {
315
                $em->persist($result);
316
            }
317
            $em->flush();
318
        }
319
320
        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

320
        return $this->render(/** @scrutinizer ignore-type */ $this->twig_template, [
Loading history...
321
            'entity' => $new_entity,
322
            'form' => $form->createView(),
323
            'import_form' => $import_form->createView(),
324
            'mass_creation_form' => $mass_creation_form->createView(),
325
            'attachment_helper' => $this->attachmentHelper,
326
            'route_base' => $this->route_base,
327
        ]);
328
    }
329
330
    protected function _delete(Request $request, AbstractNamedDBElement $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
331
    {
332
        $this->denyAccessUnlessGranted('delete', $entity);
333
334
        if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
335
            $entityManager = $this->getDoctrine()->getManager();
336
337
            //Check if we need to remove recursively
338
            if ($entity instanceof AbstractStructuralDBElement && $request->get('delete_recursive', false)) {
339
                $recursionHelper->delete($entity, false);
340
            } else {
341
                if ($entity instanceof AbstractStructuralDBElement) {
342
                    $parent = $entity->getParent();
343
344
                    //Move all sub entities to the current parent
345
                    foreach ($entity->getSubelements() as $subelement) {
346
                        $subelement->setParent($parent);
347
                        $entityManager->persist($subelement);
348
                    }
349
                }
350
351
                //Remove current element
352
                $entityManager->remove($entity);
353
            }
354
355
            $this->commentHelper->setMessage($request->request->get('log_comment', null));
356
357
            //Flush changes
358
            $entityManager->flush();
359
360
            $this->addFlash('success', 'attachment_type.deleted');
361
        } else {
362
            $this->addFlash('error', 'csfr_invalid');
363
        }
364
365
        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

365
        return $this->redirectToRoute(/** @scrutinizer ignore-type */ $this->route_base.'_new');
Loading history...
366
    }
367
368
    protected function _exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
369
    {
370
        $entity = new $this->entity_class();
371
372
        $this->denyAccessUnlessGranted('read', $entity);
373
374
        $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

374
        $entities = $em->getRepository(/** @scrutinizer ignore-type */ $this->entity_class)->findAll();
Loading history...
375
376
        return $exporter->exportEntityFromRequest($entities, $request);
377
    }
378
379
    protected function _exportEntity(AbstractNamedDBElement $entity, EntityExporter $exporter, Request $request): \Symfony\Component\HttpFoundation\Response
380
    {
381
        $this->denyAccessUnlessGranted('read', $entity);
382
383
        return $exporter->exportEntityFromRequest($entity, $request);
384
    }
385
}
386