GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

ConfigureRolesHandler   B
last analyzed

Complexity

Total Complexity 52

Size/Duplication

Total Lines 370
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 52
eloc 143
dl 0
loc 370
rs 7.44
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A ignoreUnmappableColumns() 0 13 3
A getExamplesFromFile() 0 28 6
A setImportJob() 0 8 1
A configureJob() 0 23 5
A isMappingNecessary() 0 12 3
A makeExamplesUnique() 0 4 2
A getHeaders() 0 18 4
A processSpecifics() 0 15 3
A getNextData() 0 21 2
A getExamples() 0 3 1
A saveColumCount() 0 5 1
A getRoles() 0 9 2
C configurationComplete() 0 48 13
A getReader() 0 17 3
A getExampleFromLine() 0 6 3

How to fix   Complexity   

Complex Class

Complex classes like ConfigureRolesHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ConfigureRolesHandler, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * ConfigureRolesHandler.php
4
 * Copyright (c) 2019 [email protected]
5
 *
6
 * This file is part of Firefly III (https://github.com/firefly-iii).
7
 *
8
 * This program is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License as
10
 * published by the Free Software Foundation, either version 3 of the
11
 * License, or (at your option) any later version.
12
 *
13
 * This program 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 Affero General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Affero General Public License
19
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20
 */
21
22
declare(strict_types=1);
23
24
namespace FireflyIII\Support\Import\JobConfiguration\File;
25
26
use FireflyIII\Exceptions\FireflyException;
27
use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
28
use FireflyIII\Import\Specifics\SpecificInterface;
29
use FireflyIII\Models\Attachment;
30
use FireflyIII\Models\ImportJob;
31
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
32
use Illuminate\Support\Collection;
33
use Illuminate\Support\MessageBag;
34
use League\Csv\Exception;
35
use League\Csv\Reader;
36
use League\Csv\Statement;
37
use Log;
38
39
/**
40
 * Class ConfigureRolesHandler
41
 * @deprecated
42
 * @codeCoverageIgnore
43
 */
44
class ConfigureRolesHandler implements FileConfigurationInterface
0 ignored issues
show
Deprecated Code introduced by
The interface FireflyIII\Support\Impor...eConfigurationInterface has been deprecated. ( Ignorable by Annotation )

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

44
class ConfigureRolesHandler implements /** @scrutinizer ignore-deprecated */ FileConfigurationInterface

This interface has been deprecated. The supplier of the interface has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the interface will be removed and what other interface to use instead.

Loading history...
45
{
46
    /** @var AttachmentHelperInterface */
47
    private $attachments;
48
    /** @var array */
49
    private $examples;
50
    /** @var ImportJob */
51
    private $importJob;
52
    /** @var ImportJobRepositoryInterface */
53
    private $repository;
54
    /** @var int */
55
    private $totalColumns;
56
57
    /**
58
     * Verifies that the configuration of the job is actually complete, and valid.
59
     *
60
     * @param array $config
61
     *
62
     * @return MessageBag
63
     *
64
     */
65
    public function configurationComplete(array $config): MessageBag
66
    {
67
        /** @var array $roles */
68
        $roles    = $config['column-roles'];
69
        $assigned = 0;
70
71
        // check if data actually contains amount column (foreign amount does not count)
72
        $hasAmount        = false;
73
        $hasForeignAmount = false;
74
        $hasForeignCode   = false;
75
        foreach ($roles as $role) {
76
            if ('_ignore' !== $role) {
77
                ++$assigned;
78
            }
79
            if (in_array($role, ['amount', 'amount_credit', 'amount_debit', 'amount_negated'])) {
80
                $hasAmount = true;
81
            }
82
            if ('foreign-currency-code' === $role) {
83
                $hasForeignCode = true;
84
            }
85
            if ('amount_foreign' === $role) {
86
                $hasForeignAmount = true;
87
            }
88
        }
89
90
        // all assigned and correct foreign info
91
        if ($assigned > 0 && $hasAmount && ($hasForeignCode === $hasForeignAmount)) {
92
            return new MessageBag;
93
        }
94
        if (0 === $assigned || !$hasAmount) {
95
            $message  = (string)trans('import.job_config_roles_rwarning');
96
            $messages = new MessageBag();
97
            $messages->add('error', $message);
98
99
            return $messages;
100
        }
101
102
        // warn if has foreign amount but no currency code:
103
        if ($hasForeignAmount && !$hasForeignCode) {
104
            $message  = (string)trans('import.job_config_roles_fa_warning');
105
            $messages = new MessageBag();
106
            $messages->add('error', $message);
107
108
            return $messages;
109
        }
110
111
112
        return new MessageBag; // @codeCoverageIgnore
113
    }
114
115
    /**
116
     * Store data associated with current stage.
117
     *
118
     * @param array $data
119
     *
120
     * @return MessageBag
121
     */
122
    public function configureJob(array $data): MessageBag
123
    {
124
        $config = $this->importJob->configuration;
125
        $count  = $config['column-count'];
126
        for ($i = 0; $i < $count; ++$i) {
127
            $role                            = $data['role'][$i] ?? '_ignore';
128
            $mapping                         = (isset($data['map'][$i]) && '1' === $data['map'][$i]);
129
            $config['column-roles'][$i]      = $role;
130
            $config['column-do-mapping'][$i] = $mapping;
131
            Log::debug(sprintf('Column %d has been given role %s (mapping: %s)', $i, $role, var_export($mapping, true)));
132
        }
133
        $config   = $this->ignoreUnmappableColumns($config);
134
        $messages = $this->configurationComplete($config);
135
136
        if (0 === $messages->count()) {
137
            $this->repository->setStage($this->importJob, 'ready_to_run');
138
            if ($this->isMappingNecessary($config)) {
139
                $this->repository->setStage($this->importJob, 'map');
140
            }
141
            $this->repository->setConfiguration($this->importJob, $config);
142
        }
143
144
        return $messages;
145
    }
146
147
    /**
148
     * Extracts example data from a single row and store it in the class.
149
     *
150
     * @param array $line
151
     */
152
    public function getExampleFromLine(array $line): void
153
    {
154
        foreach ($line as $column => $value) {
155
            $value = trim($value);
156
            if ('' != $value) {
157
                $this->examples[$column][] = $value;
158
            }
159
        }
160
    }
161
162
    /**
163
     * @return array
164
     */
165
    public function getExamples(): array
166
    {
167
        return $this->examples;
168
    }
169
170
    /**
171
     * Return a bunch of examples from the CSV file the user has uploaded.
172
     *
173
     * @param Reader $reader
174
     * @param array  $config
175
     *
176
     * @throws FireflyException
177
     */
178
    public function getExamplesFromFile(Reader $reader, array $config): void
179
    {
180
        $limit  = (int)config('csv.example_rows', 5);
181
        $offset = isset($config['has-headers']) && true === $config['has-headers'] ? 1 : 0;
182
183
        // make statement.
184
        try {
185
            $stmt = (new Statement)->limit($limit)->offset($offset);
186
            // @codeCoverageIgnoreStart
187
        } catch (Exception $e) {
188
            Log::error($e->getMessage());
189
            throw new FireflyException($e->getMessage());
190
        }
191
        // @codeCoverageIgnoreEnd
192
193
        // grab the records:
194
        $records = $stmt->process($reader);
195
        /** @var array $line */
196
        foreach ($records as $line) {
197
            $line               = array_values($line);
198
            $line               = $this->processSpecifics($config, $line);
199
            $count              = count($line);
200
            $this->totalColumns = $count > $this->totalColumns ? $count : $this->totalColumns;
201
            $this->getExampleFromLine($line);
202
        }
203
        // save column count:
204
        $this->saveColumCount();
205
        $this->makeExamplesUnique();
206
    }
207
208
    /**
209
     * Get the header row, if one is present.
210
     *
211
     * @param Reader $reader
212
     * @param array  $config
213
     *
214
     * @return array
215
     * @throws FireflyException
216
     */
217
    public function getHeaders(Reader $reader, array $config): array
218
    {
219
        $headers = [];
220
        if (isset($config['has-headers']) && true === $config['has-headers']) {
221
            try {
222
                $stmt    = (new Statement)->limit(1)->offset(0);
223
                $records = $stmt->process($reader);
224
                $headers = $records->fetchOne();
225
                // @codeCoverageIgnoreStart
226
            } catch (Exception $e) {
227
                Log::error($e->getMessage());
228
                throw new FireflyException($e->getMessage());
229
            }
230
            // @codeCoverageIgnoreEnd
231
            Log::debug('Detected file headers:', $headers);
232
        }
233
234
        return $headers;
235
    }
236
237
    /**
238
     * Get the data necessary to show the configuration screen.
239
     *
240
     * @return array
241
     * @throws FireflyException
242
     */
243
    public function getNextData(): array
244
    {
245
        try {
246
            $reader = $this->getReader();
247
            // @codeCoverageIgnoreStart
248
        } catch (Exception $e) {
249
            Log::error($e->getMessage());
250
            throw new FireflyException($e->getMessage());
251
        }
252
        // @codeCoverageIgnoreEnd
253
        $configuration = $this->importJob->configuration;
254
        $headers       = $this->getHeaders($reader, $configuration);
255
256
        // get example rows:
257
        $this->getExamplesFromFile($reader, $configuration);
258
259
        return [
260
            'examples' => $this->examples,
261
            'roles'    => $this->getRoles(),
262
            'total'    => $this->totalColumns,
263
            'headers'  => $headers,
264
        ];
265
    }
266
267
    /**
268
     * Return an instance of a CSV file reader so content of the file can be read.
269
     *
270
     * @throws \League\Csv\Exception
271
     */
272
    public function getReader(): Reader
273
    {
274
        $content = '';
275
        /** @var Collection $collection */
276
        $collection = $this->repository->getAttachments($this->importJob);
277
        /** @var Attachment $attachment */
278
        foreach ($collection as $attachment) {
279
            if ('import_file' === $attachment->filename) {
280
                $content = $this->attachments->getAttachmentContent($attachment);
281
                break;
282
            }
283
        }
284
        $config = $this->repository->getConfiguration($this->importJob);
285
        $reader = Reader::createFromString($content);
286
        $reader->setDelimiter($config['delimiter']);
287
288
        return $reader;
289
    }
290
291
    /**
292
     * Returns all possible roles and translate their name. Then sort them.
293
     *
294
     * @codeCoverageIgnore
295
     * @return array
296
     */
297
    public function getRoles(): array
298
    {
299
        $roles = [];
300
        foreach (array_keys(config('csv.import_roles')) as $role) {
301
            $roles[$role] = (string)trans('import.column_' . $role);
302
        }
303
        asort($roles);
304
305
        return $roles;
306
    }
307
308
    /**
309
     * If the user has checked columns that cannot be mapped to any value, this function will
310
     * uncheck them and return the configuration again.
311
     *
312
     * @param array $config
313
     *
314
     * @return array
315
     */
316
    public function ignoreUnmappableColumns(array $config): array
317
    {
318
        $count = $config['column-count'];
319
        for ($i = 0; $i < $count; ++$i) {
320
            $role    = $config['column-roles'][$i] ?? '_ignore';
321
            $mapping = $config['column-do-mapping'][$i] ?? false;
322
            // if the column can be mapped depends on the config:
323
            $canMap                          = (bool)config(sprintf('csv.import_roles.%s.mappable', $role));
324
            $mapping                         = $mapping && $canMap;
325
            $config['column-do-mapping'][$i] = $mapping;
326
        }
327
328
        return $config;
329
    }
330
331
    /**
332
     * Returns false when it's not necessary to map values. This saves time and is user friendly
333
     * (will skip to the next screen).
334
     *
335
     * @param array $config
336
     *
337
     * @return bool
338
     */
339
    public function isMappingNecessary(array $config): bool
340
    {
341
        /** @var array $doMapping */
342
        $doMapping  = $config['column-do-mapping'] ?? [];
343
        $toBeMapped = 0;
344
        foreach ($doMapping as $doMap) {
345
            if (true === $doMap) {
346
                ++$toBeMapped;
347
            }
348
        }
349
350
        return !(0 === $toBeMapped);
351
    }
352
353
    /**
354
     * Make sure that the examples do not contain double data values.
355
     */
356
    public function makeExamplesUnique(): void
357
    {
358
        foreach ($this->examples as $index => $values) {
359
            $this->examples[$index] = array_unique($values);
360
        }
361
    }
362
363
    /**
364
     * if the user has configured specific fixes to be applied, they must be applied to the example data as well.
365
     *
366
     * @param array $config
367
     * @param array $line
368
     *
369
     * @return array
370
     */
371
    public function processSpecifics(array $config, array $line): array
372
    {
373
        $validSpecifics = array_keys(config('csv.import_specifics'));
374
        $specifics      = $config['specifics'] ?? [];
375
        $names          = array_keys($specifics);
376
        foreach ($names as $name) {
377
            if (!in_array($name, $validSpecifics, true)) {
378
                continue;
379
            }
380
            /** @var SpecificInterface $specific */
381
            $specific = app('FireflyIII\Import\Specifics\\' . $name);
382
            $line     = $specific->run($line);
383
        }
384
385
        return $line;
386
387
    }
388
389
    /**
390
     * Save the column count in the job. It's used in a later stage.
391
     *
392
     * @return void
393
     */
394
    public function saveColumCount(): void
395
    {
396
        $config                 = $this->importJob->configuration;
397
        $config['column-count'] = $this->totalColumns;
398
        $this->repository->setConfiguration($this->importJob, $config);
399
    }
400
401
    /**
402
     * Set job and some start values.
403
     *
404
     * @param ImportJob $importJob
405
     */
406
    public function setImportJob(ImportJob $importJob): void
407
    {
408
        $this->importJob  = $importJob;
409
        $this->repository = app(ImportJobRepositoryInterface::class);
410
        $this->repository->setUser($importJob->user);
411
        $this->attachments  = app(AttachmentHelperInterface::class);
412
        $this->totalColumns = 0;
413
        $this->examples     = [];
414
    }
415
}
416