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.
Passed
Push — master ( cf47da...53c71b )
by James
21:48 queued 09:49
created

app/Http/Controllers/Chart/AccountController.php (3 issues)

Labels
Severity
1
<?php
2
/**
3
 * AccountController.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\Http\Controllers\Chart;
24
25
use Carbon\Carbon;
26
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
27
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
28
use FireflyIII\Http\Controllers\Controller;
29
use FireflyIII\Models\Account;
30
use FireflyIII\Models\AccountType;
31
use FireflyIII\Models\Transaction;
32
use FireflyIII\Models\TransactionCurrency;
33
use FireflyIII\Models\TransactionType;
34
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
35
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
36
use FireflyIII\Support\CacheProperties;
37
use FireflyIII\Support\Http\Controllers\AugumentData;
38
use FireflyIII\Support\Http\Controllers\ChartGeneration;
39
use FireflyIII\Support\Http\Controllers\DateCalculation;
40
use Illuminate\Http\JsonResponse;
41
use Illuminate\Support\Collection;
42
use Log;
43
44
/**
45
 * Class AccountController.
46
 *
47
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
48
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
49
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
50
 */
51
class AccountController extends Controller
52
{
53
    use DateCalculation, AugumentData, ChartGeneration;
54
55
    /** @var GeneratorInterface Chart generation methods. */
56
    protected $generator;
57
58
    /** @var AccountRepositoryInterface Account repository. */
59
    private $accountRepository;
60
61
    /** @var CurrencyRepositoryInterface */
62
    private $currencyRepository;
63
64
    /**
65
     * AccountController constructor.
66
     */
67
    public function __construct()
68
    {
69
        parent::__construct();
70
71
        $this->middleware(
72
            function ($request, $next) {
73
                $this->generator          = app(GeneratorInterface::class);
74
                $this->accountRepository  = app(AccountRepositoryInterface::class);
75
                $this->currencyRepository = app(CurrencyRepositoryInterface::class);
76
77
                return $next($request);
78
            }
79
        );
80
81
82
    }
83
84
85
    /**
86
     * Shows the balances for all the user's expense accounts (on the front page).
87
     *
88
     * This chart is (multi) currency aware.
89
     *
90
     * @return JsonResponse
91
     */
92
    public function expenseAccounts(): JsonResponse
93
    {
94
        /** @var Carbon $start */
95
        $start = clone session('start', Carbon::now()->startOfMonth());
96
        /** @var Carbon $end */
97
        $end   = clone session('end', Carbon::now()->endOfMonth());
98
        $cache = new CacheProperties;
99
        $cache->addProperty($start);
100
        $cache->addProperty($end);
101
        $cache->addProperty('chart.account.expense-accounts');
102
        if ($cache->has()) {
103
            return response()->json($cache->get()); // @codeCoverageIgnore
104
        }
105
        $start->subDay();
106
107
        // prep some vars:
108
        $currencies = [];
109
        $chartData  = [];
110
        $tempData   = [];
111
112
        // grab all accounts and names
113
        $accounts     = $this->accountRepository->getAccountsByType([AccountType::EXPENSE]);
114
        $accountNames = $this->extractNames($accounts);
115
116
        // grab all balances
117
        $startBalances = app('steam')->balancesPerCurrencyByAccounts($accounts, $start);
118
        $endBalances   = app('steam')->balancesPerCurrencyByAccounts($accounts, $end);
119
120
        // loop the end balances. This is an array for each account ($expenses)
121
        foreach ($endBalances as $accountId => $expenses) {
122
            $accountId = (int)$accountId;
123
            // loop each expense entry (each entry can be a different currency).
124
            foreach ($expenses as $currencyId => $endAmount) {
125
                $currencyId = (int)$currencyId;
126
127
                // see if there is an accompanying start amount.
128
                // grab the difference and find the currency.
129
                $startAmount             = $startBalances[$accountId][$currencyId] ?? '0';
130
                $diff                    = bcsub($endAmount, $startAmount);
131
                $currencies[$currencyId] = $currencies[$currencyId] ?? $this->currencyRepository->findNull($currencyId);
132
                if (0 !== bccomp($diff, '0')) {
133
                    // store the values in a temporary array.
134
                    $tempData[] = [
135
                        'name'        => $accountNames[$accountId],
136
                        'difference'  => $diff,
137
                        'diff_float'  => (float)$diff,
138
                        'currency_id' => $currencyId,
139
                    ];
140
                }
141
            }
142
        }
143
144
        // sort temp array by amount.
145
        $amounts = array_column($tempData, 'diff_float');
146
        array_multisort($amounts, SORT_DESC, $tempData);
147
148
        // loop all found currencies and build the data array for the chart.
149
        /**
150
         * @var int                 $currencyId
151
         * @var TransactionCurrency $currency
152
         */
153
        foreach ($currencies as $currencyId => $currency) {
154
            $dataSet
155
                                    = [
156
                'label'           => (string)trans('firefly.spent'),
157
                'type'            => 'bar',
158
                'currency_symbol' => $currency->symbol,
159
                'entries'         => $this->expandNames($tempData),
160
            ];
161
            $chartData[$currencyId] = $dataSet;
162
        }
163
164
        // loop temp data and place data in correct array:
165
        foreach ($tempData as $entry) {
166
            $currencyId                               = $entry['currency_id'];
167
            $name                                     = $entry['name'];
168
            $chartData[$currencyId]['entries'][$name] = $entry['difference'];
169
        }
170
171
        $data = $this->generator->multiSet($chartData);
172
        $cache->store($data);
173
174
        return response()->json($data);
175
    }
176
177
178
    /**
179
     * Expenses per budget, as shown on account overview.
180
     *
181
     * @param Account $account
182
     * @param Carbon  $start
183
     * @param Carbon  $end
184
     *
185
     * @return JsonResponse
186
     */
187
    public function expenseBudget(Account $account, Carbon $start, Carbon $end): JsonResponse
188
    {
189
        $cache = new CacheProperties;
190
        $cache->addProperty($account->id);
191
        $cache->addProperty($start);
192
        $cache->addProperty($end);
193
        $cache->addProperty('chart.account.expense-budget');
194
        if ($cache->has()) {
195
            return response()->json($cache->get()); // @codeCoverageIgnore
196
        }
197
        /** @var TransactionCollectorInterface $collector */
198
        $collector = app(TransactionCollectorInterface::class);
199
        $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withBudgetInformation()->setTypes([TransactionType::WITHDRAWAL]);
200
        $transactions = $collector->getTransactions();
201
        $chartData    = [];
202
        $result       = [];
203
        $budgetIds    = [];
204
        /** @var Transaction $transaction */
205
        foreach ($transactions as $transaction) {
206
            $jrnlBudgetId  = (int)$transaction->transaction_journal_budget_id;
207
            $transBudgetId = (int)$transaction->transaction_budget_id;
208
            $currencyName  = $transaction->transaction_currency_name;
209
            $budgetId      = max($jrnlBudgetId, $transBudgetId);
210
            $combi         = $budgetId . $currencyName;
211
            $budgetIds[]   = $budgetId;
212
            if (!isset($result[$combi])) {
213
                $result[$combi] = [
214
                    'total'           => '0',
215
                    'budget_id'       => $budgetId,
216
                    'currency'        => $currencyName,
217
                    'currency_symbol' => $transaction->transaction_currency_symbol,
218
                ];
219
            }
220
            $result[$combi]['total'] = bcadd($transaction->transaction_amount, $result[$combi]['total']);
221
        }
222
223
        $names = $this->getBudgetNames($budgetIds);
224
225
        foreach ($result as $row) {
226
            $budgetId          = $row['budget_id'];
227
            $name              = $names[$budgetId];
228
            $label             = (string)trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency']]);
229
            $chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol']];
230
        }
231
232
        $data = $this->generator->multiCurrencyPieChart($chartData);
233
        $cache->store($data);
234
235
        return response()->json($data);
236
    }
237
238
    /**
239
     * Expenses per budget for all time, as shown on account overview.
240
     *
241
     * @param AccountRepositoryInterface $repository
242
     * @param Account                    $account
243
     *
244
     * @return JsonResponse
245
     */
246
    public function expenseBudgetAll(AccountRepositoryInterface $repository, Account $account): JsonResponse
247
    {
248
        $start = $repository->oldestJournalDate($account) ?? Carbon::now()->startOfMonth();
249
        $end   = Carbon::now();
250
251
        return $this->expenseBudget($account, $start, $end);
252
    }
253
254
255
    /**
256
     * Expenses per category for one single account.
257
     *
258
     * @param Account $account
259
     * @param Carbon  $start
260
     * @param Carbon  $end
261
     *
262
     * @return JsonResponse
263
     */
264
    public function expenseCategory(Account $account, Carbon $start, Carbon $end): JsonResponse
265
    {
266
        $cache = new CacheProperties;
267
        $cache->addProperty($account->id);
268
        $cache->addProperty($start);
269
        $cache->addProperty($end);
270
        $cache->addProperty('chart.account.expense-category');
271
        if ($cache->has()) {
272
            return response()->json($cache->get()); // @codeCoverageIgnore
273
        }
274
275
        /** @var TransactionCollectorInterface $collector */
276
        $collector = app(TransactionCollectorInterface::class);
277
        $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::WITHDRAWAL]);
278
        $transactions = $collector->getTransactions();
279
        $result       = [];
280
        $chartData    = [];
281
        $categoryIds  = [];
282
283
        /** @var Transaction $transaction */
284
        foreach ($transactions as $transaction) {
285
            $jrnlCatId     = (int)$transaction->transaction_journal_category_id;
286
            $transCatId    = (int)$transaction->transaction_category_id;
287
            $currencyName  = $transaction->transaction_currency_name;
288
            $categoryId    = max($jrnlCatId, $transCatId);
289
            $combi         = $categoryId . $currencyName;
290
            $categoryIds[] = $categoryId;
291
            if (!isset($result[$combi])) {
292
                $result[$combi] = [
293
                    'total'           => '0',
294
                    'category_id'     => $categoryId,
295
                    'currency'        => $currencyName,
296
                    'currency_symbol' => $transaction->transaction_currency_symbol,
297
                ];
298
            }
299
            $result[$combi]['total'] = bcadd($transaction->transaction_amount, $result[$combi]['total']);
300
        }
301
302
        $names = $this->getCategoryNames($categoryIds);
303
304
        foreach ($result as $row) {
305
            $categoryId        = $row['category_id'];
306
            $name              = $names[$categoryId] ?? '(unknown)';
307
            $label             = (string)trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency']]);
308
            $chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol']];
309
        }
310
311
        $data = $this->generator->multiCurrencyPieChart($chartData);
312
        $cache->store($data);
313
314
        return response()->json($data);
315
    }
316
317
    /**
318
     * Expenses grouped by category for account.
319
     *
320
     * @param AccountRepositoryInterface $repository
321
     * @param Account                    $account
322
     *
323
     * @return JsonResponse
324
     */
325
    public function expenseCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse
326
    {
327
        $start = $repository->oldestJournalDate($account) ?? Carbon::now()->startOfMonth();
328
        $end   = Carbon::now();
329
330
        return $this->expenseCategory($account, $start, $end);
331
    }
332
333
334
    /**
335
     * Shows the balances for all the user's frontpage accounts.
336
     *
337
     * @param AccountRepositoryInterface $repository
338
     *
339
     * @return JsonResponse
340
     */
341
    public function frontpage(AccountRepositoryInterface $repository): JsonResponse
342
    {
343
        $start      = clone session('start', Carbon::now()->startOfMonth());
344
        $end        = clone session('end', Carbon::now()->endOfMonth());
345
        $defaultSet = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray();
346
        Log::debug('Default set is ', $defaultSet);
347
        $frontPage = app('preferences')->get('frontPageAccounts', $defaultSet);
348
349
350
        Log::debug('Frontpage preference set is ', $frontPage->data);
0 ignored issues
show
$frontPage->data of type string is incompatible with the type array expected by parameter $context of Illuminate\Support\Facades\Log::debug(). ( Ignorable by Annotation )

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

350
        Log::debug('Frontpage preference set is ', /** @scrutinizer ignore-type */ $frontPage->data);
Loading history...
351
        if (0 === \count($frontPage->data)) {
0 ignored issues
show
$frontPage->data of type string is incompatible with the type Countable|array expected by parameter $var of count(). ( Ignorable by Annotation )

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

351
        if (0 === \count(/** @scrutinizer ignore-type */ $frontPage->data)) {
Loading history...
352
            $frontPage->data = $defaultSet;
353
            Log::debug('frontpage set is empty!');
354
            $frontPage->save();
355
        }
356
        $accounts = $repository->getAccountsById($frontPage->data);
0 ignored issues
show
$frontPage->data of type string is incompatible with the type array expected by parameter $accountIds of FireflyIII\Repositories\...face::getAccountsById(). ( Ignorable by Annotation )

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

356
        $accounts = $repository->getAccountsById(/** @scrutinizer ignore-type */ $frontPage->data);
Loading history...
357
358
        return response()->json($this->accountBalanceChart($accounts, $start, $end));
359
    }
360
361
362
    /**
363
     * Shows all income per account for each category.
364
     *
365
     * @param Account $account
366
     * @param Carbon  $start
367
     * @param Carbon  $end
368
     *
369
     * @return JsonResponse
370
     */
371
    public function incomeCategory(Account $account, Carbon $start, Carbon $end): JsonResponse
372
    {
373
        $cache = new CacheProperties;
374
        $cache->addProperty($account->id);
375
        $cache->addProperty($start);
376
        $cache->addProperty($end);
377
        $cache->addProperty('chart.account.income-category');
378
        if ($cache->has()) {
379
            return response()->json($cache->get()); // @codeCoverageIgnore
380
        }
381
382
        // grab all journals:
383
        /** @var TransactionCollectorInterface $collector */
384
        $collector = app(TransactionCollectorInterface::class);
385
        $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::DEPOSIT]);
386
        $transactions = $collector->getTransactions();
387
        $result       = [];
388
        $chartData    = [];
389
        $categoryIds  = [];
390
        /** @var Transaction $transaction */
391
        foreach ($transactions as $transaction) {
392
            $jrnlCatId     = (int)$transaction->transaction_journal_category_id;
393
            $transCatId    = (int)$transaction->transaction_category_id;
394
            $categoryId    = max($jrnlCatId, $transCatId);
395
            $currencyName  = $transaction->transaction_currency_name;
396
            $combi         = $categoryId . $currencyName;
397
            $categoryIds[] = $categoryId;
398
            if (!isset($result[$combi])) {
399
                $result[$combi] = [
400
                    'total'           => '0',
401
                    'category_id'     => $categoryId,
402
                    'currency'        => $currencyName,
403
                    'currency_symbol' => $transaction->transaction_currency_symbol,
404
                ];
405
            }
406
            $result[$combi]['total'] = bcadd($transaction->transaction_amount, $result[$combi]['total']);
407
        }
408
409
        $names = $this->getCategoryNames($categoryIds);
410
        foreach ($result as $row) {
411
            $categoryId        = $row['category_id'];
412
            $name              = $names[$categoryId] ?? '(unknown)';
413
            $label             = (string)trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency']]);
414
            $chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol']];
415
        }
416
        $data = $this->generator->multiCurrencyPieChart($chartData);
417
        $cache->store($data);
418
419
        return response()->json($data);
420
    }
421
422
    /**
423
     * Shows the income grouped by category for an account, in all time.
424
     *
425
     * @param AccountRepositoryInterface $repository
426
     * @param Account                    $account
427
     *
428
     * @return JsonResponse
429
     */
430
    public function incomeCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse
431
    {
432
        $start = $repository->oldestJournalDate($account) ?? Carbon::now()->startOfMonth();
433
        $end   = Carbon::now();
434
435
        return $this->incomeCategory($account, $start, $end);
436
    }
437
438
439
    /**
440
     * Shows overview of account during a single period.
441
     *
442
     * TODO this chart is not multi-currency aware.
443
     *
444
     * @param Account $account
445
     * @param Carbon  $start
446
     *
447
     * @param Carbon  $end
448
     *
449
     * @return JsonResponse
450
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
451
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
452
     */
453
    public function period(Account $account, Carbon $start, Carbon $end): JsonResponse
454
    {
455
        $cache = new CacheProperties;
456
        $cache->addProperty('chart.account.period');
457
        $cache->addProperty($start);
458
        $cache->addProperty($end);
459
        $cache->addProperty($account->id);
460
        if ($cache->has()) {
461
            return response()->json($cache->get()); // @codeCoverageIgnore
462
        }
463
464
        $step      = $this->calculateStep($start, $end);
465
        $chartData = [];
466
        $current   = clone $start;
467
        switch ($step) {
468
            case '1D':
469
                $format   = (string)trans('config.month_and_day');
470
                $range    = app('steam')->balanceInRange($account, $start, $end);
471
                $previous = array_values($range)[0];
472
                while ($end >= $current) {
473
                    $theDate           = $current->format('Y-m-d');
474
                    $balance           = $range[$theDate] ?? $previous;
475
                    $label             = $current->formatLocalized($format);
476
                    $chartData[$label] = (float)$balance;
477
                    $previous          = $balance;
478
                    $current->addDay();
479
                }
480
                break;
481
            // @codeCoverageIgnoreStart
482
            case '1W':
483
            case '1M':
484
            case '1Y':
485
                while ($end >= $current) {
486
                    $balance           = (float)app('steam')->balance($account, $current);
487
                    $label             = app('navigation')->periodShow($current, $step);
488
                    $chartData[$label] = $balance;
489
                    $current           = app('navigation')->addPeriod($current, $step, 0);
490
                }
491
                break;
492
            // @codeCoverageIgnoreEnd
493
        }
494
        $data = $this->generator->singleSet($account->name, $chartData);
495
        $cache->store($data);
496
497
        return response()->json($data);
498
    }
499
500
    /**
501
     * Shows the balances for a given set of dates and accounts.
502
     *
503
     * TODO this chart is not multi-currency aware.
504
     *
505
     * @param Carbon     $start
506
     * @param Carbon     $end
507
     * @param Collection $accounts
508
     *
509
     * @return JsonResponse
510
     */
511
    public function report(Collection $accounts, Carbon $start, Carbon $end): JsonResponse
512
    {
513
        return response()->json($this->accountBalanceChart($accounts, $start, $end));
514
    }
515
516
517
    /**
518
     * Shows the balances for all the user's revenue accounts.
519
     *
520
     * This chart is multi-currency aware.
521
     *
522
     * @return JsonResponse
523
     */
524
    public function revenueAccounts(): JsonResponse
525
    {
526
        /** @var Carbon $start */
527
        $start = clone session('start', Carbon::now()->startOfMonth());
528
        /** @var Carbon $end */
529
        $end   = clone session('end', Carbon::now()->endOfMonth());
530
        $cache = new CacheProperties;
531
        $cache->addProperty($start);
532
        $cache->addProperty($end);
533
        $cache->addProperty('chart.account.revenue-accounts');
534
        if ($cache->has()) {
535
            return response()->json($cache->get()); // @codeCoverageIgnore
536
        }
537
        $start->subDay();
538
539
        // prep some vars:
540
        $currencies = [];
541
        $chartData  = [];
542
        $tempData   = [];
543
544
        // grab all accounts and names
545
        $accounts     = $this->accountRepository->getAccountsByType([AccountType::REVENUE]);
546
        $accountNames = $this->extractNames($accounts);
547
548
        // grab all balances
549
        $startBalances = app('steam')->balancesPerCurrencyByAccounts($accounts, $start);
550
        $endBalances   = app('steam')->balancesPerCurrencyByAccounts($accounts, $end);
551
552
        // loop the end balances. This is an array for each account ($expenses)
553
        foreach ($endBalances as $accountId => $expenses) {
554
            $accountId = (int)$accountId;
555
            // loop each expense entry (each entry can be a different currency).
556
            foreach ($expenses as $currencyId => $endAmount) {
557
                $currencyId = (int)$currencyId;
558
559
                // see if there is an accompanying start amount.
560
                // grab the difference and find the currency.
561
                $startAmount             = $startBalances[$accountId][$currencyId] ?? '0';
562
                $diff                    = bcsub($endAmount, $startAmount);
563
                $currencies[$currencyId] = $currencies[$currencyId] ?? $this->currencyRepository->findNull($currencyId);
564
                if (0 !== bccomp($diff, '0')) {
565
                    // store the values in a temporary array.
566
                    $tempData[] = [
567
                        'name'        => $accountNames[$accountId],
568
                        'difference'  => $diff,
569
                        'diff_float'  => (float)$diff,
570
                        'currency_id' => $currencyId,
571
                    ];
572
                }
573
            }
574
        }
575
576
        // sort temp array by amount.
577
        $amounts = array_column($tempData, 'diff_float');
578
        array_multisort($amounts, SORT_DESC, $tempData);
579
580
        // loop all found currencies and build the data array for the chart.
581
        /**
582
         * @var int                 $currencyId
583
         * @var TransactionCurrency $currency
584
         */
585
        foreach ($currencies as $currencyId => $currency) {
586
            $dataSet
587
                                    = [
588
                'label'           => (string)trans('firefly.earned'),
589
                'type'            => 'bar',
590
                'currency_symbol' => $currency->symbol,
591
                'entries'         => $this->expandNames($tempData),
592
            ];
593
            $chartData[$currencyId] = $dataSet;
594
        }
595
596
        // loop temp data and place data in correct array:
597
        foreach ($tempData as $entry) {
598
            $currencyId                               = $entry['currency_id'];
599
            $name                                     = $entry['name'];
600
            $chartData[$currencyId]['entries'][$name] = bcmul($entry['difference'], '-1');
601
        }
602
603
        $data = $this->generator->multiSet($chartData);
604
        $cache->store($data);
605
606
        return response()->json($data);
607
    }
608
}
609