Passed
Push — master ( 30a677...1ae68b )
by Marcel
03:50 queued 12s
created

DatasourceController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 10
c 1
b 0
f 0
nc 1
nop 11
dl 0
loc 24
rs 9.9332

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
 * 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\Controller;
13
14
use OCA\Analytics\Datasource\DatasourceEvent;
15
use OCA\Analytics\Datasource\Excel;
16
use OCA\Analytics\Datasource\ExternalFile;
17
use OCA\Analytics\Datasource\File;
18
use OCA\Analytics\Datasource\Github;
19
use OCA\Analytics\Datasource\Json;
20
use OCA\Analytics\Datasource\Regex;
21
use OCP\AppFramework\Controller;
22
use OCP\EventDispatcher\IEventDispatcher;
23
use OCP\Files\NotFoundException;
24
use OCP\IL10N;
25
use OCP\IRequest;
26
use Psr\Log\LoggerInterface;
27
28
class DatasourceController extends Controller
29
{
30
    private $logger;
31
    private $GithubService;
32
    private $FileService;
33
    private $ExternalFileService;
34
    private $RegexService;
35
    private $JsonService;
36
    private $ExcelService;
37
    /** @var IEventDispatcher */
38
    private $dispatcher;
39
    private $l10n;
40
41
    const DATASET_TYPE_GROUP = 0;
42
    const DATASET_TYPE_FILE = 1;
43
    const DATASET_TYPE_INTERNAL_DB = 2;
44
    const DATASET_TYPE_GIT = 3;
45
    const DATASET_TYPE_EXTERNAL_FILE = 4;
46
    const DATASET_TYPE_REGEX = 5;
47
    const DATASET_TYPE_JSON = 6;
48
    const DATASET_TYPE_EXCEL = 7;
49
50
    public function __construct(
51
        string           $AppName,
52
        IRequest         $request,
53
        LoggerInterface  $logger,
54
        Github           $GithubService,
55
        File             $FileService,
56
        Regex            $RegexService,
57
        Json             $JsonService,
58
        ExternalFile     $ExternalFileService,
59
        Excel            $ExcelService,
60
        IL10N            $l10n,
61
        IEventDispatcher $dispatcher
62
    )
63
    {
64
        parent::__construct($AppName, $request);
65
        $this->logger = $logger;
66
        $this->ExternalFileService = $ExternalFileService;
67
        $this->GithubService = $GithubService;
68
        $this->RegexService = $RegexService;
69
        $this->FileService = $FileService;
70
        $this->JsonService = $JsonService;
71
        $this->ExcelService = $ExcelService;
72
        $this->dispatcher = $dispatcher;
73
        $this->l10n = $l10n;
74
    }
75
76
    /**
77
     * get all data source ids + names
78
     *
79
     * @NoAdminRequired
80
     */
81
    public function index()
82
    {
83
        $datasources = array();
84
        $result = array();
85
        foreach ($this->getDatasources() as $key => $class) {
86
            $datasources[$key] = $class->getName();
87
        }
88
        $result['datasources'] = $datasources;
89
90
        $options = array();
91
        foreach ($this->getDatasources() as $key => $class) {
92
            $options[$key] = $class->getTemplate();
93
        }
94
        $result['options'] = $options;
95
96
        return $result;
97
    }
98
99
    /**
100
     * get all data source templates
101
     *
102
     * @NoAdminRequired
103
     * @return array
104
     */
105
    public function getTemplates()
106
    {
107
        $result = array();
108
        foreach ($this->getDatasources() as $key => $class) {
109
            $result[$key] = $class->getTemplate();
110
        }
111
        return $result;
112
    }
113
114
    /**
115
     * Get the data from a datasource;
116
     *
117
     * @NoAdminRequired
118
     * @param int $datasourceId
119
     * @param $datasetMetadata
120
     * @return array|NotFoundException
121
     */
122
    public function read(int $datasourceId, $datasetMetadata)
123
    {
124
        if (!$this->getDatasources()[$datasourceId]) {
125
            $result['error'] = $this->l10n->t('Data source not available anymore');
0 ignored issues
show
Comprehensibility Best Practice introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = array(); before regardless.
Loading history...
126
            return $result;
127
        }
128
129
        $option = array();
130
        // before 3.1.0, the options were in another format. as of 3.1.0 the standard option array is used
131
        if ($datasetMetadata['link'][0] !== '{') {
132
            $option['link'] = $datasetMetadata['link'];
133
        } else {
134
            $option = json_decode($datasetMetadata['link'], true);
135
        }
136
        $option['user_id'] = $datasetMetadata['user_id'];
137
138
        try {
139
            // read the data from the source
140
            $result = $this->getDatasources()[$datasourceId]->readData($option);
141
142
            // if data source should be timestamped/snapshoted
143
            if (isset($option['timestamp']) and $option['timestamp'] === 'true') {
144
                $result['data'] = array_map(function ($tag) {
145
                    $columns = count($tag);
146
                    return array($tag[$columns - 2], $tag[$columns - 2], $tag[$columns - 1]);
147
                }, $result['data']);
148
                date_default_timezone_set('UTC');
149
                // shift values by one dimension and stores date in second column
150
                $result['data'] = $this->replaceDimension($result['data'], 1, date("Y-m-d H:i:s") . 'Z');
151
            }
152
153
            if (isset($datasetMetadata['filteroptions']) && strlen($datasetMetadata['filteroptions']) >> 2) {
154
                // filter data
155
                $result = $this->filterData($result, $datasetMetadata['filteroptions']);
156
                // remove columns and aggregate data
157
                $result = $this->aggregateData($result, $datasetMetadata['filteroptions']);
158
            }
159
160
161
        } catch (\Error $e) {
162
            $result['error'] = $e->getMessage();
163
        }
164
165
        if (empty($result['data'])) {
166
            $result['status'] = 'nodata';
167
        }
168
        return $result;
169
    }
170
171
    /**
172
     * combine internal and registered datasources
173
     * @return array
174
     */
175
    private function getDatasources()
176
    {
177
        return $this->getOwnDatasources() + $this->getRegisteredDatasources();
178
    }
179
180
    /**
181
     * map all internal data sources to their IDs
182
     * @return array
183
     */
184
    private function getOwnDatasources()
185
    {
186
        $dataSources = [];
187
        $dataSources[self::DATASET_TYPE_FILE] = $this->FileService;
188
        $dataSources[self::DATASET_TYPE_EXCEL] = $this->ExcelService;
189
        $dataSources[self::DATASET_TYPE_GIT] = $this->GithubService;
190
        $dataSources[self::DATASET_TYPE_EXTERNAL_FILE] = $this->ExternalFileService;
191
        $dataSources[self::DATASET_TYPE_REGEX] = $this->RegexService;
192
        $dataSources[self::DATASET_TYPE_JSON] = $this->JsonService;
193
        return $dataSources;
194
    }
195
196
    /**
197
     * map all registered data sources to their IDs
198
     * @return array
199
     */
200
    private function getRegisteredDatasources()
201
    {
202
        $dataSources = [];
203
        $event = new DatasourceEvent();
204
        $this->dispatcher->dispatchTyped($event);
205
206
        foreach ($event->getDataSources() as $class) {
207
            try {
208
                $uniqueId = '99' . \OC::$server->get($class)->getId();
209
210
                if (isset($dataSources[$uniqueId])) {
211
                    $this->logger->error(new \InvalidArgumentException('Data source with the same ID already registered: ' . \OC::$server->get($class)->getName()));
212
                    continue;
213
                }
214
                $dataSources[$uniqueId] = \OC::$server->get($class);
215
            } catch (\Error $e) {
216
                $this->logger->error('Can not initialize data source: ' . json_encode($class));
217
                $this->logger->error($e->getMessage());
218
            }
219
        }
220
        return $dataSources;
221
    }
222
223
    /**
224
     * replace all values of one dimension
225
     *
226
     * @NoAdminRequired
227
     * @param $Array
228
     * @param $Find
229
     * @param $Replace
230
     * @return array
231
     */
232
    private function replaceDimension($Array, $Find, $Replace)
233
    {
234
        if (is_array($Array)) {
235
            foreach ($Array as $Key => $Val) {
236
                if (is_array($Array[$Key])) {
237
                    $Array[$Key] = $this->replaceDimension($Array[$Key], $Find, $Replace);
238
                } else {
239
                    if ($Key === $Find) {
240
                        $Array[$Key] = $Replace;
241
                    }
242
                }
243
            }
244
        }
245
        return $Array;
246
    }
247
248
    /**
249
     * apply the fiven filters to the hole result set
250
     *
251
     * @NoAdminRequired
252
     * @param $data
253
     * @param $filter
254
     * @return array
255
     */
256
    private function filterData($data, $filter)
257
    {
258
        $options = json_decode($filter, true);
259
        if (isset($options['filter'])) {
260
            foreach ($options['filter'] as $key => $value) {
261
                $filterValue = $value['value'];
262
                $filterOption = $value['option'];
263
                $filtered = array();
264
265
                foreach ($data['data'] as $record) {
266
                    if (
267
                        ($filterOption === 'EQ' && $record[$key] === $filterValue)
268
                        || ($filterOption === 'GT' && $record[$key] > $filterValue)
269
                        || ($filterOption === 'LT' && $record[$key] < $filterValue)
270
                        || ($filterOption === 'LIKE' && strpos($record[$key], $filterValue) !== FALSE)
271
                    ) {
272
                        array_push($filtered, $record);
273
                    } else if ($filterOption === 'IN') {
274
                        $filterValues = explode(',', $filterValue);
275
                        if (in_array($record[$key], $filterValues)) {
276
                            array_push($filtered, $record);
277
                        }
278
                    }
279
                }
280
                $data['data'] = $filtered;
281
            }
282
        }
283
        return $data;
284
    }
285
286
    private function aggregateData($data, $filter)
287
    {
288
        $options = json_decode($filter, true);
289
        if (isset($options['drilldown'])) {
290
            // Sort the indices in descending order
291
            $sortedIndices = array_keys($options['drilldown']);
292
            rsort($sortedIndices);
293
294
            foreach ($sortedIndices as $removeIndex) {
295
                $aggregatedData = [];
296
297
                // remove the header of the column which is not needed
298
                unset($data['header'][$removeIndex]);
299
                $data['header'] = array_values($data['header']);
300
301
                // remove the column of the data
302
                foreach ($data['data'] as $row) {
303
                    // Remove the desired column by its index
304
                    unset($row[$removeIndex]);
305
306
                    // The last column is assumed to always be the value
307
                    $value = array_pop($row);
308
309
                    // If there are no columns left except the value column
310
                    if (empty($row)) {
311
                        $key = 'xxsingle_valuexx';
312
                    } else {
313
                        // Use remaining columns as key
314
                        $key = implode("|", $row);
315
                    }
316
317
                    if (!isset($aggregatedData[$key])) {
318
                        $aggregatedData[$key] = 0;
319
                    }
320
                    $aggregatedData[$key] += $value;
321
                }
322
323
                // Convert the associative array to the desired format
324
                $result = [];
325
                foreach ($aggregatedData as $aKey => $aValue) {
326
                    // If only the value column remains, append its total value
327
                    if ($aKey === 'xxsingle_valuexx') {
328
                        $aKey = $this->l10n->t('Total');
329
                        // Add an empty column to the beginning
330
                        array_unshift($data['header'], "");
331
                    }
332
                    $result[] = [$aKey, $aValue];
333
                }
334
335
                $data['data'] = $result;
336
            }
337
        }
338
        return $data;
339
    }
340
}