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.

ApplyRules   B
last analyzed

Complexity

Total Complexity 46

Size/Duplication

Total Lines 354
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 46
eloc 154
dl 0
loc 354
rs 8.72
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
B verifyInputAccounts() 0 40 8
B verifyInputRules() 0 25 7
A getRulesToApply() 0 18 4
A stupidLaravel() 0 10 1
A verifyInput() 0 17 2
B handle() 0 74 5
A includeRule() 0 5 3
A grabAllRules() 0 3 1
B verifyInputDates() 0 31 8
B verifyInputRuleGroups() 0 25 7

How to fix   Complexity   

Complex Class

Complex classes like ApplyRules 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 ApplyRules, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * ApplyRules.php
4
 * Copyright (c) 2020 [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\Console\Commands\Tools;
25
26
27
use Carbon\Carbon;
28
use FireflyIII\Console\Commands\VerifiesAccessToken;
29
use FireflyIII\Exceptions\FireflyException;
30
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
31
use FireflyIII\Models\AccountType;
32
use FireflyIII\Models\Rule;
33
use FireflyIII\Models\RuleGroup;
34
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
35
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
36
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
37
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
38
use FireflyIII\TransactionRules\Engine\RuleEngine;
39
use Illuminate\Console\Command;
40
use Illuminate\Support\Collection;
41
use Log;
42
43
/**
44
 * Class ApplyRules
45
 */
46
class ApplyRules extends Command
47
{
48
    use VerifiesAccessToken;
0 ignored issues
show
Bug introduced by
The trait FireflyIII\Console\Commands\VerifiesAccessToken requires the property $data which is not provided by FireflyIII\Console\Commands\Tools\ApplyRules.
Loading history...
49
50
    /**
51
     * The console command description.
52
     *
53
     * @var string
54
     */
55
    protected $description = 'This command will apply your rules and rule groups on a selection of your transactions.';
56
    /**
57
     * The name and signature of the console command.
58
     *
59
     * @var string
60
     */
61
    protected $signature
62
        = 'firefly-iii:apply-rules
63
                            {--user=1 : The user ID that the import should import for.}
64
                            {--token= : The user\'s access token.}
65
                            {--accounts= : A comma-separated list of asset accounts or liabilities to apply your rules to.}
66
                            {--rule_groups= : A comma-separated list of rule groups to apply. Take the ID\'s of these rule groups from the Firefly III interface.}
67
                            {--rules= : A comma-separated list of rules to apply. Take the ID\'s of these rules from the Firefly III interface. Using this option overrules the option that selects rule groups.}
68
                            {--all_rules : If set, will overrule both settings and simply apply ALL of your rules.}
69
                            {--start_date= : The date of the earliest transaction to be included (inclusive). If omitted, will be your very first transaction ever. Format: YYYY-MM-DD}
70
                            {--end_date= : The date of the latest transaction to be included (inclusive). If omitted, will be your latest transaction ever. Format: YYYY-MM-DD}';
71
    /** @var array */
72
    private $acceptedAccounts;
73
    /** @var Collection */
74
    private $accounts;
75
    /** @var bool */
76
    private $allRules;
77
    /** @var Carbon */
78
    private $endDate;
79
    /** @var Collection */
80
    private $groups;
81
    /** @var RuleGroupRepositoryInterface */
82
    private $ruleGroupRepository;
83
    /** @var array */
84
    private $ruleGroupSelection;
85
    /** @var RuleRepositoryInterface */
86
    private $ruleRepository;
87
    /** @var array */
88
    private $ruleSelection;
89
    /** @var Carbon */
90
    private $startDate;
91
92
    /**
93
     * Execute the console command.
94
     *
95
     * @throws FireflyException
96
     * @return int
97
     */
98
    public function handle(): int
99
    {
100
        $this->stupidLaravel();
101
        // @codeCoverageIgnoreStart
102
        if (!$this->verifyAccessToken()) {
103
            $this->error('Invalid access token.');
104
105
            return 1;
106
        }
107
        // @codeCoverageIgnoreEnd
108
109
        // set user:
110
        $this->ruleRepository->setUser($this->getUser());
111
        $this->ruleGroupRepository->setUser($this->getUser());
112
113
        $result = $this->verifyInput();
114
        if (false === $result) {
115
            // app('telemetry')->feature('executed-command-with-error', $this->signature);
116
            return 1;
117
        }
118
119
        $this->allRules = $this->option('all_rules');
0 ignored issues
show
Documentation Bug introduced by
The property $allRules was declared of type boolean, but $this->option('all_rules') is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
120
121
        // always get all the rules of the user.
122
        $this->grabAllRules();
123
124
        // loop all groups and rules and indicate if they're included:
125
        $rulesToApply = $this->getRulesToApply();
126
        $count        = count($rulesToApply);
127
        if (0 === $count) {
128
            $this->error('No rules or rule groups have been included.');
129
            $this->warn('Make a selection using:');
130
            $this->warn('    --rules=1,2,...');
131
            $this->warn('    --rule_groups=1,2,...');
132
            $this->warn('    --all_rules');
133
134
            // app('telemetry')->feature('executed-command-with-error', $this->signature);
135
            return 1;
136
        }
137
138
        /** @var GroupCollectorInterface $collector */
139
        $collector = app(GroupCollectorInterface::class);
140
        $collector->setUser($this->getUser());
141
        $collector->setAccounts($this->accounts);
142
        $collector->setRange($this->startDate, $this->endDate);
143
        $journals = $collector->getExtractedJournals();
144
145
        // start running rules.
146
        $this->line(sprintf('Will apply %d rule(s) to %d transaction(s).', $count, count($journals)));
147
148
        // start looping.
149
        /** @var RuleEngine $ruleEngine */
150
        $ruleEngine = app(RuleEngine::class);
151
        $ruleEngine->setUser($this->getUser());
152
        $ruleEngine->setRulesToApply($rulesToApply);
153
154
        // for this call, the rule engine only includes "store" rules:
155
        $ruleEngine->setTriggerMode(RuleEngine::TRIGGER_STORE);
156
157
        $bar = $this->output->createProgressBar(count($journals));
158
        Log::debug(sprintf('Now looping %d transactions.', count($journals)));
159
        /** @var array $journal */
160
        foreach ($journals as $journal) {
161
            Log::debug('Start of new journal.');
162
            $ruleEngine->processJournalArray($journal);
163
            Log::debug('Done with all rules for this group + done with journal.');
164
            /** @noinspection DisconnectedForeachInstructionInspection */
165
            $bar->advance();
166
        }
167
        $this->line('');
168
        $this->line('Done!');
169
170
        // app('telemetry')->feature('executed-command', $this->signature);
171
        return 0;
172
    }
173
174
    /**
175
     * @return array
176
     */
177
    private function getRulesToApply(): array
178
    {
179
        $rulesToApply = [];
180
        /** @var RuleGroup $group */
181
        foreach ($this->groups as $group) {
182
            $rules = $this->ruleGroupRepository->getActiveStoreRules($group);
183
            /** @var Rule $rule */
184
            foreach ($rules as $rule) {
185
                // if in rule selection, or group in selection or all rules, it's included.
186
                $test = $this->includeRule($rule, $group);
187
                if (true === $test) {
188
                    Log::debug(sprintf('Will include rule #%d "%s"', $rule->id, $rule->title));
189
                    $rulesToApply[] = $rule->id;
190
                }
191
            }
192
        }
193
194
        return $rulesToApply;
195
    }
196
197
    /**
198
     */
199
    private function grabAllRules(): void
200
    {
201
        $this->groups = $this->ruleGroupRepository->getActiveGroups();
202
    }
203
204
    /**
205
     * @param Rule      $rule
206
     * @param RuleGroup $group
207
     *
208
     * @return bool
209
     */
210
    private function includeRule(Rule $rule, RuleGroup $group): bool
211
    {
212
        return in_array($group->id, $this->ruleGroupSelection, true)
213
               || in_array($rule->id, $this->ruleSelection, true)
214
               || $this->allRules;
215
    }
216
217
    /**
218
     * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
219
     * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
220
     * be called from the handle method instead of using the constructor to initialize the command.
221
     *
222
     * @codeCoverageIgnore
223
     */
224
    private function stupidLaravel(): void
225
    {
226
        $this->allRules            = false;
227
        $this->accounts            = new Collection;
228
        $this->ruleSelection       = [];
229
        $this->ruleGroupSelection  = [];
230
        $this->ruleRepository      = app(RuleRepositoryInterface::class);
231
        $this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
232
        $this->acceptedAccounts    = [AccountType::DEFAULT, AccountType::DEBT, AccountType::ASSET, AccountType::LOAN, AccountType::MORTGAGE];
233
        $this->groups              = new Collection;
234
    }
235
236
    /**
237
     * @throws FireflyException
238
     * @return bool
239
     */
240
    private function verifyInput(): bool
241
    {
242
        // verify account.
243
        $result = $this->verifyInputAccounts();
244
        if (false === $result) {
245
            return $result;
246
        }
247
248
        // verify rule groups.
249
        $this->verifyInputRuleGroups();
250
251
        // verify rules.
252
        $this->verifyInputRules();
253
254
        $this->verifyInputDates();
255
256
        return true;
257
    }
258
259
    /**
260
     * @throws FireflyException
261
     * @return bool
262
     */
263
    private function verifyInputAccounts(): bool
264
    {
265
        $accountString = $this->option('accounts');
266
        if (null === $accountString || '' === $accountString) {
267
            $this->error('Please use the --accounts option to indicate the accounts to apply rules to.');
268
269
            return false;
270
        }
271
        $finalList   = new Collection;
272
        $accountList = explode(',', $accountString);
273
274
        // @codeCoverageIgnoreStart
275
        if (0 === count($accountList)) {
276
            $this->error('Please use the --accounts option to indicate the accounts to apply rules to.');
277
278
            return false;
279
        }
280
        // @codeCoverageIgnoreEnd
281
282
        /** @var AccountRepositoryInterface $accountRepository */
283
        $accountRepository = app(AccountRepositoryInterface::class);
284
        $accountRepository->setUser($this->getUser());
285
286
287
        foreach ($accountList as $accountId) {
288
            $accountId = (int) $accountId;
289
            $account   = $accountRepository->findNull($accountId);
290
            if (null !== $account && in_array($account->accountType->type, $this->acceptedAccounts, true)) {
291
                $finalList->push($account);
292
            }
293
        }
294
295
        if (0 === $finalList->count()) {
296
            $this->error('Please make sure all accounts in --accounts are asset accounts or liabilities.');
297
298
            return false;
299
        }
300
        $this->accounts = $finalList;
301
302
        return true;
303
304
    }
305
306
    /**
307
     * @throws FireflyException
308
     */
309
    private function verifyInputDates(): void
310
    {
311
        // parse start date.
312
        $startDate   = Carbon::now()->startOfMonth();
313
        $startString = $this->option('start_date');
314
        if (null === $startString) {
0 ignored issues
show
introduced by
The condition null === $startString is always false.
Loading history...
315
            /** @var JournalRepositoryInterface $repository */
316
            $repository = app(JournalRepositoryInterface::class);
317
            $repository->setUser($this->getUser());
318
            $first = $repository->firstNull();
319
            if (null !== $first) {
320
                $startDate = $first->date;
321
            }
322
        }
323
        if (null !== $startString && '' !== $startString) {
324
            $startDate = Carbon::createFromFormat('Y-m-d', $startString);
325
        }
326
327
        // parse end date
328
        $endDate   = Carbon::now();
329
        $endString = $this->option('end_date');
330
        if (null !== $endString && '' !== $endString) {
331
            $endDate = Carbon::createFromFormat('Y-m-d', $endString);
332
        }
333
334
        if ($startDate > $endDate) {
335
            [$endDate, $startDate] = [$startDate, $endDate];
336
        }
337
338
        $this->startDate = $startDate;
339
        $this->endDate   = $endDate;
340
    }
341
342
    /**
343
     * @return bool
344
     */
345
    private function verifyInputRuleGroups(): bool
346
    {
347
        $ruleGroupString = $this->option('rule_groups');
348
        if (null === $ruleGroupString || '' === $ruleGroupString) {
349
            // can be empty.
350
            return true;
351
        }
352
        $ruleGroupList = explode(',', $ruleGroupString);
353
        // @codeCoverageIgnoreStart
354
        if (0 === count($ruleGroupList)) {
355
            // can be empty.
356
            return true;
357
        }
358
        // @codeCoverageIgnoreEnd
359
        foreach ($ruleGroupList as $ruleGroupId) {
360
            $ruleGroup = $this->ruleGroupRepository->find((int) $ruleGroupId);
361
            if ($ruleGroup->active) {
362
                $this->ruleGroupSelection[] = $ruleGroup->id;
363
            }
364
            if (false === $ruleGroup->active) {
365
                $this->warn(sprintf('Will ignore inactive rule group #%d ("%s")', $ruleGroup->id, $ruleGroup->title));
366
            }
367
        }
368
369
        return true;
370
    }
371
372
    /**
373
     * @return bool
374
     */
375
    private function verifyInputRules(): bool
376
    {
377
        $ruleString = $this->option('rules');
378
        if (null === $ruleString || '' === $ruleString) {
379
            // can be empty.
380
            return true;
381
        }
382
        $ruleList = explode(',', $ruleString);
383
384
        // @codeCoverageIgnoreStart
385
        if (0 === count($ruleList)) {
386
            // can be empty.
387
388
            return true;
389
        }
390
        // @codeCoverageIgnoreEnd
391
392
        foreach ($ruleList as $ruleId) {
393
            $rule = $this->ruleRepository->find((int) $ruleId);
394
            if (null !== $rule && $rule->active) {
395
                $this->ruleSelection[] = $rule->id;
396
            }
397
        }
398
399
        return true;
400
    }
401
}
402