LogDataTable::configure()   F
last analyzed

Complexity

Conditions 23
Paths 1

Size

Total Lines 202
Code Lines 132

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 23
eloc 132
c 3
b 0
f 0
nc 1
nop 2
dl 0
loc 202
rs 3.3333

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 - 2022 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
namespace App\DataTables;
24
25
use App\DataTables\Column\IconLinkColumn;
26
use App\DataTables\Column\LocaleDateTimeColumn;
27
use App\DataTables\Column\LogEntryExtraColumn;
28
use App\DataTables\Column\LogEntryTargetColumn;
29
use App\DataTables\Column\RevertLogColumn;
30
use App\DataTables\Column\RowClassColumn;
31
use App\DataTables\Filters\AttachmentFilter;
32
use App\DataTables\Filters\LogFilter;
33
use App\Entity\Base\AbstractDBElement;
34
use App\Entity\Contracts\TimeTravelInterface;
35
use App\Entity\LogSystem\AbstractLogEntry;
36
use App\Entity\LogSystem\CollectionElementDeleted;
37
use App\Entity\LogSystem\ElementCreatedLogEntry;
38
use App\Entity\LogSystem\ElementDeletedLogEntry;
39
use App\Entity\LogSystem\ElementEditedLogEntry;
40
use App\Entity\LogSystem\PartStockChangedLogEntry;
41
use App\Entity\UserSystem\Group;
42
use App\Entity\UserSystem\User;
43
use App\Exceptions\EntityNotSupportedException;
44
use App\Repository\LogEntryRepository;
45
use App\Services\ElementTypeNameGenerator;
46
use App\Services\EntityURLGenerator;
47
use App\Services\UserSystem\UserAvatarHelper;
48
use Doctrine\ORM\EntityManagerInterface;
49
use Doctrine\ORM\QueryBuilder;
50
use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider;
51
use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter;
52
use Omines\DataTablesBundle\Column\TextColumn;
53
use Omines\DataTablesBundle\DataTable;
54
use Omines\DataTablesBundle\DataTableTypeInterface;
55
use Psr\Log\LogLevel;
56
use Symfony\Component\OptionsResolver\Options;
57
use Symfony\Component\OptionsResolver\OptionsResolver;
58
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
59
use Symfony\Component\Security\Core\Security;
60
use Symfony\Contracts\Translation\TranslatorInterface;
61
62
use function Symfony\Component\Translation\t;
63
64
class LogDataTable implements DataTableTypeInterface
65
{
66
    protected ElementTypeNameGenerator $elementTypeNameGenerator;
67
    protected TranslatorInterface $translator;
68
    protected UrlGeneratorInterface $urlGenerator;
69
    protected EntityURLGenerator $entityURLGenerator;
70
    protected LogEntryRepository $logRepo;
71
    protected Security $security;
72
    protected UserAvatarHelper $userAvatarHelper;
73
74
    public function __construct(ElementTypeNameGenerator $elementTypeNameGenerator, TranslatorInterface $translator,
75
        UrlGeneratorInterface $urlGenerator, EntityURLGenerator $entityURLGenerator, EntityManagerInterface $entityManager,
76
        Security $security, UserAvatarHelper $userAvatarHelper)
77
    {
78
        $this->elementTypeNameGenerator = $elementTypeNameGenerator;
79
        $this->translator = $translator;
80
        $this->urlGenerator = $urlGenerator;
81
        $this->entityURLGenerator = $entityURLGenerator;
82
        $this->logRepo = $entityManager->getRepository(AbstractLogEntry::class);
83
        $this->security = $security;
84
        $this->userAvatarHelper = $userAvatarHelper;
85
    }
86
87
    public function configureOptions(OptionsResolver $optionsResolver): void
88
    {
89
        $optionsResolver->setDefaults([
90
            'mode' => 'system_log',
91
            'filter_elements' => [],
92
            'filter' => null,
93
        ]);
94
95
        $optionsResolver->setAllowedTypes('filter_elements', ['array', 'object']);
96
        $optionsResolver->setAllowedTypes('mode', 'string');
97
        $optionsResolver->setAllowedTypes('filter', ['null', LogFilter::class]);
98
99
        $optionsResolver->setNormalizer('filter_elements', static function (Options $options, $value) {
100
            if (!is_array($value)) {
101
                return [$value];
102
            }
103
104
            return $value;
105
        });
106
107
        $optionsResolver->setAllowedValues('mode', ['system_log', 'element_history', 'last_activity']);
108
    }
109
110
    public function configure(DataTable $dataTable, array $options): void
111
    {
112
        $resolver = new OptionsResolver();
113
        $this->configureOptions($resolver);
114
        $options = $resolver->resolve($options);
115
116
        //This special $$rowClass column is used to set the row class depending on the log level. The class gets set by the frontend controller
117
        $dataTable->add('dont_matter', RowClassColumn::class, [
118
            'render' => static function ($value, AbstractLogEntry $context) {
119
                switch ($context->getLevel()) {
120
                    case AbstractLogEntry::LEVEL_EMERGENCY:
121
                    case AbstractLogEntry::LEVEL_ALERT:
122
                    case AbstractLogEntry::LEVEL_CRITICAL:
123
                    case AbstractLogEntry::LEVEL_ERROR:
124
                        return 'table-danger';
125
                    case AbstractLogEntry::LEVEL_WARNING:
126
                        return 'table-warning';
127
                    case AbstractLogEntry::LEVEL_NOTICE:
128
                        return 'table-info';
129
                    default:
130
                        return '';
131
                }
132
            },
133
        ]);
134
135
        $dataTable->add('symbol', TextColumn::class, [
136
            'label' => '',
137
            'className' => 'no-colvis',
138
            'render' => static function ($value, AbstractLogEntry $context) {
139
                switch ($context->getLevelString()) {
140
                    case LogLevel::DEBUG:
141
                        $symbol = 'fa-bug';
142
143
                        break;
144
                    case LogLevel::INFO:
145
                        $symbol = 'fa-info';
146
147
                        break;
148
                    case LogLevel::NOTICE:
149
                        $symbol = 'fa-flag';
150
151
                        break;
152
                    case LogLevel::WARNING:
153
                        $symbol = 'fa-exclamation-circle';
154
155
                        break;
156
                    case LogLevel::ERROR:
157
                        $symbol = 'fa-exclamation-triangle';
158
159
                        break;
160
                    case LogLevel::CRITICAL:
161
                        $symbol = 'fa-bolt';
162
163
                        break;
164
                    case LogLevel::ALERT:
165
                        $symbol = 'fa-radiation';
166
167
                        break;
168
                    case LogLevel::EMERGENCY:
169
                        $symbol = 'fa-skull-crossbones';
170
171
                        break;
172
                    default:
173
                        $symbol = 'fa-question-circle';
174
175
                        break;
176
                }
177
178
                return sprintf(
179
                    '<i class="fas fa-fw %s" title="%s"></i>',
180
                    $symbol,
181
                    $context->getLevelString()
182
                );
183
            },
184
        ]);
185
186
        $dataTable->add('id', TextColumn::class, [
187
            'label' => 'log.id',
188
            'visible' => false,
189
        ]);
190
191
        $dataTable->add('timestamp', LocaleDateTimeColumn::class, [
192
            'label' => 'log.timestamp',
193
            'timeFormat' => 'medium',
194
        ]);
195
196
        $dataTable->add('type', TextColumn::class, [
197
            'label' => 'log.type',
198
            'propertyPath' => 'type',
199
            'render' => function (string $value, AbstractLogEntry $context) {
200
                $text = $this->translator->trans('log.type.'.$value);
201
202
                if ($context instanceof PartStockChangedLogEntry) {
203
                    $text .= sprintf(
204
                        ' (<i>%s</i>)',
205
                        $this->translator->trans('log.part_stock_changed.' . $context->getInstockChangeType())
206
                    );
207
                }
208
209
                return $text;
210
            },
211
        ]);
212
213
        $dataTable->add('level', TextColumn::class, [
214
            'label' => 'log.level',
215
            'visible' => 'system_log' === $options['mode'],
216
            'propertyPath' => 'levelString',
217
            'render' => function (string $value, AbstractLogEntry $context) {
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

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

217
            'render' => function (string $value, /** @scrutinizer ignore-unused */ AbstractLogEntry $context) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
218
                return $this->translator->trans('log.level.'.$value);
219
            },
220
        ]);
221
222
        $dataTable->add('user', TextColumn::class, [
223
            'label' => 'log.user',
224
            'render' => function ($value, AbstractLogEntry $context) {
225
                $user = $context->getUser();
226
227
                //If user was deleted, show the info from the username field
228
                if ($user === null) {
229
                    return sprintf(
230
                        '@%s [%s]',
231
                        htmlentities($context->getUsername()),
232
                        $this->translator->trans('log.target_deleted'),
233
                    );
234
                }
235
236
                $img_url = $this->userAvatarHelper->getAvatarSmURL($user);
237
238
                return sprintf(
239
                    '<img src="%s" data-thumbnail="%s" class="avatar-xs" data-controller="elements--hoverpic"> <a href="%s">%s</a>',
240
                    $img_url,
241
                    $this->userAvatarHelper->getAvatarMdURL($user),
242
                    $this->urlGenerator->generate('user_info', ['id' => $user->getID()]),
243
                    htmlentities($user->getFullName(true))
244
                );
245
            },
246
        ]);
247
248
        $dataTable->add('target_type', TextColumn::class, [
249
            'label' => 'log.target_type',
250
            'visible' => false,
251
            'render' => function ($value, AbstractLogEntry $context) {
252
                $class = $context->getTargetClass();
253
                if (null !== $class) {
254
                    return $this->elementTypeNameGenerator->getLocalizedTypeLabel($class);
255
                }
256
257
                return '';
258
            },
259
        ]);
260
261
        $dataTable->add('target', LogEntryTargetColumn::class, [
262
            'label' => 'log.target',
263
            'show_associated' => 'element_history' !== $options['mode'],
264
        ]);
265
266
        $dataTable->add('extra', LogEntryExtraColumn::class, [
267
            'label' => 'log.extra',
268
        ]);
269
270
        $dataTable->add('timeTravel', IconLinkColumn::class, [
271
            'label' => '',
272
            'icon' => 'fas fa-fw fa-eye',
273
            'href' => function ($value, AbstractLogEntry $context) {
274
                if (
275
                    ($context instanceof TimeTravelInterface
276
                        && $context->hasOldDataInformations())
277
                    || $context instanceof CollectionElementDeleted
278
                ) {
279
                    try {
280
                        $target = $this->logRepo->getTargetElement($context);
281
                        if (null !== $target) {
282
                            return $this->entityURLGenerator->timeTravelURL($target, $context->getTimestamp());
283
                        }
284
                    } catch (EntityNotSupportedException $exception) {
285
                        return null;
286
                    }
287
                }
288
289
                return null;
290
            },
291
            'disabled' => function ($value, AbstractLogEntry $context) {
292
                return !$this->security->isGranted('show_history', $context->getTargetClass());
293
            },
294
        ]);
295
296
        $dataTable->add('actionRevert', RevertLogColumn::class, [
297
            'label' => '',
298
        ]);
299
300
        $dataTable->addOrderBy('timestamp', DataTable::SORT_DESCENDING);
301
302
        $dataTable->createAdapter(ORMAdapter::class, [
303
            'entity' => AbstractLogEntry::class,
304
            'query' => function (QueryBuilder $builder) use ($options): void {
305
                $this->getQuery($builder, $options);
306
            },
307
            'criteria' => [
308
                function (QueryBuilder $builder) use ($options): void {
309
                    $this->buildCriteria($builder, $options);
310
                },
311
                new SearchCriteriaProvider(),
312
            ],
313
        ]);
314
    }
315
316
    private function buildCriteria(QueryBuilder $builder, array $options): void
317
    {
318
        if (!empty($options['filter'])) {
319
            $filter = $options['filter'];
320
            $filter->apply($builder);
321
        }
322
323
    }
324
325
    protected function getQuery(QueryBuilder $builder, array $options): void
326
    {
327
        $builder->select('log')
328
            ->addSelect('user')
329
            ->from(AbstractLogEntry::class, 'log')
330
            ->leftJoin('log.user', 'user');
331
332
        /* Do this here as we don't want to show up the global count of all log entries in the footer line, with these modes  */
333
        if ('last_activity' === $options['mode']) {
334
            $builder->where('log INSTANCE OF '.ElementCreatedLogEntry::class)
335
                ->orWhere('log INSTANCE OF '.ElementDeletedLogEntry::class)
336
                ->orWhere('log INSTANCE OF '.ElementEditedLogEntry::class)
337
                ->orWhere('log INSTANCE OF '.CollectionElementDeleted::class)
338
                ->andWhere('log.target_type NOT IN (:disallowed)');
339
340
            $builder->setParameter('disallowed', [
341
                AbstractLogEntry::targetTypeClassToID(User::class),
342
                AbstractLogEntry::targetTypeClassToID(Group::class),
343
            ]);
344
        }
345
346
        if (!empty($options['filter_elements'])) {
347
            foreach ($options['filter_elements'] as $element) {
348
                /** @var AbstractDBElement $element */
349
350
                $target_type = AbstractLogEntry::targetTypeClassToID(get_class($element));
351
                $target_id = $element->getID();
352
                $builder->orWhere("log.target_type = ${target_type} AND log.target_id = ${target_id}");
353
            }
354
        }
355
    }
356
}
357