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.

RecurringRepository::getTransactionPaginator()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 22
rs 9.7333
c 0
b 0
f 0
cc 2
nc 2
nop 3
1
<?php
2
/**
3
 * RecurringRepository.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\Repositories\Recurring;
25
26
use Carbon\Carbon;
27
use FireflyIII\Exceptions\FireflyException;
28
use FireflyIII\Factory\RecurrenceFactory;
29
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
30
use FireflyIII\Models\Note;
31
use FireflyIII\Models\Preference;
32
use FireflyIII\Models\Recurrence;
33
use FireflyIII\Models\RecurrenceMeta;
34
use FireflyIII\Models\RecurrenceRepetition;
35
use FireflyIII\Models\RecurrenceTransaction;
36
use FireflyIII\Models\RecurrenceTransactionMeta;
37
use FireflyIII\Models\TransactionJournal;
38
use FireflyIII\Models\TransactionJournalMeta;
39
use FireflyIII\Services\Internal\Destroy\RecurrenceDestroyService;
40
use FireflyIII\Services\Internal\Update\RecurrenceUpdateService;
41
use FireflyIII\Support\Repositories\Recurring\CalculateRangeOccurrences;
42
use FireflyIII\Support\Repositories\Recurring\CalculateXOccurrences;
43
use FireflyIII\Support\Repositories\Recurring\CalculateXOccurrencesSince;
44
use FireflyIII\Support\Repositories\Recurring\FiltersWeekends;
45
use FireflyIII\User;
46
use Illuminate\Pagination\LengthAwarePaginator;
47
use Illuminate\Support\Collection;
48
use Log;
49
50
/**
51
 * Class RecurringRepository
52
 */
53
class RecurringRepository implements RecurringRepositoryInterface
54
{
55
    use CalculateRangeOccurrences, CalculateXOccurrences, CalculateXOccurrencesSince, FiltersWeekends;
0 ignored issues
show
introduced by
The trait FireflyIII\Support\Repos...g\CalculateXOccurrences requires some properties which are not provided by FireflyIII\Repositories\...ing\RecurringRepository: $daysInMonth, $dayOfWeekIso
Loading history...
introduced by
The trait FireflyIII\Support\Repos...culateXOccurrencesSince requires some properties which are not provided by FireflyIII\Repositories\...ing\RecurringRepository: $daysInMonth, $dayOfWeekIso
Loading history...
introduced by
The trait FireflyIII\Support\Repos...lculateRangeOccurrences requires some properties which are not provided by FireflyIII\Repositories\...ing\RecurringRepository: $daysInMonth, $dayOfWeekIso
Loading history...
introduced by
The trait FireflyIII\Support\Repos...curring\FiltersWeekends requires some properties which are not provided by FireflyIII\Repositories\...ing\RecurringRepository: $weekend, $dayOfWeekIso
Loading history...
56
    /** @var User */
57
    private $user;
58
59
    /**
60
     * Constructor.
61
     */
62
    public function __construct()
63
    {
64
        if ('testing' === config('app.env')) {
65
            Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
66
        }
67
    }
68
69
    /**
70
     * Destroy a recurring transaction.
71
     *
72
     * @param Recurrence $recurrence
73
     */
74
    public function destroy(Recurrence $recurrence): void
75
    {
76
        /** @var RecurrenceDestroyService $service */
77
        $service = app(RecurrenceDestroyService::class);
78
        $service->destroy($recurrence);
79
    }
80
81
    /**
82
     * Returns all of the user's recurring transactions.
83
     *
84
     * @return Collection
85
     */
86
    public function get(): Collection
87
    {
88
        return $this->user->recurrences()
89
                          ->with(['TransactionCurrency', 'TransactionType', 'RecurrenceRepetitions', 'RecurrenceTransactions'])
90
                          ->orderBy('active', 'DESC')
91
                          ->orderBy('transaction_type_id', 'ASC')
92
                          ->orderBy('title', 'ASC')
93
                          ->get();
94
    }
95
96
    /**
97
     * Get ALL recurring transactions.
98
     *
99
     * @return Collection
100
     */
101
    public function getAll(): Collection
102
    {
103
        // grab ALL recurring transactions:
104
        return Recurrence
105
            ::with(['TransactionCurrency', 'TransactionType', 'RecurrenceRepetitions', 'RecurrenceTransactions'])
106
            ->orderBy('active', 'DESC')
107
            ->orderBy('title', 'ASC')
108
            ->get();
109
    }
110
111
    /**
112
     * Get the budget ID from a recurring transaction transaction.
113
     *
114
     * @param RecurrenceTransaction $recTransaction
115
     *
116
     * @return null|int
117
     */
118
    public function getBudget(RecurrenceTransaction $recTransaction): ?int
119
    {
120
        $return = 0;
121
        /** @var RecurrenceTransactionMeta $meta */
122
        foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
123
            if ('budget_id' === $meta->name) {
124
                $return = (int)$meta->value;
125
            }
126
        }
127
128
        return 0 === $return ? null : $return;
129
    }
130
131
    /**
132
     * Get the category from a recurring transaction transaction.
133
     *
134
     * @param RecurrenceTransaction $recTransaction
135
     *
136
     * @return null|string
137
     */
138
    public function getCategory(RecurrenceTransaction $recTransaction): ?string
139
    {
140
        $return = '';
141
        /** @var RecurrenceTransactionMeta $meta */
142
        foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
143
            if ('category_name' === $meta->name) {
144
                $return = (string)$meta->value;
145
            }
146
        }
147
148
        return '' === $return ? null : $return;
149
    }
150
151
    /**
152
     * Returns the journals created for this recurrence, possibly limited by time.
153
     *
154
     * @param Recurrence  $recurrence
155
     * @param Carbon|null $start
156
     * @param Carbon|null $end
157
     *
158
     * @return int
159
     */
160
    public function getJournalCount(Recurrence $recurrence, Carbon $start = null, Carbon $end = null): int
161
    {
162
        $query = TransactionJournal
163
            ::leftJoin('journal_meta', 'journal_meta.transaction_journal_id', '=', 'transaction_journals.id')
164
            ->where('transaction_journals.user_id', $recurrence->user_id)
165
            ->whereNull('transaction_journals.deleted_at')
166
            ->where('journal_meta.name', 'recurrence_id')
167
            ->where('journal_meta.data', '"' . $recurrence->id . '"');
168
        if (null !== $start) {
169
            $query->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00'));
170
        }
171
        if (null !== $end) {
172
            $query->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00'));
173
        }
174
175
        return $query->get(['transaction_journals.*'])->count();
176
    }
177
178
    /**
179
     * Get journal ID's for journals created by this recurring transaction.
180
     *
181
     * @param Recurrence $recurrence
182
     *
183
     * @return array
184
     */
185
    public function getJournalIds(Recurrence $recurrence): array
186
    {
187
        return TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
188
                                     ->where('transaction_journals.user_id', $this->user->id)
189
                                     ->where('journal_meta.name', '=', 'recurrence_id')
190
                                     ->where('journal_meta.data', '=', json_encode((string)$recurrence->id))
191
                                     ->get(['journal_meta.transaction_journal_id'])->pluck('transaction_journal_id')->toArray();
192
    }
193
194
    /**
195
     * Get the notes.
196
     *
197
     * @param Recurrence $recurrence
198
     *
199
     * @return string
200
     */
201
    public function getNoteText(Recurrence $recurrence): string
202
    {
203
        /** @var Note $note */
204
        $note = $recurrence->notes()->first();
205
        if (null !== $note) {
206
            return (string)$note->text;
207
        }
208
209
        return '';
210
    }
211
212
    /**
213
     * Generate events in the date range.
214
     *
215
     * @param RecurrenceRepetition $repetition
216
     * @param Carbon               $start
217
     * @param Carbon               $end
218
     *
219
     * @return array
220
     *
221
     */
222
    public function getOccurrencesInRange(RecurrenceRepetition $repetition, Carbon $start, Carbon $end): array
223
    {
224
        $occurrences = [];
225
        $mutator     = clone $start;
226
        $mutator->startOfDay();
227
        $skipMod = $repetition->repetition_skip + 1;
228
        Log::debug(sprintf('Calculating occurrences for rep type "%s"', $repetition->repetition_type));
229
        Log::debug(sprintf('Mutator is now: %s', $mutator->format('Y-m-d')));
230
231
        if ('daily' === $repetition->repetition_type) {
232
            $occurrences = $this->getDailyInRange($mutator, $end, $skipMod);
233
        }
234
        if ('weekly' === $repetition->repetition_type) {
235
            $occurrences = $this->getWeeklyInRange($mutator, $end, $skipMod, $repetition->repetition_moment);
236
        }
237
        if ('monthly' === $repetition->repetition_type) {
238
            $occurrences = $this->getMonthlyInRange($mutator, $end, $skipMod, $repetition->repetition_moment);
239
        }
240
        if ('ndom' === $repetition->repetition_type) {
241
            $occurrences = $this->getNdomInRange($mutator, $end, $skipMod, $repetition->repetition_moment);
242
        }
243
        if ('yearly' === $repetition->repetition_type) {
244
            $occurrences = $this->getYearlyInRange($mutator, $end, $skipMod, $repetition->repetition_moment);
245
        }
246
247
248
        // filter out all the weekend days:
249
        $occurrences = $this->filterWeekends($repetition, $occurrences);
250
251
        return $occurrences;
252
    }
253
254
    /**
255
     * @param RecurrenceTransaction $transaction
256
     *
257
     * @return int|null
258
     */
259
    public function getPiggyBank(RecurrenceTransaction $transaction): ?int
260
    {
261
        $meta = $transaction->recurrenceTransactionMeta;
262
        /** @var RecurrenceTransactionMeta $metaEntry */
263
        foreach ($meta as $metaEntry) {
264
            if ('piggy_bank_id' === $metaEntry->name) {
265
                return (int)$metaEntry->value;
266
            }
267
        }
268
269
        return null;
270
    }
271
272
    /**
273
     * Get the tags from the recurring transaction.
274
     *
275
     * @param RecurrenceTransaction $transaction
276
     *
277
     * @return array
278
     */
279
    public function getTags(RecurrenceTransaction $transaction): array
280
    {
281
        $tags = [];
282
        /** @var RecurrenceMeta $meta */
283
        foreach ($transaction->recurrenceTransactionMeta as $meta) {
284
            if ('tags' === $meta->name && '' !== $meta->value) {
285
                //$tags = explode(',', $meta->value);
286
                $tags = json_decode($meta->value, true, 512, JSON_THROW_ON_ERROR);
287
            }
288
        }
289
290
        return $tags;
291
    }
292
293
    /**
294
     * @param Recurrence $recurrence
295
     * @param int        $page
296
     * @param int        $pageSize
297
     *
298
     * @return LengthAwarePaginator
299
     */
300
    public function getTransactionPaginator(Recurrence $recurrence, int $page, int $pageSize): LengthAwarePaginator
301
    {
302
        $journalMeta = TransactionJournalMeta
303
            ::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
304
            ->whereNull('transaction_journals.deleted_at')
305
            ->where('transaction_journals.user_id', $this->user->id)
306
            ->where('name', 'recurrence_id')
307
            ->where('data', json_encode((string)$recurrence->id))
308
            ->get()->pluck('transaction_journal_id')->toArray();
309
        $search      = [];
310
        foreach ($journalMeta as $journalId) {
311
            $search[] = (int)$journalId;
312
        }
313
        /** @var GroupCollectorInterface $collector */
314
        $collector = app(GroupCollectorInterface::class);
315
316
        $collector->setUser($recurrence->user);
317
        $collector->withCategoryInformation()->withBudgetInformation()->setLimit($pageSize)->setPage($page)
318
                  ->withAccountInformation();
319
        $collector->setJournalIds($search);
320
321
        return $collector->getPaginatedGroups();
322
    }
323
324
    /**
325
     * @param Recurrence $recurrence
326
     *
327
     * @return Collection
328
     */
329
    public function getTransactions(Recurrence $recurrence): Collection
330
    {
331
        $journalMeta = TransactionJournalMeta
332
            ::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
333
            ->whereNull('transaction_journals.deleted_at')
334
            ->where('transaction_journals.user_id', $this->user->id)
335
            ->where('name', 'recurrence_id')
336
            ->where('data', json_encode((string)$recurrence->id))
337
            ->get()->pluck('transaction_journal_id')->toArray();
338
        $search      = [];
339
340
        foreach ($journalMeta as $journalId) {
341
            $search[] = (int)$journalId;
342
        }
343
        if (0 === count($search)) {
344
345
            return new Collection;
346
        }
347
348
        /** @var GroupCollectorInterface $collector */
349
        $collector = app(GroupCollectorInterface::class);
350
351
        $collector->setUser($recurrence->user);
352
        $collector->withCategoryInformation()->withBudgetInformation()->withAccountInformation();
353
        // filter on specific journals.
354
        $collector->setJournalIds($search);
355
356
        return $collector->getGroups();
357
    }
358
359
    /**
360
     * Calculate the next X iterations starting on the date given in $date.
361
     *
362
     * @param RecurrenceRepetition $repetition
363
     * @param Carbon               $date
364
     * @param int                  $count
365
     *
366
     * @return array
367
     *
368
     */
369
    public function getXOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count): array
370
    {
371
        $skipMod     = $repetition->repetition_skip + 1;
372
        $occurrences = [];
373
        if ('daily' === $repetition->repetition_type) {
374
            $occurrences = $this->getXDailyOccurrences($date, $count, $skipMod);
375
        }
376
        if ('weekly' === $repetition->repetition_type) {
377
            $occurrences = $this->getXWeeklyOccurrences($date, $count, $skipMod, $repetition->repetition_moment);
378
        }
379
        if ('monthly' === $repetition->repetition_type) {
380
            $occurrences = $this->getXMonthlyOccurrences($date, $count, $skipMod, $repetition->repetition_moment);
381
        }
382
        if ('ndom' === $repetition->repetition_type) {
383
            $occurrences = $this->getXNDomOccurrences($date, $count, $skipMod, $repetition->repetition_moment);
384
        }
385
        if ('yearly' === $repetition->repetition_type) {
386
            $occurrences = $this->getXYearlyOccurrences($date, $count, $skipMod, $repetition->repetition_moment);
387
        }
388
389
        // filter out all the weekend days:
390
        $occurrences = $this->filterWeekends($repetition, $occurrences);
391
392
        return $occurrences;
393
    }
394
395
    /**
396
     * Parse the repetition in a string that is user readable.
397
     *
398
     * @param RecurrenceRepetition $repetition
399
     *
400
     * @return string
401
     *
402
     */
403
    public function repetitionDescription(RecurrenceRepetition $repetition): string
404
    {
405
        Log::debug('Now in repetitionDescription()');
406
        /** @var Preference $pref */
407
        $pref     = app('preferences')->getForUser($this->user, 'language', config('firefly.default_language', 'en_US'));
408
        $language = $pref->data;
409
        if ('daily' === $repetition->repetition_type) {
410
            return (string)trans('firefly.recurring_daily', [], $language);
411
        }
412
        if ('weekly' === $repetition->repetition_type) {
413
414
            $dayOfWeek = trans(sprintf('config.dow_%s', $repetition->repetition_moment), [], $language);
415
            if ($repetition->repetition_skip > 0) {
416
                return (string)trans('firefly.recurring_weekly_skip', ['weekday' => $dayOfWeek, 'skip' => $repetition->repetition_skip + 1], $language);
417
            }
418
419
            return (string)trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek], $language);
420
        }
421
        if ('monthly' === $repetition->repetition_type) {
422
            if ($repetition->repetition_skip > 0) {
423
                return (string)trans(
424
                    'firefly.recurring_monthly_skip', ['dayOfMonth' => $repetition->repetition_moment, 'skip' => $repetition->repetition_skip + 1], $language
425
                );
426
            }
427
428
            return (string)trans(
429
                'firefly.recurring_monthly', ['dayOfMonth' => $repetition->repetition_moment, 'skip' => $repetition->repetition_skip - 1], $language
430
            );
431
        }
432
        if ('ndom' === $repetition->repetition_type) {
433
            $parts = explode(',', $repetition->repetition_moment);
434
            // first part is number of week, second is weekday.
435
            $dayOfWeek = trans(sprintf('config.dow_%s', $parts[1]), [], $language);
436
437
            return (string)trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $language);
438
        }
439
        if ('yearly' === $repetition->repetition_type) {
440
            //
441
            $today       = Carbon::now()->endOfYear();
442
            $repDate     = Carbon::createFromFormat('Y-m-d', $repetition->repetition_moment);
443
            $diffInYears = $today->diffInYears($repDate);
444
            $repDate->addYears($diffInYears); // technically not necessary.
445
            $string = $repDate->formatLocalized((string)trans('config.month_and_day_no_year'));
446
447
            return (string)trans('firefly.recurring_yearly', ['date' => $string], $language);
448
        }
449
450
        return '';
451
452
    }
453
454
    /**
455
     * Set user for in repository.
456
     *
457
     * @param User $user
458
     */
459
    public function setUser(User $user): void
460
    {
461
        $this->user = $user;
462
    }
463
464
    /**
465
     * @param array $data
466
     *
467
     * @return Recurrence
468
     * @throws FireflyException
469
     */
470
    public function store(array $data): Recurrence
471
    {
472
        /** @var RecurrenceFactory $factory */
473
        $factory = app(RecurrenceFactory::class);
474
        $factory->setUser($this->user);
475
        $result = $factory->create($data);
476
        if (null === $result) {
477
            throw new FireflyException($factory->getErrors()->first());
0 ignored issues
show
Bug introduced by
The method getErrors() does not exist on FireflyIII\Factory\RecurrenceFactory. ( Ignorable by Annotation )

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

477
            throw new FireflyException($factory->/** @scrutinizer ignore-call */ getErrors()->first());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
478
        }
479
480
        return $result;
481
    }
482
483
    /**
484
     * Update a recurring transaction.
485
     *
486
     * @param Recurrence $recurrence
487
     * @param array      $data
488
     *
489
     * @return Recurrence
490
     * @throws FireflyException
491
     */
492
    public function update(Recurrence $recurrence, array $data): Recurrence
493
    {
494
        /** @var RecurrenceUpdateService $service */
495
        $service = app(RecurrenceUpdateService::class);
496
497
        return $service->update($recurrence, $data);
498
    }
499
500
    /**
501
     * Calculate the next X iterations starting on the date given in $date.
502
     * Returns an array of Carbon objects.
503
     *
504
     * Only returns them of they are after $afterDate
505
     *
506
     * @param RecurrenceRepetition $repetition
507
     * @param Carbon               $date
508
     * @param Carbon               $afterDate
509
     * @param int                  $count
510
     *
511
     * @return array
512
     * @throws FireflyException
513
     */
514
    public function getXOccurrencesSince(RecurrenceRepetition $repetition, Carbon $date, Carbon $afterDate, int $count): array
515
    {
516
        Log::debug('Now in getXOccurrencesSince()');
517
        $skipMod     = $repetition->repetition_skip + 1;
518
        $occurrences = [];
519
        if ('daily' === $repetition->repetition_type) {
520
            $occurrences = $this->getXDailyOccurrencesSince($date, $afterDate, $count, $skipMod);
521
        }
522
        if ('weekly' === $repetition->repetition_type) {
523
            $occurrences = $this->getXWeeklyOccurrencesSince($date, $afterDate, $count, $skipMod, $repetition->repetition_moment);
524
        }
525
        if ('monthly' === $repetition->repetition_type) {
526
            $occurrences = $this->getXMonthlyOccurrencesSince($date, $afterDate, $count, $skipMod, $repetition->repetition_moment);
527
        }
528
        if ('ndom' === $repetition->repetition_type) {
529
            $occurrences = $this->getXNDomOccurrencesSince($date, $afterDate, $count, $skipMod, $repetition->repetition_moment);
530
        }
531
        if ('yearly' === $repetition->repetition_type) {
532
            $occurrences = $this->getXYearlyOccurrencesSince($date, $afterDate, $count, $skipMod, $repetition->repetition_moment);
533
        }
534
535
        // filter out all the weekend days:
536
        $occurrences = $this->filterWeekends($repetition, $occurrences);
537
538
        return $occurrences;
539
    }
540
}
541