Passed
Push — master ( 37b02e...ebbbe1 )
by James
08:59
created

ImportJobRepository::processFile()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 48
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 48
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 28
nc 7
nop 2
1
<?php
2
/**
3
 * ImportJobRepository.php
4
 * Copyright (c) 2017 [email protected]
5
 *
6
 * This file is part of Firefly III.
7
 *
8
 * Firefly III is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * Firefly III is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
20
 */
21
declare(strict_types=1);
22
23
namespace FireflyIII\Repositories\ImportJob;
24
25
use Crypt;
26
use FireflyIII\Exceptions\FireflyException;
27
use FireflyIII\Models\ImportJob;
28
use FireflyIII\Models\TransactionJournalMeta;
29
use FireflyIII\Repositories\User\UserRepositoryInterface;
30
use FireflyIII\User;
31
use Illuminate\Support\Str;
32
use Log;
33
use SplFileObject;
34
use Storage;
35
use Symfony\Component\HttpFoundation\File\UploadedFile;
36
37
/**
38
 * Class ImportJobRepository.
39
 */
40
class ImportJobRepository implements ImportJobRepositoryInterface
41
{
42
    /** @var User */
43
    private $user;
44
45
    /**
46
     * @param ImportJob $job
47
     * @param int       $index
48
     * @param string    $error
49
     *
50
     * @return ImportJob
51
     */
52
    public function addError(ImportJob $job, int $index, string $error): ImportJob
53
    {
54
        $extended                     = $this->getExtendedStatus($job);
55
        $extended['errors'][$index][] = $error;
56
57
        return $this->setExtendedStatus($job, $extended);
58
    }
59
60
    /**
61
     * @param ImportJob $job
62
     * @param int       $steps
63
     *
64
     * @return ImportJob
65
     */
66
    public function addStepsDone(ImportJob $job, int $steps = 1): ImportJob
67
    {
68
        $status         = $this->getExtendedStatus($job);
69
        $status['done'] += $steps;
70
        Log::debug(sprintf('Add %d to steps done for job "%s" making steps done %d', $steps, $job->key, $status['done']));
0 ignored issues
show
Bug introduced by James Cole
The property key does not seem to exist on FireflyIII\Models\ImportJob. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
71
72
        return $this->setExtendedStatus($job, $status);
73
    }
74
75
    /**
76
     * @param ImportJob $job
77
     * @param int       $steps
78
     *
79
     * @return ImportJob
80
     */
81
    public function addTotalSteps(ImportJob $job, int $steps = 1): ImportJob
82
    {
83
        $extended          = $this->getExtendedStatus($job);
84
        $total             = (int)($extended['steps'] ?? 0);
85
        $total             += $steps;
86
        $extended['steps'] = $total;
87
88
        return $this->setExtendedStatus($job, $extended);
89
90
    }
91
92
    /**
93
     * Return number of imported rows with this hash value.
94
     *
95
     * @param string $hash
96
     *
97
     * @return int
98
     */
99
    public function countByHash(string $hash): int
100
    {
101
        $json  = json_encode($hash);
102
        $count = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
103
                                       ->where('data', $json)
104
                                       ->where('name', 'importHash')
105
                                       ->count();
106
107
        return (int)$count;
108
    }
109
110
    /**
111
     * @param string $type
112
     *
113
     * @return ImportJob
114
     *
115
     * @throws FireflyException
116
     */
117
    public function create(string $type): ImportJob
118
    {
119
        $count = 0;
120
        $type  = strtolower($type);
121
122
        while ($count < 30) {
123
            $key      = Str::random(12);
124
            $existing = $this->findByKey($key);
125
            if (null === $existing->id) {
126
                $importJob = new ImportJob;
127
                $importJob->user()->associate($this->user);
128
                $importJob->file_type       = $type;
0 ignored issues
show
Bug introduced by James Cole
The property file_type does not seem to exist on FireflyIII\Models\ImportJob. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
129
                $importJob->key             = Str::random(12);
0 ignored issues
show
Bug introduced by James Cole
The property key does not seem to exist on FireflyIII\Models\ImportJob. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
130
                $importJob->status          = 'new';
0 ignored issues
show
Bug introduced by James Cole
The property status does not seem to exist on FireflyIII\Models\ImportJob. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
131
                $importJob->configuration   = [];
0 ignored issues
show
Bug introduced by James Cole
The property configuration does not seem to exist on FireflyIII\Models\ImportJob. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
132
                $importJob->extended_status = [
133
                    'steps'  => 0,
134
                    'done'   => 0,
135
                    'tag'    => 0,
136
                    'errors' => [],
137
                ];
138
                $importJob->save();
139
140
                // breaks the loop:
141
                return $importJob;
142
            }
143
            ++$count;
144
        }
145
        throw new FireflyException('Could not create an import job with a unique key after 30 tries.');
146
    }
147
148
    /**
149
     * @param string $key
150
     *
151
     * @return ImportJob
152
     */
153
    public function findByKey(string $key): ImportJob
154
    {
155
        /** @var ImportJob $result */
156
        $result = $this->user->importJobs()->where('key', $key)->first(['import_jobs.*']);
157
        if (null === $result) {
158
            return new ImportJob;
159
        }
160
161
        return $result;
162
    }
163
164
    /**
165
     * Return configuration of job.
166
     *
167
     * @param ImportJob $job
168
     *
169
     * @return array
170
     */
171
    public function getConfiguration(ImportJob $job): array
172
    {
173
        $config = $job->configuration;
174
        if (is_array($config)) {
175
            return $config;
176
        }
177
178
        return [];
179
    }
180
181
    /**
182
     * Return extended status of job.
183
     *
184
     * @param ImportJob $job
185
     *
186
     * @return array
187
     */
188
    public function getExtendedStatus(ImportJob $job): array
189
    {
190
        $status = $job->extended_status;
191
        if (is_array($status)) {
0 ignored issues
show
introduced by James Cole
The condition is_array($status) is always false.
Loading history...
192
            return $status;
193
        }
194
195
        return [];
196
    }
197
198
    /**
199
     * @param ImportJob $job
200
     *
201
     * @return string
202
     */
203
    public function getStatus(ImportJob $job): string
204
    {
205
        return $job->status;
206
    }
207
208
    /**
209
     * @param ImportJob    $job
210
     * @param UploadedFile $file
211
     *
212
     * @return bool
213
     */
214
    public function processConfiguration(ImportJob $job, UploadedFile $file): bool
215
    {
216
        /** @var UserRepositoryInterface $repository */
217
        $repository = app(UserRepositoryInterface::class);
218
        // demo user's configuration upload is ignored completely.
219
        if (!$repository->hasRole($this->user, 'demo')) {
220
            Log::debug(
221
                'Uploaded configuration file',
222
                ['name' => $file->getClientOriginalName(), 'size' => $file->getSize(), 'mime' => $file->getClientMimeType()]
223
            );
224
225
            $configFileObject = new SplFileObject($file->getRealPath());
226
            $configRaw        = $configFileObject->fread($configFileObject->getSize());
227
            $configuration    = json_decode($configRaw, true);
228
            Log::debug(sprintf('Raw configuration is %s', $configRaw));
229
            if (null !== $configuration && is_array($configuration)) {
230
                Log::debug('Found configuration', $configuration);
231
                $this->setConfiguration($job, $configuration);
232
            }
233
            if (null === $configuration) {
234
                Log::error('Uploaded configuration is NULL');
235
            }
236
            if (false === $configuration) {
237
                Log::error('Uploaded configuration is FALSE');
238
            }
239
        }
240
241
        return true;
242
    }
243
244
    /**
245
     * @param ImportJob         $job
246
     * @param null|UploadedFile $file
247
     *
248
     * @return bool
249
     *
250
     * @throws \Illuminate\Contracts\Encryption\EncryptException
251
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
252
     */
253
    public function processFile(ImportJob $job, ?UploadedFile $file): bool
254
    {
255
        if (null === $file) {
256
            return false;
257
        }
258
        /** @var UserRepositoryInterface $repository */
259
        $repository = app(UserRepositoryInterface::class);
260
        $newName    = sprintf('%s.upload', $job->key);
0 ignored issues
show
Bug introduced by James Cole
The property key does not seem to exist on FireflyIII\Models\ImportJob. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
261
        $uploaded   = new SplFileObject($file->getRealPath());
262
        $content    = trim($uploaded->fread($uploaded->getSize()));
263
264
        // verify content:
265
        $result = mb_detect_encoding($content, 'UTF-8', true);
266
        if ($result === false) {
267
            Log::error(sprintf('Cannot detect encoding for uploaded import file "%s".', $file->getClientOriginalName()));
268
269
            return false;
270
        }
271
        if ($result !== 'ASCII' && $result !== 'UTF-8') {
272
            Log::error(sprintf('Uploaded import file is %s instead of UTF8!', var_export($result, true)));
273
274
            return false;
275
        }
276
277
        $contentEncrypted = Crypt::encrypt($content);
278
        $disk             = Storage::disk('upload');
0 ignored issues
show
Bug Best Practice introduced by James Cole
The method Illuminate\Support\Facades\Storage::disk() is not static, but was called statically. ( Ignorable by Annotation )

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

278
        /** @scrutinizer ignore-call */ 
279
        $disk             = Storage::disk('upload');
Loading history...
279
280
        // user is demo user, replace upload with prepared file.
281
        if ($repository->hasRole($this->user, 'demo')) {
282
            $stubsDisk        = Storage::disk('stubs');
283
            $content          = $stubsDisk->get('demo-import.csv');
284
            $contentEncrypted = Crypt::encrypt($content);
285
            $disk->put($newName, $contentEncrypted);
286
            Log::debug('Replaced upload with demo file.');
287
288
            // also set up prepared configuration.
289
            $configuration = json_decode($stubsDisk->get('demo-configuration.json'), true);
290
            $this->setConfiguration($job, $configuration);
291
            Log::debug('Set configuration for demo user', $configuration);
292
        }
293
294
        if (!$repository->hasRole($this->user, 'demo')) {
295
            // user is not demo, process original upload:
296
            $disk->put($newName, $contentEncrypted);
297
            Log::debug('Uploaded file', ['name' => $file->getClientOriginalName(), 'size' => $file->getSize(), 'mime' => $file->getClientMimeType()]);
298
        }
299
300
        return true;
301
    }
302
303
    /**
304
     * @param ImportJob $job
305
     * @param array     $configuration
306
     *
307
     * @return ImportJob
308
     */
309
    public function setConfiguration(ImportJob $job, array $configuration): ImportJob
310
    {
311
        Log::debug(sprintf('Incoming config for job "%s" is: ', $job->key), $configuration);
0 ignored issues
show
Bug introduced by James Cole
The property key does not seem to exist on FireflyIII\Models\ImportJob. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
312
        $currentConfig      = $job->configuration;
313
        $newConfig          = array_merge($currentConfig, $configuration);
314
        $job->configuration = $newConfig;
0 ignored issues
show
Bug introduced by James Cole
The property configuration does not seem to exist on FireflyIII\Models\ImportJob. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
315
        $job->save();
316
        Log::debug(sprintf('Set config of job "%s" to: ', $job->key), $newConfig);
317
318
        return $job;
319
    }
320
321
    /**
322
     * @param ImportJob $job
323
     * @param array     $array
324
     *
325
     * @return ImportJob
326
     */
327
    public function setExtendedStatus(ImportJob $job, array $array): ImportJob
328
    {
329
        $currentStatus        = $job->extended_status;
330
        $newStatus            = array_merge($currentStatus, $array);
0 ignored issues
show
Bug introduced by James Cole
$currentStatus of type string is incompatible with the type array expected by parameter $array1 of array_merge(). ( Ignorable by Annotation )

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

330
        $newStatus            = array_merge(/** @scrutinizer ignore-type */ $currentStatus, $array);
Loading history...
331
        $job->extended_status = $newStatus;
332
        $job->save();
333
334
        return $job;
335
    }
336
337
    /**
338
     * @param ImportJob $job
339
     * @param string    $status
340
     *
341
     * @return ImportJob
342
     */
343
    public function setStatus(ImportJob $job, string $status): ImportJob
344
    {
345
        $job->status = $status;
0 ignored issues
show
Bug introduced by James Cole
The property status does not seem to exist on FireflyIII\Models\ImportJob. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
346
        $job->save();
347
348
        return $job;
349
    }
350
351
    /**
352
     * @param ImportJob $job
353
     * @param int       $steps
354
     *
355
     * @return ImportJob
356
     */
357
    public function setStepsDone(ImportJob $job, int $steps): ImportJob
358
    {
359
        $status         = $this->getExtendedStatus($job);
360
        $status['done'] = $steps;
361
        Log::debug(sprintf('Set steps done for job "%s" to %d', $job->key, $steps));
0 ignored issues
show
Bug introduced by James Cole
The property key does not seem to exist on FireflyIII\Models\ImportJob. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
362
363
        return $this->setExtendedStatus($job, $status);
364
    }
365
366
    /**
367
     * @param ImportJob $job
368
     * @param int       $count
369
     *
370
     * @return ImportJob
371
     */
372
    public function setTotalSteps(ImportJob $job, int $count): ImportJob
373
    {
374
        $status          = $this->getExtendedStatus($job);
375
        $status['steps'] = $count;
376
        Log::debug(sprintf('Set total steps for job "%s" to %d', $job->key, $count));
0 ignored issues
show
Bug introduced by James Cole
The property key does not seem to exist on FireflyIII\Models\ImportJob. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
377
378
        return $this->setExtendedStatus($job, $status);
379
    }
380
381
    /**
382
     * @param User $user
383
     */
384
    public function setUser(User $user)
385
    {
386
        $this->user = $user;
387
    }
388
389
    /**
390
     * @param ImportJob $job
391
     * @param string    $status
392
     *
393
     * @return ImportJob
394
     */
395
    public function updateStatus(ImportJob $job, string $status): ImportJob
396
    {
397
        $job->status = $status;
0 ignored issues
show
Bug introduced by James Cole
The property status does not seem to exist on FireflyIII\Models\ImportJob. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
398
        $job->save();
399
400
        return $job;
401
    }
402
403
    /**
404
     * Return import file content.
405
     *
406
     * @param ImportJob $job
407
     *
408
     * @return string
409
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
410
     */
411
    public function uploadFileContents(ImportJob $job): string
412
    {
413
        return $job->uploadFileContents();
414
    }
415
}
416