Passed
Push — master ( 810bf3...bcc162 )
by Marcel
03:09 queued 11s
created

ReportService::getOwnFavoriteReports()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
c 3
b 0
f 0
nc 3
nop 0
dl 0
loc 15
rs 9.9666
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 2021 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
        if (count($ownReports) === 0) return $ownReports;
90
91
        // get data load indicators for icons shown in the advanced screen
92
        $dataloads = $this->DataloadMapper->getAllDataloadMetadata();
93
        foreach ($dataloads as $dataload) {
94
            $key = array_search($dataload['dataset'], array_column($ownReports, 'dataset'));
95
            if ($key !== '') {
96
                if ($dataload['schedules'] !== '' and $dataload['schedules'] !== null) {
97
                    $dataload['schedules'] = 1;
98
                } else {
99
                    $dataload['schedules'] = 0;
100
                }
101
                $ownReports[$key]['dataloads'] = $dataload['dataloads'];
102
                $ownReports[$key]['schedules'] = $dataload['schedules'];
103
            }
104
        }
105
106
        // get shared reports and remove duplicates
107
        $sharedReports = $this->ShareService->getSharedReports();
108
        foreach ($sharedReports as $sharedReport) {
109
            if (!array_search($sharedReport['id'], array_column($ownReports, 'id'))) {
110
                $sharedReport['type'] = '99';
111
                $sharedReport['parrent'] = '0';
112
                array_push($ownReports, $sharedReport);
113
            }
114
        }
115
116
        $favoriteMigration = $this->config->getUserValue($this->userId, 'analytics', 'favMig', '0');
117
        if ($favoriteMigration === '0') {
118
            $this->logger->info('Favorite migration being performed');
119
            $this->favoriteMigration($ownReports);
120
            $this->config->setUserValue($this->userId, 'analytics', 'favMig', 3.7);
121
        }
122
123
        $favorites = $this->tagManager->load('analytics')->getFavorites();
124
        foreach ($ownReports as &$ownReport) {
125
            $hasTag = 0;
126
            if (is_array($favorites) and in_array($ownReport['id'], $favorites)) {
127
                $hasTag = 1;
128
            }
129
            $ownReport['favorite'] = $hasTag;
130
            $ownReport = $this->VariableService->replaceTextVariables($ownReport);
131
        }
132
133
        return $ownReports;
134
    }
135
136
    /**
137
     * get own report details
138
     *
139
     * @param int $reportId
140
     * @return array
141
     * @throws Exception
142
     */
143
    public function read(int $reportId, $replace = true)
144
    {
145
        $ownReport = $this->ReportMapper->read($reportId);
146
        if (!empty($ownReport)) {
147
            $ownReport['permissions'] = \OCP\Constants::PERMISSION_UPDATE;
148
            if ($replace) $ownReport = $this->VariableService->replaceTextVariables($ownReport);
149
150
            if ($ownReport['type'] === DatasourceController::DATASET_TYPE_INTERNAL_DB && $ownReport['dataset'] !== 0) {
151
                $dataset = $this->DatasetService->readOwn($ownReport['dataset']);
152
                $ownReport['dimension1'] = $dataset['dimension1'];
153
                $ownReport['dimension2'] = $dataset['dimension2'];
154
                $ownReport['value'] = $dataset['value'];
155
            }
156
157
        }
158
        return $ownReport;
159
    }
160
161
    /**
162
     * create new blank report
163
     *
164
     * @return int
165
     */
166
    public function create($name, $subheader, $parent, $type, int $dataset, $link, $visualization, $chart, $dimension1, $dimension2, $value): int
167
    {
168
        $array = json_decode($link, true);
169
        foreach ($array as $key => $value) {
0 ignored issues
show
introduced by
$value is overwriting one of the parameters of this function.
Loading history...
170
            $array[$key] = htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8');
171
        }
172
        $link = json_encode($array);
173
174
        if ($type === DatasourceController::DATASET_TYPE_GROUP) {
175
            $parent = 0;
176
        }
177
        if ($type === DatasourceController::DATASET_TYPE_INTERNAL_DB && $dataset === 0) { // New dataset
178
            $dataset = $this->DatasetService->create($name, $dimension1, $dimension2, $value);
179
        }
180
        $reportId = $this->ReportMapper->create($name, $subheader, $parent, $type, $dataset, $link, $visualization, $chart, $dimension1, $dimension2, $value);
181
        $this->ActivityManager->triggerEvent($reportId, ActivityManager::OBJECT_REPORT, ActivityManager::SUBJECT_REPORT_ADD);
182
        return $reportId;
183
    }
184
185
    /**
186
     * copy an existing report with the current navigation status
187
     *
188
     * @NoAdminRequired
189
     * @param int $reportId
190
     * @param $chartoptions
191
     * @param $dataoptions
192
     * @param $filteroptions
193
     * @return int
194
     * @throws Exception
195
     */
196
    public function createCopy(int $reportId, $chartoptions, $dataoptions, $filteroptions)
197
    {
198
199
        $template = $this->ReportMapper->read($reportId);
200
        $newId = $this->ReportMapper->create(
201
        // TRANSLATORS Noun
202
            $template['name'] . ' ' . $this->l10n->t('copy'),
203
            $template['subheader'],
204
            $template['parent'],
205
            $template['type'],
206
            $template['dataset'],
207
            $template['link'],
208
            $template['visualization'],
209
            $template['chart'],
210
            $template['dimension1'],
211
            $template['dimension2'],
212
            $template['value']);
213
        $this->ReportMapper->updateOptions($newId, $chartoptions, $dataoptions, $filteroptions);
214
        return $newId;
215
    }
216
217
    /**
218
     * create new report
219
     *
220
     * @param string $file
221
     * @return int
222
     */
223
    public function createFromDataFile($file = '')
224
    {
225
        $this->ActivityManager->triggerEvent(0, ActivityManager::OBJECT_REPORT, ActivityManager::SUBJECT_REPORT_ADD);
226
227
        if ($file !== '') {
228
            $name = explode('.', end(explode('/', $file)))[0];
229
            $subheader = $file;
230
            $parent = 0;
231
            $dataset = 0;
232
            $type = DatasourceController::DATASET_TYPE_FILE;
233
            $link = $file;
234
            $visualization = 'table';
235
            $chart = 'line';
236
            $reportId = $this->ReportMapper->create($name, $subheader, $parent, $type, $dataset, $link, $visualization, $chart, '', '', '');
237
        }
238
        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...
239
    }
240
241
    /**
242
     * update report details
243
     *
244
     * @param int $reportId
245
     * @param $name
246
     * @param $subheader
247
     * @param int $parent
248
     * @param $link
249
     * @param $visualization
250
     * @param $chart
251
     * @param $chartoptions
252
     * @param $dataoptions
253
     * @param $dimension1
254
     * @param $dimension2
255
     * @param $value
256
     * @return bool
257
     */
258
    public function update(int $reportId, $name, $subheader, int $parent, $link, $visualization, $chart, $chartoptions, $dataoptions, $dimension1 = null, $dimension2 = null, $value = null)
259
    {
260
        $array = json_decode($link, true);
261
        foreach ($array as $key => $value) {
262
            $array[$key] = htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8');
263
        }
264
        $link = json_encode($array);
265
266
        return $this->ReportMapper->update($reportId, $name, $subheader, $parent, $link, $visualization, $chart, $chartoptions, $dataoptions, $dimension1, $dimension2, $value);
267
    }
268
269
    /**
270
     * Delete Dataset and all depending objects
271
     *
272
     * @param int $reportId
273
     * @return string
274
     * @throws Exception
275
     */
276
    public function delete(int $reportId)
277
    {
278
        $metadata = $this->read($reportId);
279
        $this->ActivityManager->triggerEvent($reportId, ActivityManager::OBJECT_REPORT, ActivityManager::SUBJECT_REPORT_DELETE);
280
        $this->ShareService->deleteShareByReport($reportId);
281
        $this->ThresholdMapper->deleteThresholdByReport($reportId);
282
        $this->setFavorite($reportId, 'false');
283
        $this->ReportMapper->delete($reportId);
284
285
        $report = $this->reportsForDataset((int)$metadata['dataset']);
286
        if (empty($report) && (int)$metadata['type'] === DatasourceController::DATASET_TYPE_INTERNAL_DB) {
287
            return $metadata['dataset'];
288
        } else {
289
            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...
290
        }
291
    }
292
293
    /**
294
     * get own reports which are marked as favorites
295
     *
296
     * @return array|bool
297
     */
298
    public function getOwnFavoriteReports()
299
    {
300
        $ownReports = $this->ReportMapper->index();
301
        $favorites = $this->tagManager->load('analytics')->getFavorites();
302
        $sharedReports = $this->ShareService->getSharedReports();
303
304
        foreach ($favorites as $favorite) {
305
            if (array_search($favorite, array_column($ownReports, 'id')) === false
306
                && array_search($favorite, array_column($sharedReports, 'id')) === false) {
307
                unset($favorites[$favorite]);
308
                $this->tagManager->load('analytics')->removeFromFavorites($favorite);
309
            }
310
        }
311
312
        return $favorites;
313
    }
314
315
    /**
316
     * set/remove the favorite flag for a report
317
     *
318
     * @param int $reportId
319
     * @param string $favorite
320
     * @return bool
321
     */
322
    public function setFavorite(int $reportId, string $favorite)
323
    {
324
        if ($favorite === 'true') {
325
            $return = $this->tagManager->load('analytics')->addToFavorites($reportId);
326
        } else {
327
            $return = $this->tagManager->load('analytics')->removeFromFavorites($reportId);
328
        }
329
        return $return;
330
    }
331
332
    /**
333
     * Import Report from File
334
     *
335
     * @param string|null $path
336
     * @param string|null $raw
337
     * @return int
338
     * @throws \OCP\Files\NotFoundException
339
     * @throws \OCP\Files\NotPermittedException
340
     */
341
    public function import(string $path = null, string $raw = null)
342
    {
343
        if ($path !== '') {
344
            $file = $this->rootFolder->getUserFolder($this->userId)->get($path);
345
            $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

345
            /** @scrutinizer ignore-call */ 
346
            $data = $file->getContent();
Loading history...
346
        } else if ($raw !== null) {
347
            $data = $raw;
348
        } else {
349
            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...
350
        }
351
        $data = json_decode($data, true);
352
353
        $report = $data['report'];
354
        isset($report['name']) ? $name = $report['name'] : $name = '';
355
        isset($report['subheader']) ? $subheader = $report['subheader'] : $subheader = '';
356
        $parent = 0;
357
        $dataset = 0;
358
        isset($report['type']) ? $type = $report['type'] : $type = null;
359
        isset($report['link']) ? $link = $report['link'] : $link = null;
360
        isset($report['visualization']) ? $visualization = $report['visualization'] : $visualization = null;
361
        isset($report['chart']) ? $chart = $report['chart'] : $chart = null;
362
        isset($report['chartoptions']) ? $chartoptions = $report['chartoptions'] : $chartoptions = null;
363
        isset($report['dataoptions']) ? $dataoptions = $report['dataoptions'] : $dataoptions = null;
364
        isset($report['filteroptions']) ? $filteroptions = $report['filteroptions'] : $filteroptions = null;
365
        isset($report['dimension1']) ? $dimension1 = $report['dimension1'] : $dimension1 = null;
366
        isset($report['dimension2']) ? $dimension2 = $report['dimension2'] : $dimension2 = null;
367
        isset($report['value']) ? $value = $report['value'] : $value = null;
368
369
        $reportId = $this->create($name, $subheader, $parent, $type, $dataset, $link, $visualization, $chart, $dimension1, $dimension2, $value);
370
        $this->updateOptions($reportId, $chartoptions, $dataoptions, $filteroptions);
371
        $report = $this->ReportMapper->read($reportId);
372
        $datasetId = $report['dataset'];
373
374
        foreach ($data['dataload'] as $dataload) {
375
            isset($dataload['datasource']) ? $datasource = $dataload['datasource'] : $datasource = null;
376
            isset($dataload['name']) ? $name = $dataload['name'] : $name = null;
377
            isset($dataload['option']) ? $option = $dataload['option'] : $option = null;
378
            $schedule = null;
379
380
            $dataloadId = $this->DataloadMapper->create($datasetId, $datasource);
381
            $this->DataloadMapper->update($dataloadId, $name, $option, $schedule);
382
        }
383
384
        foreach ($data['threshold'] as $threshold) {
385
            isset($threshold['dimension1']) ? $dimension1 = $threshold['dimension1'] : $dimension1 = null;
386
            isset($threshold['value']) ? $value = $threshold['value'] : $value = null;
387
            isset($threshold['option']) ? $option = $threshold['option'] : $option = null;
388
            isset($threshold['severity']) ? $severity = $threshold['severity'] : $severity = null;
389
            $value = $this->floatvalue($value);
390
            $this->ThresholdMapper->create($reportId, $dimension1, $value, $option, $severity);
391
        }
392
393
        foreach ($data['data'] as $dData) {
394
            isset($dData[0]) ? $dimension1 = $dData[0] : $dimension1 = null;
395
            isset($dData[1]) ? $dimension2 = $dData[1] : $dimension2 = null;
396
            isset($dData[2]) ? $value = $dData[2] : $value = null;
397
            $this->StorageMapper->create($datasetId, $dimension1, $dimension2, $value);
398
        }
399
400
        if (isset($data['favorite'])) {
401
            $this->setFavorite($reportId, $data['favorite']);
402
        }
403
404
        return $reportId;
405
    }
406
407
    /**
408
     * Export Report
409
     *
410
     * @param int $reportId
411
     * @return DataDownloadResponse
412
     */
413
    public function export(int $reportId)
414
    {
415
        $result = array();
416
        $result['report'] = $this->ReportMapper->read($reportId);
417
        $datasetId = $result['report']['dataset'];
418
        $result['dataload'] = $this->DataloadMapper->read($datasetId);
419
        $result['threshold'] = $this->ThresholdMapper->getThresholdsByReport($reportId);
420
        $result['favorite'] = '';
421
422
        if ($result['report']['type'] === DatasourceController::DATASET_TYPE_INTERNAL_DB) {
423
            $result['data'] = $this->StorageMapper->read($datasetId);
424
        }
425
426
        unset($result['report']['id'], $result['report']['user_id'], $result['report']['user_id'], $result['report']['parent'], $result['report']['dataset']);
427
        $data = json_encode($result);
428
        return new DataDownloadResponse($data, $result['report']['name'] . '.export.txt', 'text/plain; charset=utf-8');
429
    }
430
431
    /**
432
     * Update report options
433
     *
434
     * @param int $reportId
435
     * @param $chartoptions
436
     * @param $dataoptions
437
     * @param $filteroptions
438
     * @return bool
439
     */
440
    public function updateOptions(int $reportId, $chartoptions, $dataoptions, $filteroptions)
441
    {
442
        return $this->ReportMapper->updateOptions($reportId, $chartoptions, $dataoptions, $filteroptions);
443
    }
444
445
    /**
446
     * get report refresh options
447
     *
448
     * @NoAdminRequired
449
     * @param int $reportId
450
     * @param $refresh
451
     * @return bool
452
     */
453
    public function updateRefresh(int $reportId, $refresh)
454
    {
455
        return $this->ReportMapper->updateRefresh($reportId, $refresh);
456
    }
457
458
    /**
459
     * search for reports
460
     *
461
     * @param string $searchString
462
     * @return array
463
     */
464
    public function search(string $searchString)
465
    {
466
        return $this->ReportMapper->search($searchString);
467
    }
468
469
    /**
470
     * @throws Exception
471
     */
472
    public function reportsForDataset($datasetId) {
473
        return $this->ReportMapper->reportsForDataset($datasetId);
474
    }
475
476
    /**
477
     * migrate old favorite ids
478
     *
479
     * @param $ownReports
480
     * @return bool
481
     */
482
    private function favoriteMigration($ownReports) {
483
        $favorites = $this->tagManager->load('analytics')->getFavorites();
484
        foreach ($favorites as $favorite) {
485
            $key = array_search($favorite, array_column($ownReports, 'dataset'));
486
            if ($key) {
487
                $this->logger->info('Favorite was migrated from '. $ownReports[$key]['dataset'] . ' to new report ' . $ownReports[$key]['id']);
488
                $this->tagManager->load('analytics')->removeFromFavorites($ownReports[$key]['dataset']);
489
                $this->tagManager->load('analytics')->addToFavorites($ownReports[$key]['id']);
490
            }
491
        }
492
        return true;
493
    }
494
495
    private function floatvalue($val)
496
    {
497
        $val = str_replace(",", ".", $val);
498
        $val = preg_replace('/\.(?=.*\.)/', '', $val);
499
        $val = preg_replace('/[^0-9-.]+/', '', $val);
500
        if (is_numeric($val)) {
501
            return number_format(floatval($val), 2, '.', '');
502
        } else {
503
            return false;
504
        }
505
    }
506
507
}
508