Passed
Push — master ( 9df72b...a7b6af )
by Marcel
02:33
created

ReportService   F

Complexity

Total Complexity 84

Size/Duplication

Total Lines 531
Duplicated Lines 0 %

Importance

Changes 8
Bugs 1 Features 0
Metric Value
eloc 233
c 8
b 1
f 0
dl 0
loc 531
rs 2
wmc 84

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 31 1
C index() 0 50 12
A read() 0 16 5
A isOwn() 0 7 2
A floatvalue() 0 9 2
A update() 0 9 2
A createFromDataFile() 0 16 2
A reportsForDataset() 0 2 1
B create() 0 23 7
A delete() 0 14 3
A search() 0 3 1
A updateOptions() 0 3 1
A getOwnFavoriteReports() 0 15 4
A setFavorite() 0 8 2
A deleteByUser() 0 10 2
A export() 0 16 2
F import() 0 64 29
A updateGroup() 0 3 1
A updateRefresh() 0 3 1
A favoriteMigration() 0 11 3
A createCopy() 0 19 1

How to fix   Complexity   

Complex Class

Complex classes like ReportService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ReportService, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Analytics
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the LICENSE.md file.
7
 *
8
 * @author Marcel Scherello <[email protected]>
9
 * @copyright 2019-2022 Marcel Scherello
10
 */
11
12
namespace OCA\Analytics\Service;
13
14
use OCA\Analytics\Activity\ActivityManager;
15
use OCA\Analytics\Controller\DatasourceController;
16
use OCA\Analytics\Db\DataloadMapper;
17
use OCA\Analytics\Db\ReportMapper;
18
use OCA\Analytics\Db\StorageMapper;
19
use OCA\Analytics\Db\ThresholdMapper;
20
use OCP\AppFramework\Http\DataDownloadResponse;
21
use OCP\DB\Exception;
22
use OCP\Files\IRootFolder;
23
use OCP\ITagManager;
24
use OCP\IConfig;
25
use OCP\PreConditionNotMetException;
26
use Psr\Log\LoggerInterface;
27
use OCP\IL10N;
28
29
class ReportService
30
{
31
    /** @var IConfig */
32
    protected $config;
33
    private $userId;
34
    private $logger;
35
    private $tagManager;
36
    private $ShareService;
37
    private $DatasetService;
38
    private $StorageMapper;
39
    private $ReportMapper;
40
    private $ThresholdMapper;
41
    private $DataloadMapper;
42
    private $ActivityManager;
43
    private $rootFolder;
44
    private $VariableService;
45
    private $l10n;
46
47
    public function __construct(
48
        $userId,
49
        IL10N $l10n,
50
        LoggerInterface $logger,
51
        ITagManager $tagManager,
52
        ShareService $ShareService,
53
        DatasetService $DatasetService,
54
        StorageMapper $StorageMapper,
55
        ReportMapper $ReportMapper,
56
        ThresholdMapper $ThresholdMapper,
57
        DataloadMapper $DataloadMapper,
58
        ActivityManager $ActivityManager,
59
        IRootFolder $rootFolder,
60
        IConfig $config,
61
        VariableService $VariableService
62
    )
63
    {
64
        $this->userId = $userId;
65
        $this->logger = $logger;
66
        $this->tagManager = $tagManager;
67
        $this->ShareService = $ShareService;
68
        $this->DatasetService = $DatasetService;
69
        $this->ThresholdMapper = $ThresholdMapper;
70
        $this->StorageMapper = $StorageMapper;
71
        $this->ReportMapper = $ReportMapper;
72
        $this->DataloadMapper = $DataloadMapper;
73
        $this->ActivityManager = $ActivityManager;
74
        $this->rootFolder = $rootFolder;
75
        $this->VariableService = $VariableService;
76
        $this->config = $config;
77
        $this->l10n = $l10n;
78
    }
79
80
    /**
81
     * get all reports
82
     *
83
     * @return array
84
     * @throws PreConditionNotMetException
85
     */
86
    public function index(): array
87
    {
88
        $ownReports = $this->ReportMapper->index();
89
90
        // get shared reports and remove duplicates
91
        $sharedReports = $this->ShareService->getSharedReports();
92
        foreach ($sharedReports as $sharedReport) {
93
            $this->logger->info('reportservice: '. $sharedReport['name']);
94
95
            if (!array_search($sharedReport['id'], array_column($ownReports, 'id'))) {
96
                $sharedReport['type'] = '99';
97
                $sharedReport['parrent'] = '0';
98
                array_push($ownReports, $sharedReport);
99
            }
100
        }
101
        if (count($ownReports) === 0) return $ownReports;
102
103
        // get data load indicators for icons shown in the advanced screen
104
        $dataloads = $this->DataloadMapper->getAllDataloadMetadata();
105
        foreach ($dataloads as $dataload) {
106
            $key = array_search($dataload['dataset'], array_column($ownReports, 'dataset'));
107
            if ($key !== '') {
108
                if ($dataload['schedules'] !== '' and $dataload['schedules'] !== null) {
109
                    $dataload['schedules'] = 1;
110
                } else {
111
                    $dataload['schedules'] = 0;
112
                }
113
                $ownReports[$key]['dataloads'] = $dataload['dataloads'];
114
                $ownReports[$key]['schedules'] = $dataload['schedules'];
115
            }
116
        }
117
118
        $favoriteMigration = $this->config->getUserValue($this->userId, 'analytics', 'favMig', '0');
119
        if ($favoriteMigration === '0') {
120
            $this->logger->info('Favorite migration being performed');
121
            $this->favoriteMigration($ownReports);
122
            $this->config->setUserValue($this->userId, 'analytics', 'favMig', 3.7);
123
        }
124
125
        $favorites = $this->tagManager->load('analytics')->getFavorites();
126
        foreach ($ownReports as &$ownReport) {
127
            $hasTag = 0;
128
            if (is_array($favorites) and in_array($ownReport['id'], $favorites)) {
129
                $hasTag = 1;
130
            }
131
            $ownReport['favorite'] = $hasTag;
132
            $ownReport = $this->VariableService->replaceTextVariables($ownReport);
133
        }
134
135
        return $ownReports;
136
    }
137
138
    /**
139
     * get own report details
140
     *
141
     * @param int $reportId
142
     * @return array
143
     * @throws Exception
144
     */
145
    public function read(int $reportId, $replace = true)
146
    {
147
        $ownReport = $this->ReportMapper->readOwn($reportId);
148
        if (!empty($ownReport)) {
149
            $ownReport['permissions'] = \OCP\Constants::PERMISSION_UPDATE;
150
            if ($replace) $ownReport = $this->VariableService->replaceTextVariables($ownReport);
151
152
            if ($ownReport['type'] === DatasourceController::DATASET_TYPE_INTERNAL_DB && $ownReport['dataset'] !== 0) {
153
                $dataset = $this->DatasetService->readOwn($ownReport['dataset']);
154
                $ownReport['dimension1'] = $dataset['dimension1'];
155
                $ownReport['dimension2'] = $dataset['dimension2'];
156
                $ownReport['value'] = $dataset['value'];
157
            }
158
159
        }
160
        return $ownReport;
161
    }
162
163
    /**
164
     * check if own report
165
     *
166
     * @param int $reportId
167
     * @return bool
168
     */
169
    public function isOwn(int $reportId)
170
    {
171
        $ownReport = $this->ReportMapper->readOwn($reportId);
172
        if (!empty($ownReport)) {
173
            return true;
174
        } else {
175
            return false;
176
        }
177
    }
178
179
    /**
180
     * create new blank report
181
     *
182
     * @return int
183
     */
184
    public function create($name, $subheader, $parent, $type, int $dataset, $link, $visualization, $chart, $dimension1, $dimension2, $value, $addReport = null): int
185
    {
186
        $array = json_decode($link, true);
187
        if (is_array($array)){
188
            foreach ($array as $key => $value) {
0 ignored issues
show
introduced by
$value is overwriting one of the parameters of this function.
Loading history...
189
                $array[$key] = htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8');
190
            }
191
        }
192
        $link = json_encode($array);
193
194
        if ($type === DatasourceController::DATASET_TYPE_GROUP) {
195
            $parent = 0;
196
        }
197
        if ($type === DatasourceController::DATASET_TYPE_INTERNAL_DB && $dataset === 0) { // New dataset
198
            $dataset = $this->DatasetService->create($name, $dimension1, $dimension2, $value);
199
        }
200
        $reportId = $this->ReportMapper->create($name, $subheader, $parent, $type, $dataset, $link, $visualization, $chart, $dimension1, $dimension2, $value);
201
        $this->ActivityManager->triggerEvent($reportId, ActivityManager::OBJECT_REPORT, ActivityManager::SUBJECT_REPORT_ADD);
202
203
        if ($addReport !== null) {
204
            $this->updateGroup($addReport, $reportId);
205
        }
206
        return $reportId;
207
    }
208
209
    /**
210
     * copy an existing report with the current navigation status
211
     *
212
     * @NoAdminRequired
213
     * @param int $reportId
214
     * @param $chartoptions
215
     * @param $dataoptions
216
     * @param $filteroptions
217
     * @return int
218
     * @throws Exception
219
     */
220
    public function createCopy(int $reportId, $chartoptions, $dataoptions, $filteroptions)
221
    {
222
223
        $template = $this->ReportMapper->readOwn($reportId);
224
        $newId = $this->ReportMapper->create(
225
        // TRANSLATORS Noun
226
            $template['name'] . ' ' . $this->l10n->t('copy'),
227
            $template['subheader'],
228
            $template['parent'],
229
            $template['type'],
230
            $template['dataset'],
231
            $template['link'],
232
            $template['visualization'],
233
            $template['chart'],
234
            $template['dimension1'],
235
            $template['dimension2'],
236
            $template['value']);
237
        $this->ReportMapper->updateOptions($newId, $chartoptions, $dataoptions, $filteroptions);
238
        return $newId;
239
    }
240
241
    /**
242
     * create new report
243
     *
244
     * @param string $file
245
     * @return int
246
     */
247
    public function createFromDataFile($file = '')
248
    {
249
        $this->ActivityManager->triggerEvent(0, ActivityManager::OBJECT_REPORT, ActivityManager::SUBJECT_REPORT_ADD);
250
251
        if ($file !== '') {
252
            $name = explode('.', end(explode('/', $file)))[0];
253
            $subheader = $file;
254
            $parent = 0;
255
            $dataset = 0;
256
            $type = DatasourceController::DATASET_TYPE_FILE;
257
            $link = $file;
258
            $visualization = 'table';
259
            $chart = 'line';
260
            $reportId = $this->ReportMapper->create($name, $subheader, $parent, $type, $dataset, $link, $visualization, $chart, '', '', '');
261
        }
262
        return $reportId;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $reportId does not seem to be defined for all execution paths leading up to this point.
Loading history...
263
    }
264
265
    /**
266
     * update report details
267
     *
268
     * @param int $reportId
269
     * @param $name
270
     * @param $subheader
271
     * @param int $parent
272
     * @param $link
273
     * @param $visualization
274
     * @param $chart
275
     * @param $chartoptions
276
     * @param $dataoptions
277
     * @param $dimension1
278
     * @param $dimension2
279
     * @param $value
280
     * @return bool
281
     */
282
    public function update(int $reportId, $name, $subheader, int $parent, $link, $visualization, $chart, $chartoptions, $dataoptions, $dimension1 = null, $dimension2 = null, $value = null)
283
    {
284
        $array = json_decode($link, true);
285
        foreach ($array as $key => $value) {
286
            $array[$key] = htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8');
287
        }
288
        $link = json_encode($array);
289
290
        return $this->ReportMapper->update($reportId, $name, $subheader, $parent, $link, $visualization, $chart, $chartoptions, $dataoptions, $dimension1, $dimension2, $value);
291
    }
292
293
    /**
294
     * Delete Dataset and all depending objects
295
     *
296
     * @param int $reportId
297
     * @return string
298
     * @throws Exception
299
     */
300
    public function delete(int $reportId)
301
    {
302
        $metadata = $this->ReportMapper->readOwn($reportId);
303
        //$this->ActivityManager->triggerEvent($reportId, ActivityManager::OBJECT_REPORT, ActivityManager::SUBJECT_REPORT_DELETE);
304
        $this->ShareService->deleteShareByReport($reportId);
305
        $this->ThresholdMapper->deleteThresholdByReport($reportId);
306
        $this->setFavorite($reportId, 'false');
307
        $this->ReportMapper->delete($reportId);
308
309
        $report = $this->reportsForDataset((int)$metadata['dataset']);
310
        if (empty($report) && (int)$metadata['type'] === DatasourceController::DATASET_TYPE_INTERNAL_DB) {
311
            return $metadata['dataset'];
312
        } else {
313
            return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type string.
Loading history...
314
        }
315
    }
316
317
    /**
318
     * get dataset by user
319
     *
320
     * @param string $userId
321
     * @return array|bool
322
     * @throws Exception
323
     */
324
    public function deleteByUser(string $userId)
325
    {
326
        $reports = $this->ReportMapper->indexByUser($userId);
327
        foreach ($reports as $report) {
328
            $this->ShareService->deleteShareByReport($report['id']);
329
            $this->ThresholdMapper->deleteThresholdByReport($report['id']);
330
            $this->setFavorite($report['id'], 'false');
331
            $this->ReportMapper->delete($report['id']);
332
        }
333
        return true;
334
    }
335
336
    /**
337
     * get own reports which are marked as favorites
338
     *
339
     * @return array|bool
340
     */
341
    public function getOwnFavoriteReports()
342
    {
343
        $ownReports = $this->ReportMapper->index();
344
        $favorites = $this->tagManager->load('analytics')->getFavorites();
345
        $sharedReports = $this->ShareService->getSharedReports();
346
347
        foreach ($favorites as $favorite) {
348
            if (array_search($favorite, array_column($ownReports, 'id')) === false
349
                && array_search($favorite, array_column($sharedReports, 'id')) === false) {
350
                unset($favorites[$favorite]);
351
                $this->tagManager->load('analytics')->removeFromFavorites($favorite);
352
            }
353
        }
354
355
        return $favorites;
356
    }
357
358
    /**
359
     * set/remove the favorite flag for a report
360
     *
361
     * @param int $reportId
362
     * @param string $favorite
363
     * @return bool
364
     */
365
    public function setFavorite(int $reportId, string $favorite)
366
    {
367
        if ($favorite === 'true') {
368
            $return = $this->tagManager->load('analytics')->addToFavorites($reportId);
369
        } else {
370
            $return = $this->tagManager->load('analytics')->removeFromFavorites($reportId);
371
        }
372
        return $return;
373
    }
374
375
    /**
376
     * Import Report from File
377
     *
378
     * @param string|null $path
379
     * @param string|null $raw
380
     * @return int
381
     * @throws \OCP\Files\NotFoundException
382
     * @throws \OCP\Files\NotPermittedException
383
     */
384
    public function import(string $path = null, string $raw = null)
385
    {
386
        if ($path !== '') {
387
            $file = $this->rootFolder->getUserFolder($this->userId)->get($path);
388
            $data = $file->getContent();
0 ignored issues
show
Bug introduced by
The method getContent() does not exist on OCP\Files\Node. It seems like you code against a sub-type of OCP\Files\Node such as OCP\Files\File. ( Ignorable by Annotation )

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

388
            /** @scrutinizer ignore-call */ 
389
            $data = $file->getContent();
Loading history...
389
        } else if ($raw !== null) {
390
            $data = $raw;
391
        } else {
392
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
393
        }
394
        $data = json_decode($data, true);
395
396
        $report = $data['report'];
397
        isset($report['name']) ? $name = $report['name'] : $name = '';
398
        isset($report['subheader']) ? $subheader = $report['subheader'] : $subheader = '';
399
        $parent = 0;
400
        $dataset = 0;
401
        isset($report['type']) ? $type = $report['type'] : $type = null;
402
        isset($report['link']) ? $link = $report['link'] : $link = null;
403
        isset($report['visualization']) ? $visualization = $report['visualization'] : $visualization = null;
404
        isset($report['chart']) ? $chart = $report['chart'] : $chart = null;
405
        isset($report['chartoptions']) ? $chartoptions = $report['chartoptions'] : $chartoptions = null;
406
        isset($report['dataoptions']) ? $dataoptions = $report['dataoptions'] : $dataoptions = null;
407
        isset($report['filteroptions']) ? $filteroptions = $report['filteroptions'] : $filteroptions = null;
408
        isset($report['dimension1']) ? $dimension1 = $report['dimension1'] : $dimension1 = null;
409
        isset($report['dimension2']) ? $dimension2 = $report['dimension2'] : $dimension2 = null;
410
        isset($report['value']) ? $value = $report['value'] : $value = null;
411
412
        $reportId = $this->create($name, $subheader, $parent, $type, $dataset, $link, $visualization, $chart, $dimension1, $dimension2, $value);
413
        $this->updateOptions($reportId, $chartoptions, $dataoptions, $filteroptions);
414
        $report = $this->ReportMapper->readOwn($reportId);
415
        $datasetId = $report['dataset'];
416
417
        foreach ($data['dataload'] as $dataload) {
418
            isset($dataload['datasource']) ? $datasource = $dataload['datasource'] : $datasource = null;
419
            isset($dataload['name']) ? $name = $dataload['name'] : $name = null;
420
            isset($dataload['option']) ? $option = $dataload['option'] : $option = null;
421
            $schedule = null;
422
423
            $dataloadId = $this->DataloadMapper->create($datasetId, $datasource);
424
            $this->DataloadMapper->update($dataloadId, $name, $option, $schedule);
425
        }
426
427
        foreach ($data['threshold'] as $threshold) {
428
            isset($threshold['dimension1']) ? $dimension1 = $threshold['dimension1'] : $dimension1 = null;
429
            isset($threshold['value']) ? $value = $threshold['value'] : $value = null;
430
            isset($threshold['option']) ? $option = $threshold['option'] : $option = null;
431
            isset($threshold['severity']) ? $severity = $threshold['severity'] : $severity = null;
432
            $value = $this->floatvalue($value);
433
            $this->ThresholdMapper->create($reportId, $dimension1, $value, $option, $severity);
434
        }
435
436
        foreach ($data['data'] as $dData) {
437
            isset($dData[0]) ? $dimension1 = $dData[0] : $dimension1 = null;
438
            isset($dData[1]) ? $dimension2 = $dData[1] : $dimension2 = null;
439
            isset($dData[2]) ? $value = $dData[2] : $value = null;
440
            $this->StorageMapper->create($datasetId, $dimension1, $dimension2, $value);
441
        }
442
443
        if (isset($data['favorite'])) {
444
            $this->setFavorite($reportId, $data['favorite']);
445
        }
446
447
        return $reportId;
448
    }
449
450
    /**
451
     * Export Report
452
     *
453
     * @param int $reportId
454
     * @return DataDownloadResponse
455
     */
456
    public function export(int $reportId)
457
    {
458
        $result = array();
459
        $result['report'] = $this->ReportMapper->readOwn($reportId);
460
        $datasetId = $result['report']['dataset'];
461
        $result['dataload'] = $this->DataloadMapper->read($datasetId);
462
        $result['threshold'] = $this->ThresholdMapper->getThresholdsByReport($reportId);
463
        $result['favorite'] = '';
464
465
        if ($result['report']['type'] === DatasourceController::DATASET_TYPE_INTERNAL_DB) {
466
            $result['data'] = $this->StorageMapper->read($datasetId);
467
        }
468
469
        unset($result['report']['id'], $result['report']['user_id'], $result['report']['user_id'], $result['report']['parent'], $result['report']['dataset']);
470
        $data = json_encode($result);
471
        return new DataDownloadResponse($data, $result['report']['name'] . '.export.txt', 'text/plain; charset=utf-8');
472
    }
473
474
    /**
475
     * Update report options
476
     *
477
     * @param int $reportId
478
     * @param $chartoptions
479
     * @param $dataoptions
480
     * @param $filteroptions
481
     * @return bool
482
     */
483
    public function updateOptions(int $reportId, $chartoptions, $dataoptions, $filteroptions)
484
    {
485
        return $this->ReportMapper->updateOptions($reportId, $chartoptions, $dataoptions, $filteroptions);
486
    }
487
488
    /**
489
     * get report refresh options
490
     *
491
     * @NoAdminRequired
492
     * @param int $reportId
493
     * @param $refresh
494
     * @return bool
495
     */
496
    public function updateRefresh(int $reportId, $refresh)
497
    {
498
        return $this->ReportMapper->updateRefresh($reportId, $refresh);
499
    }
500
501
    /**
502
     * update report group assignment (from drag & drop)
503
     *
504
     * @NoAdminRequired
505
     * @param int $reportId
506
     * @param $groupId
507
     * @return bool
508
     */
509
    public function updateGroup(int $reportId, $groupId)
510
    {
511
        return $this->ReportMapper->updateGroup($reportId, $groupId);
512
    }
513
514
    /**
515
     * search for reports
516
     *
517
     * @param string $searchString
518
     * @return array
519
     */
520
    public function search(string $searchString)
521
    {
522
        return $this->ReportMapper->search($searchString);
523
    }
524
525
    /**
526
     * @throws Exception
527
     */
528
    public function reportsForDataset($datasetId) {
529
        return $this->ReportMapper->reportsForDataset($datasetId);
530
    }
531
532
    /**
533
     * migrate old favorite ids
534
     *
535
     * @param $ownReports
536
     * @return bool
537
     */
538
    private function favoriteMigration($ownReports) {
539
        $favorites = $this->tagManager->load('analytics')->getFavorites();
540
        foreach ($favorites as $favorite) {
541
            $key = array_search($favorite, array_column($ownReports, 'dataset'));
542
            if ($key) {
543
                $this->logger->info('Favorite was migrated from '. $ownReports[$key]['dataset'] . ' to new report ' . $ownReports[$key]['id']);
544
                $this->tagManager->load('analytics')->removeFromFavorites($ownReports[$key]['dataset']);
545
                $this->tagManager->load('analytics')->addToFavorites($ownReports[$key]['id']);
546
            }
547
        }
548
        return true;
549
    }
550
551
    private function floatvalue($val)
552
    {
553
        $val = str_replace(",", ".", $val);
554
        $val = preg_replace('/\.(?=.*\.)/', '', $val);
555
        $val = preg_replace('/[^0-9-.]+/', '', $val);
556
        if (is_numeric($val)) {
557
            return number_format(floatval($val), 2, '.', '');
558
        } else {
559
            return false;
560
        }
561
    }
562
563
}
564