Passed
Push — master ( ffe1e4...544106 )
by Marcel
05:27
created

DataloadController::importClipboard()   B

Complexity

Conditions 6
Paths 11

Size

Total Lines 36
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 26
nc 11
nop 2
dl 0
loc 36
rs 8.8817
c 0
b 0
f 0
1
<?php
2
/**
3
 * Data 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 Marcel Scherello
10
 */
11
12
namespace OCA\Analytics\Controller;
13
14
use OCA\Analytics\Activity\ActivityManager;
15
use OCA\Analytics\Db\DataloadMapper;
16
use OCP\AppFramework\Controller;
17
use OCP\AppFramework\Http\DataResponse;
18
use OCP\AppFramework\Http\NotFoundResponse;
19
use OCP\Files\NotFoundException;
20
use OCP\IL10N;
21
use OCP\ILogger;
22
use OCP\IRequest;
23
24
class DataloadController extends Controller
25
{
26
    private $logger;
27
    private $StorageController;
28
    private $DataSourceController;
29
    private $userId;
30
    private $ActivityManager;
31
    private $DatasetController;
32
    private $l10n;
33
    private $DataloadMapper;
34
35
    public function __construct(
36
        string $AppName,
37
        IRequest $request,
38
        IL10N $l10n,
39
        $userId,
40
        ILogger $logger,
41
        ActivityManager $ActivityManager,
42
        DataSourceController $DataSourceController,
43
        DatasetController $DatasetController,
44
        StorageController $StorageController,
45
        DataloadMapper $DataloadMapper
46
    )
47
    {
48
        parent::__construct($AppName, $request);
49
        $this->l10n = $l10n;
50
        $this->userId = $userId;
51
        $this->logger = $logger;
52
        $this->StorageController = $StorageController;
53
        $this->ActivityManager = $ActivityManager;
54
        $this->DataSourceController = $DataSourceController;
55
        $this->DatasetController = $DatasetController;
56
        $this->DataloadMapper = $DataloadMapper;
57
    }
58
59
    // Dataloads
60
    // Dataloads
61
    // Dataloads
62
63
    /**
64
     * create a new dataload
65
     *
66
     * @NoAdminRequired
67
     * @param int $datasetId
68
     * @param int $datasourceId
69
     * @return DataResponse
70
     */
71
    public function create(int $datasetId, int $datasourceId)
72
    {
73
        return new DataResponse($this->DataloadMapper->create($datasetId, $datasourceId));
0 ignored issues
show
Bug introduced by
$this->DataloadMapper->c...tasetId, $datasourceId) of type integer is incompatible with the type array|object expected by parameter $data of OCP\AppFramework\Http\DataResponse::__construct(). ( Ignorable by Annotation )

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

73
        return new DataResponse(/** @scrutinizer ignore-type */ $this->DataloadMapper->create($datasetId, $datasourceId));
Loading history...
74
    }
75
76
    /**
77
     * get all dataloads for a dataset
78
     *
79
     * @NoAdminRequired
80
     * @param int $datasetId
81
     * @return DataResponse
82
     */
83
    public function read(int $datasetId)
84
    {
85
        $result['dataloads'] = $this->DataloadMapper->read($datasetId);;
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...
86
        $result['templates'] = $this->DataSourceController->getTemplates();
87
        return new DataResponse($result);
88
    }
89
90
    /**
91
     * update dataload
92
     *
93
     * @NoAdminRequired
94
     * @param int $dataloadId
95
     * @param $name
96
     * @param int $datasource
97
     * @param $option
98
     * @param int $dataset
99
     * @return DataResponse
100
     */
101
    public function update(int $dataloadId, $name, $option)
102
    {
103
        return new DataResponse($this->DataloadMapper->update($dataloadId, $name, $option));
0 ignored issues
show
Bug introduced by
$this->DataloadMapper->u...loadId, $name, $option) of type true is incompatible with the type array|object expected by parameter $data of OCP\AppFramework\Http\DataResponse::__construct(). ( Ignorable by Annotation )

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

103
        return new DataResponse(/** @scrutinizer ignore-type */ $this->DataloadMapper->update($dataloadId, $name, $option));
Loading history...
104
    }
105
106
    /**
107
     * delete a dataload
108
     *
109
     * @NoAdminRequired
110
     * @param int $dataloadId
111
     * @return bool
112
     */
113
    public function delete(int $dataloadId)
114
    {
115
        return $this->DataloadMapper->delete($dataloadId);
116
    }
117
118
    /**
119
     * simulate a dataload and output its data
120
     *
121
     * @NoAdminRequired
122
     * @param int $dataloadId
123
     * @return DataResponse
124
     * @throws NotFoundException
125
     */
126
    public function simulate(int $dataloadId)
127
    {
128
        $result = $this->getDataFromDatasource($dataloadId);
129
        return new DataResponse($result);
130
    }
131
132
    /**
133
     * execute a dataload from datasource and store into dataset
134
     *
135
     * @NoAdminRequired
136
     * @param int $dataloadId
137
     * @param $path
138
     * @return DataResponse|NotFoundResponse
139
     * @throws NotFoundException
140
     * @throws \Exception
141
     */
142
    public function execute(int $dataloadId)
143
    {
144
        $result = $this->getDataFromDatasource($dataloadId);
145
        $insert = $update = 0;
146
        $datasetId = $result['datasetId'];
147
148
        if ($result['error'] === 0) {
149
            foreach ($result['data'] as &$row) {
150
                $action = $this->StorageController->update($datasetId, $row['dimension1'], $row['dimension2'], $row['dimension3']);
151
                $insert = $insert + $action['insert'];
152
                $update = $update + $action['update'];
153
            }
154
        }
155
156
        $result = [
157
            'insert' => $insert,
158
            'update' => $update,
159
            'error' => $result['error'],
160
        ];
161
162
        if ($result['error'] === 0) $this->ActivityManager->triggerEvent($datasetId, ActivityManager::OBJECT_DATA, ActivityManager::SUBJECT_DATA_ADD_IMPORT);
163
164
        return new DataResponse($result);
165
    }
166
167
    /**
168
     * get the data from datasource
169
     * to be used in simulation or execution
170
     *
171
     * @NoAdminRequired
172
     * @param int $dataloadId
173
     * @return array
174
     * @throws NotFoundException
175
     */
176
    private function getDataFromDatasource(int $dataloadId)
177
    {
178
        //$this->logger->error('DataLoadController 71:'.$dataloadId);
179
        $dataloadMetadata = $this->DataloadMapper->getDataloadById($dataloadId);
180
        $datasetMetadata = $this->DatasetController->getOwnDataset($dataloadMetadata['dataset']);
181
182
        if (!empty($datasetMetadata)) {
183
            $option = json_decode($dataloadMetadata['option'], true);
184
            $option['user_id'] = $this->userId;
185
186
            $this->logger->debug('DataLoadController 188:' . $dataloadMetadata['option'] . '---' . json_encode($option));
187
            $result = $this->DataSourceController->read((int)$dataloadMetadata['datasource'], $option);
188
            $result['datasetId'] = $dataloadMetadata['dataset'];
189
190
            if (isset($option['timestamp']) and $option['timestamp'] === 'true') {
191
                // if datasource should be timestamped/snapshoted
192
                // shift values by one dimension
193
                $result['data'] = array_map(function ($tag) {
194
                    return array(
195
                        'dimension1' => $tag['dimension2'],
196
                        'dimension2' => $tag['dimension2'],
197
                        'dimension3' => $tag['dimension3']
198
                    );
199
                }, $result['data']);
200
                $result['data'] = $this->replaceDimension($result['data'], 'dimension2', date("Y-m-dTH:i:s"));
201
            }
202
203
            return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result returns the type OCP\Files\NotFoundException which is incompatible with the documented return type array.
Loading history...
204
        } else {
205
            return new NotFoundResponse();
0 ignored issues
show
Bug Best Practice introduced by
The expression return new OCP\AppFramew...Http\NotFoundResponse() returns the type OCP\AppFramework\Http\NotFoundResponse which is incompatible with the documented return type array.
Loading history...
206
        }
207
    }
208
209
    /**
210
     * replace all values of one dimension
211
     *
212
     * @NoAdminRequired
213
     * @param $Array
214
     * @param $Find
215
     * @param $Replace
216
     * @return array
217
     */
218
    private function replaceDimension($Array, $Find, $Replace)
219
    {
220
        if (is_array($Array)) {
221
            foreach ($Array as $Key => $Val) {
222
                if (is_array($Array[$Key])) {
223
                    $Array[$Key] = $this->replaceDimension($Array[$Key], $Find, $Replace);
224
                } else {
225
                    if ($Key == $Find) {
226
                        $Array[$Key] = $Replace;
227
                    }
228
                }
229
            }
230
        }
231
        return $Array;
232
    }
233
234
    // Data Manipulation
235
    // Data Manipulation
236
    // Data Manipulation
237
238
    /**
239
     * update data from input form
240
     *
241
     * @NoAdminRequired
242
     * @param int $datasetId
243
     * @param $dimension1
244
     * @param $dimension2
245
     * @param $dimension3
246
     * @return DataResponse|NotFoundResponse
247
     * @throws \Exception
248
     */
249
    public function updateData(int $datasetId, $dimension1, $dimension2, $dimension3)
250
    {
251
        $datasetMetadata = $this->DatasetController->getOwnDataset($datasetId);
252
        if (!empty($datasetMetadata)) {
253
            $insert = $update = $errorMessage = 0;
254
            $action = array();
255
            $dimension3 = $this->floatvalue($dimension3);
256
            if ($dimension3 === false) {
257
                $errorMessage = $this->l10n->t('3rd field must be a valid number');
258
            } else {
259
                $action = $this->StorageController->update($datasetId, $dimension1, $dimension2, $dimension3);
260
                $insert = $insert + $action['insert'];
261
                $update = $update + $action['update'];
262
            }
263
264
            $result = [
265
                'insert' => $insert,
266
                'update' => $update,
267
                'error' => $errorMessage,
268
                'validate' => $action['validate'],
269
            ];
270
271
            //$this->logger->error('DataLoadController 88:'.$errorMessage);
272
            if ($errorMessage === 0) $this->ActivityManager->triggerEvent($datasetId, ActivityManager::OBJECT_DATA, ActivityManager::SUBJECT_DATA_ADD);
273
            return new DataResponse($result);
274
        } else {
275
            return new NotFoundResponse();
276
        }
277
    }
278
279
    /**
280
     * update data from input form
281
     *
282
     * @NoAdminRequired
283
     * @param int $datasetId
284
     * @param $dimension1
285
     * @param $dimension2
286
     * @return DataResponse|NotFoundResponse
287
     */
288
    public function deleteData(int $datasetId, $dimension1, $dimension2)
289
    {
290
        $datasetMetadata = $this->DatasetController->getOwnDataset($datasetId);
291
        if (!empty($datasetMetadata)) {
292
            $result = $this->StorageController->delete($datasetId, $dimension1, $dimension2);
293
            return new DataResponse($result);
0 ignored issues
show
Bug introduced by
$result of type true is incompatible with the type array|object expected by parameter $data of OCP\AppFramework\Http\DataResponse::__construct(). ( Ignorable by Annotation )

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

293
            return new DataResponse(/** @scrutinizer ignore-type */ $result);
Loading history...
294
        } else {
295
            return new NotFoundResponse();
296
        }
297
    }
298
299
    /**
300
     * Import clipboard data
301
     *
302
     * @NoAdminRequired
303
     * @param int $datasetId
304
     * @param $import
305
     * @return DataResponse|NotFoundResponse
306
     * @throws \Exception
307
     */
308
    public function importClipboard($datasetId, $import)
309
    {
310
        $datasetMetadata = $this->DatasetController->getOwnDataset($datasetId);
311
        if (!empty($datasetMetadata)) {
312
            $insert = $update = $errorMessage = $errorCounter = 0;
313
            $delimiter = $this->detectDelimiter($import);
314
            $rows = str_getcsv($import, "\n");
315
316
            foreach ($rows as &$row) {
317
                $row = str_getcsv($row, $delimiter);
318
                $row[2] = $this->floatvalue($row[2]);
319
                if ($row[2] === false) {
320
                    $errorCounter++;
321
                } else {
322
                    $action = $this->StorageController->update($datasetId, $row[0], $row[1], $row[2]);
323
                    $insert = $insert + $action['insert'];
324
                    $update = $update + $action['update'];
325
                }
326
                if ($errorCounter === 2) {
327
                    // first error is ignored; might be due to header row
328
                    $errorMessage = $this->l10n->t('3rd field must be a valid number');
329
                    break;
330
                }
331
            }
332
333
            $result = [
334
                'insert' => $insert,
335
                'update' => $update,
336
                'delimiter' => $delimiter,
337
                'error' => $errorMessage,
338
            ];
339
340
            if ($errorMessage === 0) $this->ActivityManager->triggerEvent($datasetId, ActivityManager::OBJECT_DATA, ActivityManager::SUBJECT_DATA_ADD_IMPORT);
341
            return new DataResponse($result);
342
        } else {
343
            return new NotFoundResponse();
344
        }
345
    }
346
347
    /**
348
     * Import data into dataset from an internal or external file
349
     *
350
     * @NoAdminRequired
351
     * @param int $datasetId
352
     * @param $path
353
     * @return DataResponse|NotFoundResponse
354
     * @throws NotFoundException
355
     * @throws \Exception
356
     */
357
    public function importFile(int $datasetId, $path)
358
    {
359
        //$this->logger->error('DataLoadController 100:'.$datasetId. $path);
360
        $datasetMetadata = $this->DatasetController->getOwnDataset($datasetId);
361
        if (!empty($datasetMetadata)) {
362
            $insert = $update = 0;
363
            $option['user_id'] = $datasetMetadata['user_id'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$option was never initialized. Although not strictly required by PHP, it is generally a good practice to add $option = array(); before regardless.
Loading history...
364
            $option['path'] = $path;
365
            $option['link'] = $datasetMetadata['link'];
366
            $result = $this->DataSourceController->read(DataSourceController::DATASET_TYPE_INTERNAL_FILE, $option);
367
368
            if ($result['error'] === 0) {
369
                foreach ($result['data'] as &$row) {
370
                    $action = $this->StorageController->update($datasetId, $row['dimension1'], $row['dimension2'], $row['dimension3']);
371
                    $insert = $insert + $action['insert'];
372
                    $update = $update + $action['update'];
373
                }
374
            }
375
376
            $result = [
377
                'insert' => $insert,
378
                'update' => $update,
379
                'error' => $result['error'],
380
            ];
381
382
            if ($result['error'] === 0) $this->ActivityManager->triggerEvent($datasetId, ActivityManager::OBJECT_DATA, ActivityManager::SUBJECT_DATA_ADD_IMPORT);
383
            return new DataResponse($result);
384
        } else {
385
            return new NotFoundResponse();
386
        }
387
    }
388
389
    private function detectDelimiter($data)
390
    {
391
        $delimiters = ["\t", ";", "|", ","];
392
        $data_2 = null;
393
        $delimiter = $delimiters[0];
394
        foreach ($delimiters as $d) {
395
            $firstRow = str_getcsv($data, "\n")[0];
396
            $data_1 = str_getcsv($firstRow, $d);
397
            if (sizeof($data_1) > sizeof($data_2)) {
398
                $delimiter = $d;
399
                $data_2 = $data_1;
400
            }
401
        }
402
        return $delimiter;
403
    }
404
405
    private function floatvalue($val)
406
    {
407
        $val = str_replace(",", ".", $val);
408
        $val = preg_replace('/\.(?=.*\.)/', '', $val);
409
        $val = preg_replace('/[^0-9-.]+/', '', $val);
410
        if (is_numeric($val)) {
411
            return number_format(floatval($val), 2, '.', '');
412
        } else {
413
            return false;
414
        }
415
    }
416
}