Passed
Push — master ( 3334e1...093f01 )
by Marcel
02:14
created

DataloadController::update()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 4
dl 0
loc 3
rs 10
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 $option
97
     * @param $schedule
98
     * @return DataResponse
99
     */
100
    public function update(int $dataloadId, $name, $option, $schedule)
101
    {
102
        return new DataResponse($this->DataloadMapper->update($dataloadId, $name, $option, $schedule));
0 ignored issues
show
Bug introduced by
$this->DataloadMapper->u...me, $option, $schedule) 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

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

312
            return new DataResponse(/** @scrutinizer ignore-type */ $result);
Loading history...
313
        } else {
314
            return new NotFoundResponse();
315
        }
316
    }
317
318
    /**
319
     * Import clipboard data
320
     *
321
     * @NoAdminRequired
322
     * @param int $datasetId
323
     * @param $import
324
     * @return DataResponse|NotFoundResponse
325
     * @throws \Exception
326
     */
327
    public function importClipboard($datasetId, $import)
328
    {
329
        $datasetMetadata = $this->DatasetController->getOwnDataset($datasetId);
330
        if (!empty($datasetMetadata)) {
331
            $insert = $update = $errorMessage = $errorCounter = 0;
332
            $delimiter = $this->detectDelimiter($import);
333
            $rows = str_getcsv($import, "\n");
334
335
            foreach ($rows as &$row) {
336
                $row = str_getcsv($row, $delimiter);
337
                $row[2] = $this->floatvalue($row[2]);
338
                if ($row[2] === false) {
339
                    $errorCounter++;
340
                } else {
341
                    $action = $this->StorageController->update($datasetId, $row[0], $row[1], $row[2]);
342
                    $insert = $insert + $action['insert'];
343
                    $update = $update + $action['update'];
344
                }
345
                if ($errorCounter === 2) {
346
                    // first error is ignored; might be due to header row
347
                    $errorMessage = $this->l10n->t('3rd field must be a valid number');
348
                    break;
349
                }
350
            }
351
352
            $result = [
353
                'insert' => $insert,
354
                'update' => $update,
355
                'delimiter' => $delimiter,
356
                'error' => $errorMessage,
357
            ];
358
359
            if ($errorMessage === 0) $this->ActivityManager->triggerEvent($datasetId, ActivityManager::OBJECT_DATA, ActivityManager::SUBJECT_DATA_ADD_IMPORT);
360
            return new DataResponse($result);
361
        } else {
362
            return new NotFoundResponse();
363
        }
364
    }
365
366
    /**
367
     * Import data into dataset from an internal or external file
368
     *
369
     * @NoAdminRequired
370
     * @param int $datasetId
371
     * @param $path
372
     * @return DataResponse|NotFoundResponse
373
     * @throws NotFoundException
374
     * @throws \Exception
375
     */
376
    public function importFile(int $datasetId, $path)
377
    {
378
        //$this->logger->error('DataLoadController 100:'.$datasetId. $path);
379
        $datasetMetadata = $this->DatasetController->getOwnDataset($datasetId);
380
        if (!empty($datasetMetadata)) {
381
            $insert = $update = 0;
382
            $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...
383
            $option['path'] = $path;
384
            $option['link'] = $datasetMetadata['link'];
385
            $result = $this->DataSourceController->read(DataSourceController::DATASET_TYPE_INTERNAL_FILE, $option);
386
387
            if ($result['error'] === 0) {
388
                foreach ($result['data'] as &$row) {
389
                    $action = $this->StorageController->update($datasetId, $row['dimension1'], $row['dimension2'], $row['dimension3']);
390
                    $insert = $insert + $action['insert'];
391
                    $update = $update + $action['update'];
392
                }
393
            }
394
395
            $result = [
396
                'insert' => $insert,
397
                'update' => $update,
398
                'error' => $result['error'],
399
            ];
400
401
            if ($result['error'] === 0) $this->ActivityManager->triggerEvent($datasetId, ActivityManager::OBJECT_DATA, ActivityManager::SUBJECT_DATA_ADD_IMPORT);
402
            return new DataResponse($result);
403
        } else {
404
            return new NotFoundResponse();
405
        }
406
    }
407
408
    private function detectDelimiter($data)
409
    {
410
        $delimiters = ["\t", ";", "|", ","];
411
        $data_2 = null;
412
        $delimiter = $delimiters[0];
413
        foreach ($delimiters as $d) {
414
            $firstRow = str_getcsv($data, "\n")[0];
415
            $data_1 = str_getcsv($firstRow, $d);
416
            if (sizeof($data_1) > sizeof($data_2)) {
417
                $delimiter = $d;
418
                $data_2 = $data_1;
419
            }
420
        }
421
        return $delimiter;
422
    }
423
424
    private function floatvalue($val)
425
    {
426
        $val = str_replace(",", ".", $val);
427
        $val = preg_replace('/\.(?=.*\.)/', '', $val);
428
        $val = preg_replace('/[^0-9-.]+/', '', $val);
429
        if (is_numeric($val)) {
430
            return number_format(floatval($val), 2, '.', '');
431
        } else {
432
            return false;
433
        }
434
    }
435
}