Compare commits

..

99 Commits

Author SHA1 Message Date
github-actions[bot]
55c762d55a Merge pull request #11558 from firefly-iii/release-1768850616
🤖 Automatically merge the PR into the develop branch.
2026-01-19 20:23:44 +01:00
JC5
734df18f4e 🤖 Auto commit for release 'develop' on 2026-01-19 2026-01-19 20:23:36 +01:00
James Cole
30205d828a Merge pull request #11552 from firefly-iii/dependabot/npm_and_yarn/develop/jquery-4.0.0
Bump jquery from 3.7.1 to 4.0.0
2026-01-19 20:19:30 +01:00
mergify[bot]
9c0eb8a028 Merge branch 'develop' into dependabot/npm_and_yarn/develop/jquery-4.0.0 2026-01-19 19:18:01 +00:00
James Cole
103a6d1b15 Expand events for #11544 2026-01-19 20:17:16 +01:00
mergify[bot]
99403ab9f6 Merge branch 'develop' into dependabot/npm_and_yarn/develop/jquery-4.0.0 2026-01-19 19:11:10 +00:00
James Cole
b9e5851e38 Remove unused class #11411 2026-01-19 20:09:53 +01:00
mergify[bot]
eab18ed2f2 Merge branch 'develop' into dependabot/npm_and_yarn/develop/jquery-4.0.0 2026-01-19 19:08:53 +00:00
James Cole
67d18e4b62 Clean up latest MFA handler #11455 2026-01-19 20:08:15 +01:00
mergify[bot]
71774905b0 Merge branch 'develop' into dependabot/npm_and_yarn/develop/jquery-4.0.0 2026-01-19 19:02:47 +00:00
James Cole
48f886d17c Replace multiple failures alert #11544 2026-01-19 20:02:20 +01:00
James Cole
e28df272b5 Replace multiple failures alert #11544 2026-01-19 20:02:08 +01:00
mergify[bot]
95e98cfeca Merge branch 'develop' into dependabot/npm_and_yarn/develop/jquery-4.0.0 2026-01-19 18:57:47 +00:00
James Cole
095d31fbe1 Replace multiple failures alert #11544 2026-01-19 19:57:09 +01:00
mergify[bot]
27044aeecf Merge branch 'develop' into dependabot/npm_and_yarn/develop/jquery-4.0.0 2026-01-19 18:50:15 +00:00
James Cole
8bd7752e2b Clean up more MFA event handlers #11544 2026-01-19 19:49:50 +01:00
James Cole
ee24d55554 Clean up more MFA event handlers #11544 2026-01-19 19:49:34 +01:00
mergify[bot]
740a14972a Merge branch 'develop' into dependabot/npm_and_yarn/develop/jquery-4.0.0 2026-01-19 18:43:44 +00:00
James Cole
c6667c2293 Continued. #11544 2026-01-19 19:43:23 +01:00
James Cole
8327a3d670 Enabled MFA notifications #11544 2026-01-19 19:43:06 +01:00
mergify[bot]
5534bedde0 Merge branch 'develop' into dependabot/npm_and_yarn/develop/jquery-4.0.0 2026-01-19 18:37:02 +00:00
James Cole
83fe7f6f6d MFA disabled #11544 2026-01-19 19:36:21 +01:00
mergify[bot]
e41dbdc3a7 Merge branch 'develop' into dependabot/npm_and_yarn/develop/jquery-4.0.0 2026-01-19 18:33:03 +00:00
James Cole
670f98eb66 Merge branch 'main' into develop 2026-01-19 19:32:21 +01:00
mergify[bot]
7e02bee90b Merge branch 'develop' into dependabot/npm_and_yarn/develop/jquery-4.0.0 2026-01-19 06:56:31 +00:00
github-actions[bot]
be6801654f Merge pull request #11555 from firefly-iii/release-1768805748
🤖 Automatically merge the PR into the develop branch.
2026-01-19 07:55:56 +01:00
JC5
93fe1f5558 🤖 Auto commit for release 'develop' on 2026-01-19 2026-01-19 07:55:48 +01:00
James Cole
d12fd40a57 Merge pull request #11554 from firefly-iii/JC5-patch-2
Update PHP version setup in release workflow
2026-01-19 07:52:00 +01:00
James Cole
4405b2ac8f Update PHP version setup in release workflow
Set default PHP version to 8.4 if not provided.

Signed-off-by: James Cole <james@firefly-iii.org>
2026-01-19 07:51:49 +01:00
mergify[bot]
bbbc8492db Merge branch 'develop' into dependabot/npm_and_yarn/develop/jquery-4.0.0 2026-01-19 06:03:13 +00:00
James Cole
ba5b6ff694 Merge pull request #11550 from firefly-iii/dependabot/composer/develop/larastan/larastan-3.9.0 2026-01-19 07:02:34 +01:00
dependabot[bot]
edb8f811af Bump jquery from 3.7.1 to 4.0.0
Bumps [jquery](https://github.com/jquery/jquery) from 3.7.1 to 4.0.0.
- [Release notes](https://github.com/jquery/jquery/releases)
- [Changelog](https://github.com/jquery/jquery/blob/main/changelog.md)
- [Commits](https://github.com/jquery/jquery/compare/3.7.1...4.0.0)

---
updated-dependencies:
- dependency-name: jquery
  dependency-version: 4.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 04:09:35 +00:00
dependabot[bot]
492aec0652 Bump larastan/larastan from 3.8.1 to 3.9.0
Bumps [larastan/larastan](https://github.com/larastan/larastan) from 3.8.1 to 3.9.0.
- [Release notes](https://github.com/larastan/larastan/releases)
- [Changelog](https://github.com/larastan/larastan/blob/3.x/RELEASE.md)
- [Commits](https://github.com/larastan/larastan/compare/v3.8.1...v3.9.0)

---
updated-dependencies:
- dependency-name: larastan/larastan
  dependency-version: 3.9.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 04:09:13 +00:00
James Cole
c81bb44552 Fix #11546 2026-01-18 13:32:05 +01:00
James Cole
0431c95b49 Move rule actions for rule events. #11544 2026-01-18 12:13:11 +01:00
James Cole
6408db5414 Remove unused class #11544 2026-01-18 12:08:05 +01:00
James Cole
5de88a70f8 Remove piggy bank event handler and replace name event. #11544 2026-01-18 12:07:03 +01:00
James Cole
968da83c67 Migrate PiggyBankAmountIsChanged handler to listener #11544 2026-01-18 12:00:41 +01:00
James Cole
e77b4ee4a6 Rename to PiggyBankAmountIsChanged for #11544 2026-01-18 11:58:20 +01:00
James Cole
a36c775bd2 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-01-18 11:57:23 +01:00
James Cole
7773862eb9 Remove unused class. #11544 2026-01-18 11:57:17 +01:00
github-actions[bot]
ae107b1776 Merge pull request #11545 from firefly-iii/release-1768729993
🤖 Automatically merge the PR into the develop branch.
2026-01-18 10:53:20 +01:00
JC5
d1b167447b 🤖 Auto commit for release 'develop' on 2026-01-18 2026-01-18 10:53:13 +01:00
James Cole
a08ba43c23 For multiple subscriptions payment missed warning #11544 2026-01-18 10:17:50 +01:00
James Cole
64325fbce7 Remove unused class #11544 2026-01-18 10:12:39 +01:00
James Cole
aeac59e851 Subscription reminder for extension or ending. https://github.com/firefly-iii/firefly-iii/issues/11544 2026-01-18 10:11:26 +01:00
James Cole
151be7df8a Fix #11541 2026-01-18 06:07:50 +01:00
github-actions[bot]
040bd0f6e3 Merge pull request #11540 from firefly-iii/release-1768662669
🤖 Automatically merge the PR into the develop branch.
2026-01-17 16:11:15 +01:00
JC5
a859ca4e38 🤖 Auto commit for release 'develop' on 2026-01-17 2026-01-17 16:11:09 +01:00
James Cole
95c62d783a Fix https://github.com/orgs/firefly-iii/discussions/11538 2026-01-17 16:02:59 +01:00
github-actions[bot]
e7b67bc85e Merge pull request #11536 from firefly-iii/develop
🤖 Automatically merge the PR into the main branch.
2026-01-17 08:54:06 +01:00
github-actions[bot]
208f13ee75 Merge pull request #11535 from firefly-iii/release-1768636434
🤖 Automatically merge the PR into the develop branch.
2026-01-17 08:54:01 +01:00
JC5
437eecc1c9 🤖 Auto commit for release 'v6.4.16' on 2026-01-17 2026-01-17 08:53:54 +01:00
James Cole
1d34d81389 Remove double line from changelog. 2026-01-17 07:22:40 +01:00
github-actions[bot]
30844e99d4 Merge pull request #11534 from firefly-iii/release-1768630688
🤖 Automatically merge the PR into the develop branch.
2026-01-17 07:18:14 +01:00
JC5
d734449f63 🤖 Auto commit for release 'develop' on 2026-01-17 2026-01-17 07:18:08 +01:00
James Cole
69a9e3a198 Clean up account deletion and balance recalculation. 2026-01-17 07:12:14 +01:00
James Cole
3ad2f8c750 Expand changelog. 2026-01-17 07:03:29 +01:00
James Cole
2166839768 Fix #11475 2026-01-17 07:03:18 +01:00
James Cole
8ecb9d7774 Fix #11508 2026-01-17 06:58:33 +01:00
James Cole
8814fb0806 Fix https://github.com/orgs/firefly-iii/discussions/11509 2026-01-16 09:02:37 +01:00
James Cole
7481c8d4c0 Fix https://github.com/firefly-iii/firefly-iii/issues/11531 2026-01-16 07:34:20 +01:00
James Cole
1e618fbf6d Sort alphabetically https://github.com/orgs/firefly-iii/discussions/11524 2026-01-15 05:59:33 +01:00
James Cole
8b322dc903 Sort alphabetically https://github.com/orgs/firefly-iii/discussions/11524 2026-01-15 05:56:18 +01:00
github-actions[bot]
812b0e6940 Merge pull request #11520 from firefly-iii/release-1768367445
🤖 Automatically merge the PR into the develop branch.
2026-01-14 06:10:53 +01:00
JC5
9070856b9c 🤖 Auto commit for release 'develop' on 2026-01-14 2026-01-14 06:10:45 +01:00
James Cole
84082d38f2 Ignore AppServiceProvider 2026-01-14 06:05:34 +01:00
James Cole
822352827a Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-01-14 06:00:47 +01:00
github-actions[bot]
7c1b550c2c Merge pull request #11518 from firefly-iii/release-1768366825
🤖 Automatically merge the PR into the develop branch.
2026-01-14 06:00:33 +01:00
JC5
94a25d3137 🤖 Auto commit for release 'develop' on 2026-01-14 2026-01-14 06:00:25 +01:00
James Cole
4c95ab49f4 Cannot be static. 2026-01-14 05:56:08 +01:00
github-actions[bot]
be87a24b8f Merge pull request #11517 from firefly-iii/release-1768334732
🤖 Automatically merge the PR into the develop branch.
2026-01-13 21:05:40 +01:00
JC5
f6032d5502 🤖 Auto commit for release 'develop' on 2026-01-13 2026-01-13 21:05:32 +01:00
James Cole
7dcfb68fca Fix parse issue. 2026-01-13 20:59:54 +01:00
James Cole
419c796eac Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-01-13 20:54:44 +01:00
James Cole
1d5733adba Experimental command 2026-01-13 20:54:31 +01:00
github-actions[bot]
c589360ad9 Merge pull request #11516 from firefly-iii/release-1768333294
🤖 Automatically merge the PR into the develop branch.
2026-01-13 20:41:40 +01:00
JC5
d4687fb34f 🤖 Auto commit for release 'develop' on 2026-01-13 2026-01-13 20:41:34 +01:00
github-actions[bot]
9c4159ca3d Merge pull request #11515 from firefly-iii/release-1768333066
🤖 Automatically merge the PR into the develop branch.
2026-01-13 20:37:53 +01:00
JC5
93507c8b96 🤖 Auto commit for release 'develop' on 2026-01-13 2026-01-13 20:37:46 +01:00
James Cole
23d49a4194 Code clean up and restoration using Mago. 2026-01-13 20:37:41 +01:00
James Cole
858c44fbce Reset fix. 2026-01-13 20:32:42 +01:00
James Cole
95a6543a94 Fix config 2026-01-13 05:26:12 +01:00
James Cole
855d40cf2f Update lock file 2026-01-13 05:25:11 +01:00
James Cole
7d0fec6326 Mago config 2026-01-13 05:25:01 +01:00
James Cole
ebd7dca6a9 Do some code cleanup courtesy of Mago. 2026-01-13 05:14:58 +01:00
James Cole
1d41fc9845 Do some code cleanup courtesy of Mago. 2026-01-13 05:13:01 +01:00
github-actions[bot]
fe9ae9c810 Merge pull request #11505 from firefly-iii/release-1768204215
🤖 Automatically merge the PR into the develop branch.
2026-01-12 08:50:22 +01:00
JC5
84600c5208 🤖 Auto commit for release 'develop' on 2026-01-12 2026-01-12 08:50:15 +01:00
James Cole
f7e89cab0a Fix #11502 2026-01-11 17:18:31 +01:00
James Cole
8195630a6e Fix #11501 2026-01-11 10:12:48 +01:00
github-actions[bot]
d5bf80a0cb Merge pull request #11498 from firefly-iii/release-1768068342
🤖 Automatically merge the PR into the develop branch.
2026-01-10 19:05:49 +01:00
JC5
ef1c64096d 🤖 Auto commit for release 'develop' on 2026-01-10 2026-01-10 19:05:42 +01:00
James Cole
ef35eaffb4 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-01-10 18:28:21 +01:00
James Cole
869ee7c735 Add info 2026-01-10 18:28:10 +01:00
James Cole
6c534f01eb Catch null pointer. 2026-01-10 18:26:58 +01:00
github-actions[bot]
3a9d89b53d Merge pull request #11497 from firefly-iii/release-1768064652
🤖 Automatically merge the PR into the develop branch.
2026-01-10 18:04:21 +01:00
JC5
badff64cfd 🤖 Auto commit for release 'develop' on 2026-01-10 2026-01-10 18:04:12 +01:00
James Cole
abacfa212e Throw a 410. Don't report it. 2026-01-10 18:00:18 +01:00
135 changed files with 2421 additions and 1931 deletions

View File

@@ -40,7 +40,8 @@ return $config->setRules(
[
// rule sets
'@PHP83Migration' => true,
'@PHP8x3Migration' => true,
'@PHP8x4Migration' => true,
'@PhpCsFixer' => true,
'@PhpCsFixer:risky' => true,
'@PSR12' => true,

View File

@@ -14,7 +14,15 @@ SITE_OWNER=mail@example.com
# Change it to a string of exactly 32 chars or use something like `php artisan key:generate` to generate it.
# If you use Docker or similar, you can set this variable from a file by using APP_KEY_FILE
#
# Avoid the "#" character in your APP_KEY, it may break things.
# Try to avoid special characters like #, < and > in your app key. This string does not need full entropy
# When in doubt, follow the link below and pick one.
#
# https://www.random.org/strings/?num=5&len=32&digits=on&upperalpha=on&loweralpha=on&unique=on&format=html&rnd=new
#
# If you are a fancy linux nerd like me, use this command:
#
# head /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 32 && echo
#
#
APP_KEY=SomeRandomStringOf32CharsExactly

View File

@@ -42,7 +42,7 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ github.event.inputs.phpversion }}
php-version: ${{ github.event.inputs.phpversion || '8.4' }}
extensions: mbstring, intl, zip, bcmath
- name: Switch and pull
run: |

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\AccountTypeEnum;
@@ -37,6 +36,7 @@ use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\AccountFilter;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
/**
@@ -51,7 +51,7 @@ class AccountController extends Controller
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
/** @var array<int, string> */
private array $balanceTypes;
private array $balanceTypes;
private AccountRepositoryInterface $repository;
/**
@@ -60,16 +60,14 @@ class AccountController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(AccountRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(AccountRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
$this->balanceTypes = [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value];
}
@@ -83,24 +81,18 @@ class AccountController extends Controller
public function accounts(AutocompleteApiRequest $request): JsonResponse
{
Log::debug('Before All.');
[
'types' => $types,
'query' => $query,
'date' => $date,
'limit' => $limit,
]
= $request->attributes->all();
['types' => $types, 'query' => $query, 'date' => $date, 'limit' => $limit] = $request->attributes->all();
$date ??= today(config('app.timezone'));
// set date to end-of-day for account balance. so it is at $date 23:59:59
$date->endOfDay();
$return = [];
$timer = Timer::getInstance();
$return = [];
$timer = Timer::getInstance();
$timer->start(sprintf('AC accounts "%s"', $query));
$result = $this->repository->searchAccount((string)$query, $types, $limit);
$allBalances = Steam::accountsBalancesOptimized($result, $date, $this->primaryCurrency, $this->convertToPrimary);
$result = $this->repository->searchAccount((string) $query, $types, $limit);
$allBalances = Steam::accountsBalancesOptimized($result, $date, $this->primaryCurrency, $this->convertToPrimary);
/** @var Account $account */
foreach ($result as $account) {
@@ -118,17 +110,17 @@ class AccountController extends Controller
}
$return[] = [
'id' => (string)$account->id,
'id' => (string) $account->id,
'name' => $account->name,
'name_with_balance' => $nameWithBalance,
'active' => $account->active,
'type' => $account->accountType->type,
'currency_id' => (string)$useCurrency->id,
'currency_id' => (string) $useCurrency->id,
'currency_name' => $useCurrency->name,
'currency_code' => $useCurrency->code,
'currency_symbol' => $useCurrency->symbol,
'currency_decimal_places' => $useCurrency->decimal_places,
'account_currency_id' => (string)$currency->id,
'account_currency_id' => (string) $currency->id,
'account_currency_name' => $currency->name,
'account_currency_code' => $currency->code,
'account_currency_symbol' => $currency->symbol,
@@ -137,16 +129,13 @@ class AccountController extends Controller
}
// custom order.
usort(
$return,
static function (array $left, array $right): int {
$order = [AccountTypeEnum::ASSET->value, AccountTypeEnum::REVENUE->value, AccountTypeEnum::EXPENSE->value];
$posA = (int)array_search($left['type'], $order, true);
$posB = (int)array_search($right['type'], $order, true);
usort($return, static function (array $left, array $right): int {
$order = [AccountTypeEnum::ASSET->value, AccountTypeEnum::REVENUE->value, AccountTypeEnum::EXPENSE->value];
$posA = (int) array_search($left['type'], $order, true);
$posB = (int) array_search($right['type'], $order, true);
return $posA - $posB;
}
);
return $posA - $posB;
});
$timer->stop(sprintf('AC accounts "%s"', $query));
return response()->api($return);

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Bill;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class BillController
@@ -46,16 +46,14 @@ class BillController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(BillRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(BillRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -65,13 +63,7 @@ class BillController extends Controller
public function bills(AutocompleteApiRequest $request): JsonResponse
{
$result = $this->repository->searchBill($request->attributes->get('query'), $request->attributes->get('limit'));
$filtered = $result->map(
static fn (Bill $item): array => [
'id' => (string) $item->id,
'name' => $item->name,
'active' => $item->active,
]
);
$filtered = $result->map(static fn (Bill $item): array => ['id' => (string) $item->id, 'name' => $item->name, 'active' => $item->active]);
return response()->api($filtered->toArray());
}

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Budget;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class BudgetController
@@ -46,16 +46,14 @@ class BudgetController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(BudgetRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(BudgetRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -65,13 +63,7 @@ class BudgetController extends Controller
public function budgets(AutocompleteApiRequest $request): JsonResponse
{
$result = $this->repository->searchBudget($request->attributes->get('query'), $request->attributes->get('limit'));
$filtered = $result->map(
static fn (Budget $item): array => [
'id' => (string) $item->id,
'name' => $item->name,
'active' => $item->active,
]
);
$filtered = $result->map(static fn (Budget $item): array => ['id' => (string) $item->id, 'name' => $item->name, 'active' => $item->active]);
return response()->api($filtered->toArray());
}

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Category;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class CategoryController
@@ -46,16 +46,14 @@ class CategoryController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(CategoryRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(CategoryRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -65,12 +63,7 @@ class CategoryController extends Controller
public function categories(AutocompleteApiRequest $request): JsonResponse
{
$result = $this->repository->searchCategory($request->attributes->get('query'), $request->attributes->get('limit'));
$filtered = $result->map(
static fn (Category $item): array => [
'id' => (string) $item->id,
'name' => $item->name,
]
);
$filtered = $result->map(static fn (Category $item): array => ['id' => (string) $item->id, 'name' => $item->name]);
return response()->api($filtered->toArray());
}

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use Deprecated;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
@@ -33,6 +32,7 @@ use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class CurrencyController
@@ -48,16 +48,14 @@ class CurrencyController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(CurrencyRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(CurrencyRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
/**

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\ObjectGroup;
use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class ObjectGroupController
@@ -46,16 +46,14 @@ class ObjectGroupController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(ObjectGroupRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(ObjectGroupRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -69,11 +67,7 @@ class ObjectGroupController extends Controller
/** @var ObjectGroup $objectGroup */
foreach ($result as $objectGroup) {
$return[] = [
'id' => (string) $objectGroup->id,
'name' => $objectGroup->title,
'title' => $objectGroup->title,
];
$return[] = ['id' => (string) $objectGroup->id, 'name' => $objectGroup->title, 'title' => $objectGroup->title];
}
return response()->api($return);

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
@@ -34,13 +33,14 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class PiggyBankController
*/
class PiggyBankController extends Controller
{
private AccountRepositoryInterface $accountRepository;
private AccountRepositoryInterface $accountRepository;
private PiggyBankRepositoryInterface $piggyRepository;
protected array $acceptedRoles = [UserRoleEnum::READ_PIGGY_BANKS];
@@ -50,19 +50,17 @@ class PiggyBankController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->piggyRepository = app(PiggyBankRepositoryInterface::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->piggyRepository->setUser($this->user);
$this->piggyRepository->setUserGroup($this->userGroup);
$this->accountRepository->setUser($this->user);
$this->accountRepository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->piggyRepository = app(PiggyBankRepositoryInterface::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->piggyRepository->setUser($this->user);
$this->piggyRepository->setUserGroup($this->userGroup);
$this->accountRepository->setUser($this->user);
$this->accountRepository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
public function piggyBanks(AutocompleteApiRequest $request): JsonResponse
@@ -84,6 +82,7 @@ class PiggyBankController extends Controller
'currency_decimal_places' => $currency->decimal_places,
'object_group_id' => null === $objectGroup ? null : (string) $objectGroup->id,
'object_group_title' => $objectGroup?->title,
'object_group_order' => $objectGroup?->order,
];
}
@@ -108,7 +107,7 @@ class PiggyBankController extends Controller
'%s (%s / %s)',
$piggy->name,
Amount::formatAnything($currency, $currentAmount, false),
Amount::formatAnything($currency, $piggy->target_amount, false),
Amount::formatAnything($currency, $piggy->target_amount, false)
),
'currency_id' => (string) $currency->id,
'currency_name' => $currency->name,

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Recurrence;
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class RecurrenceController
@@ -46,16 +46,14 @@ class RecurrenceController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(RecurringRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(RecurringRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
public function recurring(AutocompleteApiRequest $request): JsonResponse

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Rule;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class RuleController
@@ -38,7 +38,7 @@ use Illuminate\Http\JsonResponse;
class RuleController extends Controller
{
private RuleRepositoryInterface $repository;
protected array $acceptedRoles = [UserRoleEnum::READ_RULES];
protected array $acceptedRoles = [UserRoleEnum::READ_RULES];
/**
* RuleController constructor.
@@ -46,16 +46,14 @@ class RuleController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(RuleRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(RuleRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
public function rules(AutocompleteApiRequest $request): JsonResponse
@@ -66,7 +64,7 @@ class RuleController extends Controller
/** @var Rule $rule */
foreach ($rules as $rule) {
$response[] = [
'id' => (string)$rule->id,
'id' => (string) $rule->id,
'name' => $rule->title,
'description' => $rule->description,
'active' => $rule->active,

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\RuleGroup;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class RuleGroupController
@@ -46,16 +46,14 @@ class RuleGroupController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(RuleGroupRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(RuleGroupRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
public function ruleGroups(AutocompleteApiRequest $request): JsonResponse

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class TagController
@@ -46,16 +46,14 @@ class TagController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(TagRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(TagRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
public function tags(AutocompleteApiRequest $request): JsonResponse
@@ -65,11 +63,7 @@ class TagController extends Controller
/** @var Tag $tag */
foreach ($result as $tag) {
$array[] = [
'id' => (string) $tag->id,
'name' => $tag->tag,
'tag' => $tag->tag,
];
$array[] = ['id' => (string) $tag->id, 'name' => $tag->tag, 'tag' => $tag->tag];
}
return response()->api($array);

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteTransactionApiRequest;
@@ -34,6 +33,7 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
/**
@@ -43,7 +43,7 @@ class TransactionController extends Controller
{
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
private TransactionGroupRepositoryInterface $groupRepository;
private JournalRepositoryInterface $repository;
private JournalRepositoryInterface $repository;
/**
* TransactionController constructor.
@@ -51,19 +51,17 @@ class TransactionController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(JournalRepositoryInterface::class);
$this->groupRepository = app(TransactionGroupRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->groupRepository->setUser($this->user);
$this->groupRepository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(JournalRepositoryInterface::class);
$this->groupRepository = app(TransactionGroupRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->groupRepository->setUser($this->user);
$this->groupRepository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
public function transactions(AutocompleteTransactionApiRequest $request): JsonResponse

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class TransactionTypeController
@@ -46,14 +46,12 @@ class TransactionTypeController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(TransactionTypeRepositoryInterface::class);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(TransactionTypeRepositoryInterface::class);
return $next($request);
}
);
return $next($request);
});
}
public function transactionTypes(AutocompleteApiRequest $request): JsonResponse
@@ -64,11 +62,7 @@ class TransactionTypeController extends Controller
/** @var TransactionType $type */
foreach ($types as $type) {
// different key for consistency.
$array[] = [
'id' => (string) $type->id,
'name' => $type->type,
'type' => $type->type,
];
$array[] = ['id' => (string) $type->id, 'name' => $type->type, 'type' => $type->type];
}
return response()->api($array);

View File

@@ -49,9 +49,9 @@ class AccountController extends Controller
use CleansChartData;
use CollectsAccountsFromFilter;
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
private array $chartData = [];
private array $chartData = [];
private AccountRepositoryInterface $repository;
/**
@@ -60,16 +60,14 @@ class AccountController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->repository = app(AccountRepositoryInterface::class);
$this->validateUserGroup($request);
$this->repository->setUserGroup($this->userGroup);
$this->repository->setUser($this->user);
$this->middleware(function (Request $request, $next) {
$this->repository = app(AccountRepositoryInterface::class);
$this->validateUserGroup($request);
$this->repository->setUserGroup($this->userGroup);
$this->repository->setUser($this->user);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -115,14 +113,14 @@ class AccountController extends Controller
'label' => $account->name,
// the currency that belongs to the account.
'currency_id' => (string)$currency->id,
'currency_id' => (string) $currency->id,
'currency_name' => $currency->name,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
// the primary currency
'primary_currency_id' => (string)$this->primaryCurrency->id,
'primary_currency_id' => (string) $this->primaryCurrency->id,
// the default currency of the user (could be the same!)
'date' => $params['start']->toAtomString(),
@@ -136,7 +134,7 @@ class AccountController extends Controller
];
if ($this->convertToPrimary) {
$currentSet['pc_entries'] = [];
$currentSet['primary_currency_id'] = (string)$this->primaryCurrency->id;
$currentSet['primary_currency_id'] = (string) $this->primaryCurrency->id;
$currentSet['primary_currency_code'] = $this->primaryCurrency->code;
$currentSet['primary_currency_symbol'] = $this->primaryCurrency->symbol;
$currentSet['primary_currency_decimal_places'] = $this->primaryCurrency->decimal_places;
@@ -151,7 +149,6 @@ class AccountController extends Controller
$previous = $balance;
$currentSet['entries'][$label] = $balance;
// do the same for the primary currency balance, if relevant:
$pcBalance = null;
if ($this->convertToPrimary) {
@@ -160,6 +157,7 @@ class AccountController extends Controller
$currentSet['pc_entries'][$label] = $pcBalance;
}
$currentStart = Navigation::addPeriod($currentStart, $period);
// $currentStart->addDay();
}
$this->chartData[] = $currentSet;

View File

@@ -1,6 +1,5 @@
<?php
/*
* BalanceController.php
* Copyright (c) 2025 james@firefly-iii.org
@@ -25,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Chart;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Chart\ChartRequest;
use FireflyIII\Enums\TransactionTypeEnum;
@@ -37,6 +35,7 @@ use FireflyIII\Support\Http\Api\AccountBalanceGrouped;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class BalanceController
@@ -45,10 +44,11 @@ class BalanceController extends Controller
{
use CleansChartData;
use CollectsAccountsFromFilter;
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
private array $chartData = [];
private GroupCollectorInterface $collector;
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
private array $chartData = [];
private GroupCollectorInterface $collector;
private AccountRepositoryInterface $repository;
// private TransactionCurrency $default;
@@ -56,19 +56,17 @@ class BalanceController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(AccountRepositoryInterface::class);
$this->collector = app(GroupCollectorInterface::class);
$this->repository->setUserGroup($this->userGroup);
$this->collector->setUserGroup($this->userGroup);
$this->repository->setUser($this->user);
$this->collector->setUser($this->user);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(AccountRepositoryInterface::class);
$this->collector = app(GroupCollectorInterface::class);
$this->repository->setUserGroup($this->userGroup);
$this->collector->setUserGroup($this->userGroup);
$this->repository->setUser($this->user);
$this->collector->setUser($this->user);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -89,10 +87,16 @@ class BalanceController extends Controller
// get journals for entire period:
$this->collector->setRange($queryParameters['start'], $queryParameters['end'])
$this->collector
->setRange($queryParameters['start'], $queryParameters['end'])
->withAccountInformation()
->setXorAccounts($accounts)
->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::DEPOSIT->value, TransactionTypeEnum::RECONCILIATION->value, TransactionTypeEnum::TRANSFER->value])
->setTypes([
TransactionTypeEnum::WITHDRAWAL->value,
TransactionTypeEnum::DEPOSIT->value,
TransactionTypeEnum::RECONCILIATION->value,
TransactionTypeEnum::TRANSFER->value,
])
;
$journals = $this->collector->getExtractedJournals();

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Chart;
use Illuminate\Http\Request;
use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\DateRangeRequest;
@@ -36,13 +35,14 @@ use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use FireflyIII\Support\Facades\Steam;
/**
* Class BudgetController
@@ -52,32 +52,30 @@ class BudgetController extends Controller
use CleansChartData;
use ValidatesUserGroupTrait;
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
protected OperationsRepositoryInterface $opsRepository;
private BudgetLimitRepositoryInterface $blRepository;
private array $currencies = [];
private BudgetRepositoryInterface $repository;
private BudgetLimitRepositoryInterface $blRepository;
private array $currencies = [];
private BudgetRepositoryInterface $repository;
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(BudgetRepositoryInterface::class);
$this->blRepository = app(BudgetLimitRepositoryInterface::class);
$this->opsRepository = app(OperationsRepositoryInterface::class);
$this->repository->setUserGroup($this->userGroup);
$this->opsRepository->setUserGroup($this->userGroup);
$this->blRepository->setUserGroup($this->userGroup);
$this->repository->setUser($this->user);
$this->opsRepository->setUser($this->user);
$this->blRepository->setUser($this->user);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(BudgetRepositoryInterface::class);
$this->blRepository = app(BudgetLimitRepositoryInterface::class);
$this->opsRepository = app(OperationsRepositoryInterface::class);
$this->repository->setUserGroup($this->userGroup);
$this->opsRepository->setUserGroup($this->userGroup);
$this->blRepository->setUserGroup($this->userGroup);
$this->repository->setUser($this->user);
$this->opsRepository->setUser($this->user);
$this->blRepository->setUser($this->user);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -158,18 +156,17 @@ class BudgetController extends Controller
$rows[] = $row;
}
// is always an array
$return = [];
foreach ($rows as $row) {
$current = [
'label' => $budget->name,
'currency_id' => (string)$row['currency_id'],
'currency_id' => (string) $row['currency_id'],
'currency_name' => $row['currency_name'],
'currency_code' => $row['currency_code'],
'currency_decimal_places' => $row['currency_decimal_places'],
'primary_currency_id' => (string)$this->primaryCurrency->id,
'primary_currency_id' => (string) $this->primaryCurrency->id,
'primary_currency_name' => $this->primaryCurrency->name,
'primary_currency_code' => $this->primaryCurrency->code,
'primary_currency_symbol' => $this->primaryCurrency->symbol,
@@ -181,12 +178,7 @@ class BudgetController extends Controller
'end_date' => $row['end'],
'yAxisID' => 0,
'type' => 'bar',
'entries' => [
'budgeted' => $row['budgeted'],
'spent' => $row['spent'],
'left' => $row['left'],
'overspent' => $row['overspent'],
],
'entries' => ['budgeted' => $row['budgeted'], 'spent' => $row['spent'], 'left' => $row['left'], 'overspent' => $row['overspent']],
'pc_entries' => [
'budgeted' => $row['pc_budgeted'],
'spent' => $row['pc_spent'],
@@ -233,11 +225,11 @@ class BudgetController extends Controller
foreach ($spent as $currencyId => $block) {
$this->currencies[$currencyId] ??= Amount::getTransactionCurrencyById($currencyId);
$return[$currencyId] ??= [
'currency_id' => (string)$currencyId,
'currency_id' => (string) $currencyId,
'currency_code' => $block['currency_code'],
'currency_name' => $block['currency_name'],
'currency_symbol' => $block['currency_symbol'],
'currency_decimal_places' => (int)$block['currency_decimal_places'],
'currency_decimal_places' => (int) $block['currency_decimal_places'],
'start' => $start->toAtomString(),
'end' => $end->toAtomString(),
'budgeted' => '0',
@@ -251,7 +243,7 @@ class BudgetController extends Controller
/** @var array $journal */
foreach ($currentBudgetArray['transaction_journals'] as $journal) {
/** @var numeric-string $amount */
$amount = (string)$journal['amount'];
$amount = (string) $journal['amount'];
$return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], $amount);
}
}
@@ -270,14 +262,21 @@ class BudgetController extends Controller
if ($this->convertToPrimary) {
if ($current->transaction_currency_id === $this->primaryCurrency->id) {
// simply add it.
$amount = bcadd($amount, (string)$current->amount);
$amount = bcadd($amount, (string) $current->amount);
Log::debug(sprintf('Set amount in limit to %s', $amount));
}
if ($current->transaction_currency_id !== $this->primaryCurrency->id) {
// convert and then add it.
$converted = $converter->convert($current->transactionCurrency, $this->primaryCurrency, $current->start_date, $current->amount);
$amount = bcadd($amount, $converted);
Log::debug(sprintf('Budgeted in limit #%d: %s %s, converted to %s %s', $current->id, $current->transactionCurrency->code, $current->amount, $this->primaryCurrency->code, $converted));
Log::debug(sprintf(
'Budgeted in limit #%d: %s %s, converted to %s %s',
$current->id,
$current->transactionCurrency->code,
$current->amount,
$this->primaryCurrency->code,
$converted
));
Log::debug(sprintf('Set amount in limit to %s', $amount));
}
}

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Chart;
use Illuminate\Http\Request;
use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\DateRangeRequest;
@@ -40,6 +39,7 @@ use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
/**
@@ -52,25 +52,23 @@ class CategoryController extends Controller
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
private AccountRepositoryInterface $accountRepos;
private AccountRepositoryInterface $accountRepos;
private CurrencyRepositoryInterface $currencyRepos;
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->accountRepos = app(AccountRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
$this->accountRepos->setUserGroup($this->userGroup);
$this->currencyRepos->setUserGroup($this->userGroup);
$this->accountRepos->setUser($this->user);
$this->currencyRepos->setUser($this->user);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->accountRepos = app(AccountRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
$this->accountRepos->setUserGroup($this->userGroup);
$this->currencyRepos->setUserGroup($this->userGroup);
$this->accountRepos->setUser($this->user);
$this->currencyRepos->setUser($this->user);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -88,7 +86,12 @@ class CategoryController extends Controller
/** @var Carbon $end */
$end = $request->attributes->get('end');
$accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value]);
$accounts = $this->accountRepos->getAccountsByType([
AccountTypeEnum::DEBT->value,
AccountTypeEnum::LOAN->value,
AccountTypeEnum::MORTGAGE->value,
AccountTypeEnum::ASSET->value,
]);
$currencies = [];
$return = [];
$converter = new ExchangeRateConverter();
@@ -104,7 +107,7 @@ class CategoryController extends Controller
/** @var array $journal */
foreach ($journals as $journal) {
// find journal:
$journalCurrencyId = (int)$journal['currency_id'];
$journalCurrencyId = (int) $journal['currency_id'];
$type = $journal['transaction_type_type'];
$currency = $currencies[$journalCurrencyId] ?? $this->currencyRepos->find($journalCurrencyId);
$currencies[$journalCurrencyId] = $currency;
@@ -113,7 +116,7 @@ class CategoryController extends Controller
$currencyCode = $currency->code;
$currencySymbol = $currency->symbol;
$currencyDecimalPlaces = $currency->decimal_places;
$amount = Steam::positive((string)$journal['amount']);
$amount = Steam::positive((string) $journal['amount']);
$pcAmount = null;
// overrule if necessary:
@@ -130,18 +133,17 @@ class CategoryController extends Controller
Log::debug(sprintf('Converted %s %s to %s %s', $journal['currency_code'], $amount, $this->primaryCurrency->code, $pcAmount));
}
$categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category');
$categoryName = $journal['category_name'] ?? (string) trans('firefly.no_category');
$key = sprintf('%s-%s', $categoryName, $currencyCode);
// create arrays
$return[$key] ??= [
'label' => $categoryName,
'currency_id' => (string)$currencyId,
'currency_id' => (string) $currencyId,
'currency_name' => $currencyName,
'currency_code' => $currencyCode,
'currency_symbol' => $currencySymbol,
'currency_decimal_places' => $currencyDecimalPlaces,
'primary_currency_id' => (string)$this->primaryCurrency->id,
'primary_currency_id' => (string) $this->primaryCurrency->id,
'primary_currency_name' => $this->primaryCurrency->name,
'primary_currency_code' => $this->primaryCurrency->code,
'primary_currency_symbol' => $this->primaryCurrency->symbol,
@@ -151,14 +153,8 @@ class CategoryController extends Controller
'end_date' => $end->toAtomString(),
'yAxisID' => 0,
'type' => 'bar',
'entries' => [
'spent' => '0',
'earned' => '0',
],
'pc_entries' => [
'spent' => '0',
'earned' => '0',
],
'entries' => ['spent' => '0', 'earned' => '0'],
'pc_entries' => ['spent' => '0', 'earned' => '0'],
];
// add monies
@@ -182,7 +178,10 @@ class CategoryController extends Controller
$return = array_values($return);
// order by amount
usort($return, static fn (array $a, array $b): int => ((float)$a['entries']['spent'] + (float)$a['entries']['earned']) < ((float)$b['entries']['spent'] + (float)$b['entries']['earned']) ? 1 : -1);
usort($return, static fn (array $a, array $b): int => ((float) $a['entries']['spent'] + (float) $a['entries']['earned'])
< ((float) $b['entries']['spent'] + (float) $b['entries']['earned'])
? 1
: -1);
return response()->json($this->clean($return));
}

View File

@@ -24,9 +24,9 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers;
use Deprecated;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
use Deprecated;
use FireflyIII\Exceptions\BadHttpHeaderException;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Facades\Amount;
@@ -63,15 +63,16 @@ abstract class Controller extends BaseController
use ValidatesRequests;
use ValidatesUserGroupTrait;
protected const string CONTENT_TYPE = 'application/vnd.api+json';
protected const string JSON_CONTENT_TYPE = 'application/json';
protected array $accepts = ['application/json', 'application/vnd.api+json'];
protected const string CONTENT_TYPE = 'application/vnd.api+json';
protected const string JSON_CONTENT_TYPE = 'application/json';
protected bool $convertToPrimary = false;
protected array $accepts = ['application/json', 'application/vnd.api+json'];
protected bool $convertToPrimary = false;
protected TransactionCurrency $primaryCurrency;
/** @deprecated use Request classes */
protected ParameterBag $parameters;
protected ParameterBag $parameters;
/**
* Controller constructor.
@@ -79,26 +80,22 @@ abstract class Controller extends BaseController
public function __construct()
{
// get global parameters
$this->middleware(
function ($request, $next) {
$this->parameters = $this->getParameters();
if (auth()->check()) {
$language = Steam::getLanguage();
$this->convertToPrimary = Amount::convertToPrimary();
$this->primaryCurrency = Amount::getPrimaryCurrency();
app()->setLocale($language);
}
// filter down what this endpoint accepts.
if (!$request->accepts($this->accepts)) {
throw new BadHttpHeaderException(sprintf('Sorry, Accept header "%s" is not something this endpoint can provide.', $request->header('Accept')));
}
return $next($request);
$this->middleware(function ($request, $next) {
$this->parameters = $this->getParameters();
if (auth()->check()) {
$language = Steam::getLanguage();
$this->convertToPrimary = Amount::convertToPrimary();
$this->primaryCurrency = Amount::getPrimaryCurrency();
app()->setLocale($language);
}
);
// filter down what this endpoint accepts.
if (!$request->accepts($this->accepts)) {
throw new BadHttpHeaderException(sprintf('Sorry, Accept header "%s" is not something this endpoint can provide.', $request->header('Accept')));
}
return $next($request);
});
}
#[Deprecated(message: <<<'TXT'
@@ -108,7 +105,7 @@ abstract class Controller extends BaseController
private function getParameters(): ParameterBag
{
$bag = new ParameterBag();
$page = (int)request()->get('page');
$page = (int) request()->get('page');
$page = min(max(1, $page), 2 ** 16);
$bag->set('page', $page);
@@ -127,10 +124,10 @@ abstract class Controller extends BaseController
$obj = null;
if (null !== $date) {
try {
$obj = Carbon::parse((string)$date, config('app.timezone'));
$obj = Carbon::parse((string) $date, config('app.timezone'));
} catch (InvalidFormatException $e) {
// don't care
Log::warning(sprintf('Ignored invalid date "%s" in API controller parameter check: %s', substr((string)$date, 0, 20), $e->getMessage()));
Log::warning(sprintf('Ignored invalid date "%s" in API controller parameter check: %s', substr((string) $date, 0, 20), $e->getMessage()));
}
}
if ($obj instanceof Carbon) {
@@ -150,24 +147,27 @@ abstract class Controller extends BaseController
$value = null;
}
if (null !== $value) {
$value = (int)$value;
$value = (int) $value;
$value = min(max(1, $value), 2 ** 16);
$bag->set($integer, $value);
}
if (null === $value
if (
null === $value
&& 'limit' === $integer // @phpstan-ignore-line
&& auth()->check()) {
&& auth()->check()
) {
// set default for user:
/** @var User $user */
$user = auth()->user();
$pageSize = (int)Preferences::getForUser($user, 'listPageSize', 50)->data;
$pageSize = (int) Preferences::getForUser($user, 'listPageSize', 50)->data;
$bag->set($integer, $pageSize);
}
}
// sort fields:
return $bag;
// return $this->getSortParameters($bag);
}

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Data;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Data\DestroyRequest;
use FireflyIII\Enums\AccountTypeEnum;
@@ -49,6 +48,7 @@ use FireflyIII\Services\Internal\Destroy\AccountDestroyService;
use FireflyIII\Services\Internal\Destroy\JournalDestroyService;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
/**
@@ -63,13 +63,11 @@ class DestroyController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
return $next($request);
}
);
return $next($request);
});
}
public function destroy(DestroyRequest $request): JsonResponse
@@ -77,10 +75,40 @@ class DestroyController extends Controller
$objects = $request->getObjects();
$this->unused = $request->boolean('unused');
$allExceptAssets = [AccountTypeEnum::BENEFICIARY->value, AccountTypeEnum::CASH->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::EXPENSE->value, AccountTypeEnum::IMPORT->value, AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::LIABILITY_CREDIT->value, AccountTypeEnum::RECONCILIATION->value, AccountTypeEnum::REVENUE->value];
$all = [AccountTypeEnum::ASSET->value, AccountTypeEnum::BENEFICIARY->value, AccountTypeEnum::CASH->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::EXPENSE->value, AccountTypeEnum::IMPORT->value, AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::LIABILITY_CREDIT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::RECONCILIATION->value];
$allExceptAssets = [
AccountTypeEnum::BENEFICIARY->value,
AccountTypeEnum::CASH->value,
AccountTypeEnum::CREDITCARD->value,
AccountTypeEnum::DEFAULT->value,
AccountTypeEnum::EXPENSE->value,
AccountTypeEnum::IMPORT->value,
AccountTypeEnum::INITIAL_BALANCE->value,
AccountTypeEnum::LIABILITY_CREDIT->value,
AccountTypeEnum::RECONCILIATION->value,
AccountTypeEnum::REVENUE->value,
];
$all = [
AccountTypeEnum::ASSET->value,
AccountTypeEnum::BENEFICIARY->value,
AccountTypeEnum::CASH->value,
AccountTypeEnum::CREDITCARD->value,
AccountTypeEnum::DEBT->value,
AccountTypeEnum::DEFAULT->value,
AccountTypeEnum::EXPENSE->value,
AccountTypeEnum::IMPORT->value,
AccountTypeEnum::INITIAL_BALANCE->value,
AccountTypeEnum::LIABILITY_CREDIT->value,
AccountTypeEnum::LOAN->value,
AccountTypeEnum::MORTGAGE->value,
AccountTypeEnum::RECONCILIATION->value,
];
$liabilities = [AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::CREDITCARD->value];
$transactions = [TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::DEPOSIT->value, TransactionTypeEnum::TRANSFER->value, TransactionTypeEnum::RECONCILIATION->value];
$transactions = [
TransactionTypeEnum::WITHDRAWAL->value,
TransactionTypeEnum::DEPOSIT->value,
TransactionTypeEnum::TRANSFER->value,
TransactionTypeEnum::RECONCILIATION->value,
];
match ($objects) {
'budgets' => $this->destroyBudgets(),
@@ -101,7 +129,7 @@ class DestroyController extends Controller
'withdrawals' => $this->destroyTransactions([TransactionTypeEnum::WITHDRAWAL->value]),
'deposits' => $this->destroyTransactions([TransactionTypeEnum::DEPOSIT->value]),
'transfers' => $this->destroyTransactions([TransactionTypeEnum::TRANSFER->value]),
default => throw new FireflyException(sprintf('200033: This endpoint can\'t handle object "%s"', $objects)),
default => throw new FireflyException(sprintf('200033: This endpoint can\'t handle object "%s"', $objects))
};
Preferences::mark();

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Data;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Account;
@@ -40,6 +39,7 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class PurgeController
@@ -51,13 +51,11 @@ class PurgeController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
return $next($request);
}
);
return $next($request);
});
}
/**

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Models\Transaction;
use FireflyIII\Models\TransactionGroup;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\Transaction\StoreRequest;
@@ -45,7 +44,8 @@ use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use League\Fractal\Resource\Item;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\GoneHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
/**
* Class StoreController
@@ -84,7 +84,7 @@ class StoreController extends Controller
*
* Store a new transaction.
*
* @throws FireflyException|ValidationException
* @throws FireflyException|GoneHttpException|ValidationException
*/
public function store(StoreRequest $request): JsonResponse
{
@@ -133,10 +133,9 @@ class StoreController extends Controller
->withAPIInformation()
;
/** @var TransactionGroup $selectedGroup */
$selectedGroup = $collector->getGroups()->first();
if (null === $selectedGroup) {
throw new NotFoundHttpException();
throw HttpException::fromStatusCode(410, '200032: Cannot find transaction. Possibly, a rule deleted this transaction after its creation.');
}
// enrich

View File

@@ -262,7 +262,7 @@ class ListController extends Controller
// filter selection
$collection = $unfiltered->filter(
static function (Recurrence $recurrence) use ($currency): ?Recurrence { // @phpstan-ignore-line
if (array_any($recurrence->recurrenceTransactions, fn ($transaction): bool => $transaction->transaction_currency_id === $currency->id || $transaction->foreign_currency_id === $currency->id)) {
if (array_any($recurrence->recurrenceTransactions, static fn ($transaction): bool => $transaction->transaction_currency_id === $currency->id || $transaction->foreign_currency_id === $currency->id)) {
return $recurrence;
}
@@ -311,7 +311,7 @@ class ListController extends Controller
$collection = $unfiltered->filter(
static function (Rule $rule) use ($currency): ?Rule { // @phpstan-ignore-line
if (array_any($rule->ruleTriggers, fn ($trigger): bool => 'currency_is' === $trigger->trigger_type && $currency->name === $trigger->trigger_value)) {
if (array_any($rule->ruleTriggers, static fn ($trigger): bool => 'currency_is' === $trigger->trigger_type && $currency->name === $trigger->trigger_value)) {
return $rule;
}

View File

@@ -91,7 +91,7 @@ class ConvertsDatesToUTC extends Command
}
$this->friendlyInfo(sprintf('Converting field "%s" of model "%s" to UTC.', $field, $shortModel));
$items->each(
function ($item) use ($field, $timezoneField): void {
static function ($item) use ($field, $timezoneField): void {
$date = Carbon::parse($item->{$field}, $item->{$timezoneField}); // @phpstan-ignore-line
$date->setTimezone('UTC');
$item->{$field} = $date->format('Y-m-d H:i:s'); // @phpstan-ignore-line

View File

@@ -103,7 +103,7 @@ class CorrectsPrimaryCurrencyAmounts extends Command
private function recalculateAccounts(UserGroup $userGroup): void
{
$set = $userGroup->accounts()->where(function (EloquentBuilder $q): void {
$set = $userGroup->accounts()->where(static function (EloquentBuilder $q): void {
$q->whereNotNull('virtual_balance');
// this needs a different piece of code for postgres.
@@ -226,10 +226,10 @@ class CorrectsPrimaryCurrencyAmounts extends Command
$set = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(function (DatabaseBuilder $q1) use ($currency): void {
$q1->where(function (DatabaseBuilder $q2) use ($currency): void {
->where(static function (DatabaseBuilder $q1) use ($currency): void {
$q1->where(static function (DatabaseBuilder $q2) use ($currency): void {
$q2->whereNot('transactions.transaction_currency_id', $currency->id)->whereNull('transactions.foreign_currency_id');
})->orWhere(function (DatabaseBuilder $q3) use ($currency): void {
})->orWhere(static function (DatabaseBuilder $q3) use ($currency): void {
$q3->whereNot('transactions.transaction_currency_id', $currency->id)->whereNot('transactions.foreign_currency_id', $currency->id);
});
})

View File

@@ -27,6 +27,7 @@ namespace FireflyIII\Console\Commands\Correction;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Support\System\OAuthKeys;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
class RestoresOAuthKeys extends Command
{
@@ -40,7 +41,9 @@ class RestoresOAuthKeys extends Command
*/
public function handle(): int
{
Log::debug('Restore OAuth Keys command.');
$this->restoreOAuthKeys();
Log::debug('Done with OAuth Keys command.');
return 0;
}

View File

@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
/*
* ExplainAvailableBudget.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Console\Commands\Explain;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
use FireflyIII\Console\Commands\VerifiesAccessToken;
use FireflyIII\Support\Facades\Navigation;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Console\Command;
class ExplainAvailableBudget extends Command
{
use VerifiesAccessToken;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'explain:available-budget
{--date=now : A date formatted YYYY-MM-DD or the word "now"}
{--user=1 : The user ID.}
{--token= : The user\'s access token.}
';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Explains why the available budget amount is what it is.';
/**
* Execute the console command.
*/
public function handle(): int
{
$date = $this->getDate((string) $this->option('date'));
$range = Preferences::getForUser($this->getUser(), 'viewRange', '1M')->data ?? '1M';
$title = Navigation::periodShow($date, $range);
$this->line('This command explains why the "available" budget bar at the top of your /budget bar means.');
$this->line(sprintf(
'You submitted date %s and your settings show a %s period, so this explanation concerns the period "%s".',
$date->format('Y-m-d'),
$range,
$title
));
return Command::SUCCESS;
}
private function getDate(string $param): Carbon
{
if ('now' === $param) {
return today();
}
try {
$date = Carbon::parse($param);
} catch (InvalidFormatException) {
$this->warn('Invalid date given. Fall back to today\'s date.');
return today();
}
return $date;
}
}

View File

@@ -1,35 +0,0 @@
<?php
/*
* Updated.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Model\Account;
use FireflyIII\Models\Account;
use Illuminate\Queue\SerializesModels;
class Updated
{
use SerializesModels;
public function __construct(public Account $account) {}
}

View File

@@ -34,7 +34,7 @@ use Illuminate\Support\Facades\Log;
/**
* Class ChangedAmount
*/
class ChangedAmount extends Event
class PiggyBankAmountIsChanged extends Event
{
use SerializesModels;

View File

@@ -1,9 +1,9 @@
<?php
declare(strict_types=1);
/*
* ChangedName.php
* Copyright (c) 2025 james@firefly-iii.org
* PiggyBankNameIsChanged.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -21,15 +21,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Model\PiggyBank;
use FireflyIII\Events\Event;
use FireflyIII\Models\PiggyBank;
use Illuminate\Queue\SerializesModels;
class ChangedName extends Event
/**
* Needs to be an event because system needs old value as well as the new value.
*/
class PiggyBankNameIsChanged extends Event
{
use SerializesModels;

View File

@@ -1,10 +1,8 @@
<?php
/*
* WarnUserAboutBill.php
* Copyright (c) 2025 james@firefly-iii.org
* SubscriptionNeedsExtensionOrRenewal.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -24,18 +22,15 @@
declare(strict_types=1);
namespace FireflyIII\Events\Model\Bill;
namespace FireflyIII\Events\Model\Subscription;
use FireflyIII\Events\Event;
use FireflyIII\Models\Bill;
use Illuminate\Queue\SerializesModels;
/**
* Class WarnUserAboutBill.
*/
class WarnUserAboutBill extends Event
class SubscriptionNeedsExtensionOrRenewal extends Event
{
use SerializesModels;
public function __construct(public Bill $bill, public string $field, public int $diff) {}
public function __construct(public Bill $subscription, public string $field, public int $diff) {}
}

View File

@@ -1,9 +1,9 @@
<?php
declare(strict_types=1);
/*
* WarnUserAboutOverdueSubscriptions.php
* Copyright (c) 2025 james@firefly-iii.org
* SubscriptionIsOverdueForPayment.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -21,18 +21,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Model\Bill;
namespace FireflyIII\Events\Model\Subscription;
use FireflyIII\Events\Event;
use FireflyIII\User;
use Illuminate\Queue\SerializesModels;
class WarnUserAboutOverdueSubscriptions extends Event
class SubscriptionsAreOverdueForPayment extends Event
{
use SerializesModels;
public function __construct(public User $user, public array $overdue) {}
}

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Events;
use FireflyIII\User;
use Illuminate\Queue\SerializesModels;
use SensitiveParameter;
/**
* Class RequestedNewPassword.
@@ -46,7 +47,7 @@ class RequestedNewPassword extends Event
/**
* Create a new event instance. This event is triggered when a users tries to reset his or her password.
*/
public function __construct(User $user, string $token, string $ipAddress)
public function __construct(User $user, #[SensitiveParameter] string $token, string $ipAddress)
{
$this->user = $user;
$this->token = $token;

View File

@@ -1,44 +0,0 @@
<?php
/*
* EnabledMFA.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Security;
use FireflyIII\Events\Event;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Queue\SerializesModels;
class MFAUsedBackupCode extends Event
{
use SerializesModels;
public User $user;
public function __construct(Authenticatable|User|null $user)
{
if ($user instanceof User) {
$this->user = $user;
}
}
}

View File

@@ -1,8 +1,9 @@
<?php
declare(strict_types=1);
/*
* UnknownUserAttemptedLogin.php
* Copyright (c) 2024 james@firefly-iii.org.
* UnknownUserTriedLogin.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -17,16 +18,14 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Security;
namespace FireflyIII\Events\Security\System;
use Illuminate\Queue\SerializesModels;
class UnknownUserAttemptedLogin
class UnknownUserTriedLogin
{
use SerializesModels;

View File

@@ -1,8 +1,9 @@
<?php
declare(strict_types=1);
/*
* EnabledMFA.php
* Copyright (c) 2024 james@firefly-iii.org.
* UserFailedLoginAttempt.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -17,19 +18,18 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Security;
namespace FireflyIII\Events\Security\User;
use FireflyIII\Events\Event;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Queue\SerializesModels;
use InvalidArgumentException;
class EnabledMFA extends Event
class UserFailedLoginAttempt extends Event
{
use SerializesModels;
@@ -39,6 +39,10 @@ class EnabledMFA extends Event
{
if ($user instanceof User) {
$this->user = $user;
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
}
}

View File

@@ -1,8 +1,9 @@
<?php
declare(strict_types=1);
/*
* EnabledMFA.php
* Copyright (c) 2024 james@firefly-iii.org.
* UserHasDisabledMFA.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -17,19 +18,18 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Security;
namespace FireflyIII\Events\Security\User;
use FireflyIII\Events\Event;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Queue\SerializesModels;
use InvalidArgumentException;
class MFANewBackupCodes extends Event
class UserHasDisabledMFA extends Event
{
use SerializesModels;
@@ -39,6 +39,10 @@ class MFANewBackupCodes extends Event
{
if ($user instanceof User) {
$this->user = $user;
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
}
}

View File

@@ -1,8 +1,9 @@
<?php
declare(strict_types=1);
/*
* EnabledMFA.php
* Copyright (c) 2024 james@firefly-iii.org.
* UserHasEnabledMFA.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -17,19 +18,18 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Security;
namespace FireflyIII\Events\Security\User;
use FireflyIII\Events\Event;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Queue\SerializesModels;
use InvalidArgumentException;
class DisabledMFA extends Event
class UserHasEnabledMFA extends Event
{
use SerializesModels;
@@ -39,6 +39,10 @@ class DisabledMFA extends Event
{
if ($user instanceof User) {
$this->user = $user;
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
}
}

View File

@@ -1,8 +1,9 @@
<?php
declare(strict_types=1);
/*
* EnabledMFA.php
* Copyright (c) 2024 james@firefly-iii.org.
* UserHasFewMFABackupCodesLeft.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -17,19 +18,18 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Security;
namespace FireflyIII\Events\Security\User;
use FireflyIII\Events\Event;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Queue\SerializesModels;
use InvalidArgumentException;
class MFABackupFewLeft extends Event
class UserHasFewMFABackupCodesLeft extends Event
{
use SerializesModels;
@@ -39,6 +39,10 @@ class MFABackupFewLeft extends Event
{
if ($user instanceof User) {
$this->user = $user;
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/*
* UserHasGeneratedNewBackupCodes.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Events\Security\User;
use FireflyIII\Events\Event;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Queue\SerializesModels;
use InvalidArgumentException;
class UserHasGeneratedNewBackupCodes extends Event
{
use SerializesModels;
public User $user;
public function __construct(Authenticatable|User|null $user)
{
if ($user instanceof User) {
$this->user = $user;
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/*
* UserHasNoMFABackupCodesLeft.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Events\Security\User;
use FireflyIII\Events\Event;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Queue\SerializesModels;
use InvalidArgumentException;
class UserHasNoMFABackupCodesLeft extends Event
{
use SerializesModels;
public User $user;
public function __construct(Authenticatable|User|null $user)
{
if ($user instanceof User) {
$this->user = $user;
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
}
}

View File

@@ -1,8 +1,9 @@
<?php
declare(strict_types=1);
/*
* EnabledMFA.php
* Copyright (c) 2024 james@firefly-iii.org.
* UserHasUsedBackupCode.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -17,19 +18,18 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Security;
namespace FireflyIII\Events\Security\User;
use FireflyIII\Events\Event;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Queue\SerializesModels;
use InvalidArgumentException;
class MFABackupNoLeft extends Event
class UserHasUsedBackupCode extends Event
{
use SerializesModels;
@@ -39,6 +39,10 @@ class MFABackupNoLeft extends Event
{
if ($user instanceof User) {
$this->user = $user;
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
}
}

View File

@@ -1,8 +1,9 @@
<?php
declare(strict_types=1);
/*
* EnabledMFA.php
* Copyright (c) 2024 james@firefly-iii.org.
* UserKeepsFailingMFA.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -17,19 +18,18 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Security;
namespace FireflyIII\Events\Security\User;
use FireflyIII\Events\Event;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Queue\SerializesModels;
use InvalidArgumentException;
class MFAManyFailedAttempts extends Event
class UserKeepsFailingMFA extends Event
{
use SerializesModels;
@@ -39,6 +39,10 @@ class MFAManyFailedAttempts extends Event
{
if ($user instanceof User) {
$this->user = $user;
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
}
}

View File

@@ -1,44 +0,0 @@
<?php
/*
* UserAttemptedLogin.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Security;
use FireflyIII\Events\Event;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Queue\SerializesModels;
class UserAttemptedLogin extends Event
{
use SerializesModels;
public User $user;
public function __construct(Authenticatable|User|null $user)
{
if ($user instanceof User) {
$this->user = $user;
}
}
}

View File

@@ -45,6 +45,7 @@ use Override;
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\GoneHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -71,6 +72,7 @@ class Handler extends ExceptionHandler
AuthenticationException::class,
LaravelValidationException::class,
NotFoundHttpException::class,
GoneHttpException::class,
OAuthServerException::class,
LaravelOAuthException::class,
TokenMismatchException::class,

View File

@@ -23,7 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Factory;
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
use FireflyIII\Events\Model\PiggyBank\PiggyBankAmountIsChanged;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\ObjectGroup;
@@ -305,7 +305,7 @@ class PiggyBankFactory
if (0 !== bccomp($oldSavedAmount, $newSavedAmount)) {
Log::debug('Amount changed, will create event for it.');
// create event for difference.
event(new ChangedAmount($piggyBank, bcsub($newSavedAmount, $oldSavedAmount), null, null));
event(new PiggyBankAmountIsChanged($piggyBank, bcsub($newSavedAmount, $oldSavedAmount), null, null));
}
}
if (0 === count($toBeLinked)) {

View File

@@ -26,18 +26,16 @@ namespace FireflyIII\Handlers\Events;
use Exception;
use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Events\NewVersionAvailable;
use FireflyIII\Events\Security\UnknownUserAttemptedLogin;
use FireflyIII\Events\Test\OwnerTestNotificationChannel;
use FireflyIII\Notifications\Admin\UnknownUserLoginAttempt;
use FireflyIII\Notifications\Admin\UserInvitation;
use FireflyIII\Notifications\Admin\VersionCheckResult;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\Notifications\Test\OwnerTestNotificationEmail;
use FireflyIII\Notifications\Test\OwnerTestNotificationPushover;
use FireflyIII\Notifications\Test\OwnerTestNotificationSlack;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use FireflyIII\Support\Facades\FireflyConfig;
/**
* Class AdminEventHandler.
@@ -70,28 +68,6 @@ class AdminEventHandler
}
}
public function sendLoginAttemptNotification(UnknownUserAttemptedLogin $event): void
{
try {
$owner = new OwnerNotifiable();
Notification::send($owner, new UnknownUserLoginAttempt($event->address));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
/**
* Send new version message to admin.
*/

View File

@@ -28,6 +28,7 @@ use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\DestroyedTransactionGroup;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Models\AccountBalanceCalculator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
@@ -61,6 +62,9 @@ class DestroyedGroupEventHandler
private function updateRunningBalance(DestroyedTransactionGroup $event): void
{
if (false === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
return;
}
Log::debug(__METHOD__);
$group = $event->transactionGroup;
foreach ($group->transactionJournals as $journal) {

View File

@@ -1,223 +0,0 @@
<?php
/*
* MFAHandler.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events\Security;
use Exception;
use FireflyIII\Events\Security\DisabledMFA;
use FireflyIII\Events\Security\EnabledMFA;
use FireflyIII\Events\Security\MFABackupFewLeft;
use FireflyIII\Events\Security\MFABackupNoLeft;
use FireflyIII\Events\Security\MFAManyFailedAttempts;
use FireflyIII\Events\Security\MFANewBackupCodes;
use FireflyIII\Events\Security\MFAUsedBackupCode;
use FireflyIII\Notifications\Security\DisabledMFANotification;
use FireflyIII\Notifications\Security\EnabledMFANotification;
use FireflyIII\Notifications\Security\MFABackupFewLeftNotification;
use FireflyIII\Notifications\Security\MFABackupNoLeftNotification;
use FireflyIII\Notifications\Security\MFAManyFailedAttemptsNotification;
use FireflyIII\Notifications\Security\MFAUsedBackupCodeNotification;
use FireflyIII\Notifications\Security\NewBackupCodesNotification;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Log;
class MFAHandler
{
public function sendBackupFewLeftMail(MFABackupFewLeft $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$user = $event->user;
$count = $event->count;
try {
Notification::send($user, new MFABackupFewLeftNotification($user, $count));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
public function sendBackupNoLeftMail(MFABackupNoLeft $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$user = $event->user;
try {
Notification::send($user, new MFABackupNoLeftNotification($user));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
public function sendMFADisabledMail(DisabledMFA $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$user = $event->user;
try {
Notification::send($user, new DisabledMFANotification($user));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
public function sendMFAEnabledMail(EnabledMFA $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$user = $event->user;
try {
Notification::send($user, new EnabledMFANotification($user));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
public function sendMFAFailedAttemptsMail(MFAManyFailedAttempts $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$user = $event->user;
$count = $event->count;
try {
Notification::send($user, new MFAManyFailedAttemptsNotification($user, $count));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
public function sendNewMFABackupCodesMail(MFANewBackupCodes $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$user = $event->user;
try {
Notification::send($user, new NewBackupCodesNotification($user));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
public function sendUsedBackupCodeMail(MFAUsedBackupCode $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$user = $event->user;
try {
Notification::send($user, new MFAUsedBackupCodeNotification($user));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
}

View File

@@ -34,6 +34,7 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\Services\Internal\Support\CreditRecalculateService;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Models\AccountBalanceCalculator;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use Illuminate\Support\Collection;
@@ -70,8 +71,12 @@ class UpdatedGroupEventHandler
foreach ($event->transactionGroup->transactionJournals as $journal) {
$source = $journal->transactions()->where('amount', '<', '0')->first();
$dest = $journal->transactions()->where('amount', '>', '0')->first();
$repository->deleteStatisticsForModel($source->account, $journal->date);
$repository->deleteStatisticsForModel($dest->account, $journal->date);
if (null !== $source) {
$repository->deleteStatisticsForModel($source->account, $journal->date);
}
if (null !== $dest) {
$repository->deleteStatisticsForModel($dest->account, $journal->date);
}
$categories = $journal->categories;
$tags = $journal->tags;
@@ -213,6 +218,9 @@ class UpdatedGroupEventHandler
private function updateRunningBalance(UpdatedTransactionGroup $event): void
{
if (false === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
return;
}
Log::debug(__METHOD__);
$group = $event->transactionGroup;
foreach ($group->transactionJournals as $journal) {

View File

@@ -32,7 +32,6 @@ use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Events\DetectedNewIPAddress;
use FireflyIII\Events\RegisteredUser;
use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Events\Security\UserAttemptedLogin;
use FireflyIII\Events\Test\UserTestNotificationChannel;
use FireflyIII\Events\UserChangedEmail;
use FireflyIII\Exceptions\FireflyException;
@@ -43,7 +42,6 @@ use FireflyIII\Models\GroupMembership;
use FireflyIII\Models\UserGroup;
use FireflyIII\Models\UserRole;
use FireflyIII\Notifications\Admin\UserRegistration as AdminRegistrationNotification;
use FireflyIII\Notifications\Security\UserFailedLoginAttempt;
use FireflyIII\Notifications\Test\UserTestNotificationEmail;
use FireflyIII\Notifications\Test\UserTestNotificationPushover;
use FireflyIII\Notifications\Test\UserTestNotificationSlack;
@@ -51,13 +49,13 @@ use FireflyIII\Notifications\User\UserLogin;
use FireflyIII\Notifications\User\UserNewPassword;
use FireflyIII\Notifications\User\UserRegistration as UserRegistrationNotification;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\User;
use Illuminate\Auth\Events\Login;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Notification;
use FireflyIII\Support\Facades\FireflyConfig;
/**
* Class UserEventHandler.
@@ -298,27 +296,6 @@ class UserEventHandler
}
}
public function sendLoginAttemptNotification(UserAttemptedLogin $event): void
{
try {
Notification::send($event->user, new UserFailedLoginAttempt($event->user));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
/**
* Send a new password to the user.
*/

View File

@@ -25,7 +25,9 @@ namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalLink;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/**
@@ -48,13 +50,37 @@ class TransactionJournalObserver
}
});
// delete all links:
TransactionJournalLink::where('source_id', $transactionJournal->id)->delete();
TransactionJournalLink::where('destination_id', $transactionJournal->id)->delete();
// update events
// TODO move to repository
$transactionJournal->piggyBankEvents()->update(['transaction_journal_id' => null]);
// delete all from 'budget_transaction_journal'
DB::table('budget_transaction_journal')->where('transaction_journal_id', $transactionJournal->id)->delete();
// delete all from 'category_transaction_journal'
DB::table('category_transaction_journal')->where('transaction_journal_id', $transactionJournal->id)->delete();
// delete all from 'tag_transaction_journal'
DB::table('tag_transaction_journal')->where('transaction_journal_id', $transactionJournal->id)->delete();
/** @var Attachment $attachment */
foreach ($transactionJournal->attachments()->get() as $attachment) {
$repository->destroy($attachment);
}
$transactionJournal->transactionJournalMeta()->delete();
$transactionJournal->locations()->delete();
$transactionJournal->notes()->delete();
$transactionJournal->sourceJournalLinks()->delete();
$transactionJournal->destJournalLinks()->delete();
$transactionJournal->auditLogEntries()->delete();
// set all transactions AFTER this one to balance_dirty for recalc.
}
}

View File

@@ -828,7 +828,7 @@ class GroupCollector implements GroupCollectorInterface
*/
foreach ($this->sorting as $field => $direction) {
$func = 'ASC' === $direction ? 'sortBy' : 'sortByDesc';
$collection = $collection->{$func}(function (array $product, int $key) use ($field) { // @phpstan-ignore-line
$collection = $collection->{$func}(static function (array $product, int $key) use ($field) { // @phpstan-ignore-line
// depends on $field:
if ('description' === $field) {
if (1 === count($product['transactions'])) {

View File

@@ -25,12 +25,13 @@ namespace FireflyIII\Http\Controllers\Auth;
use Carbon\Carbon;
use FireflyIII\Events\ActuallyLoggedIn;
use FireflyIII\Events\Security\UnknownUserAttemptedLogin;
use FireflyIII\Events\Security\UserAttemptedLogin;
use FireflyIII\Events\Security\System\UnknownUserTriedLogin;
use FireflyIII\Events\Security\User\UserFailedLoginAttempt;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Providers\RouteServiceProvider;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\User;
use Illuminate\Contracts\Foundation\Application;
@@ -50,7 +51,6 @@ use Illuminate\Validation\ValidationException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Symfony\Component\HttpFoundation\Response as ResponseAlias;
use FireflyIII\Support\Facades\FireflyConfig;
/**
* Class LoginController
@@ -138,10 +138,10 @@ class LoginController extends Controller
$user = $this->repository->findByEmail($username);
if (!$user instanceof User) {
// send event to owner.
event(new UnknownUserAttemptedLogin($username));
event(new UnknownUserTriedLogin($username));
}
if ($user instanceof User) {
event(new UserAttemptedLogin($user));
event(new UserFailedLoginAttempt($user));
}
// Copied directly from AuthenticatesUsers, but with logging added:

View File

@@ -37,6 +37,7 @@ use Illuminate\View\View;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use SensitiveParameter;
/**
* Class ResetPasswordController
@@ -97,7 +98,7 @@ class ResetPasswordController extends Controller
// database. Otherwise, we will parse the error and return the response.
$response = $this->broker()->reset(
$this->credentials($request),
function ($user, $password): void {
function ($user, #[SensitiveParameter] $password): void {
$this->resetPassword($user, $password);
}
);
@@ -123,7 +124,7 @@ class ResetPasswordController extends Controller
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function showResetForm(Request $request, $token = null)
public function showResetForm(Request $request, #[SensitiveParameter] $token = null)
{
if ('web' !== config('firefly.authentication_guard')) {
$message = sprintf('Cannot reset password when authenticating over "%s".', config('firefly.authentication_guard'));

View File

@@ -23,13 +23,13 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Auth;
use FireflyIII\Support\Facades\Preferences;
use Carbon\Carbon;
use FireflyIII\Events\Security\MFABackupFewLeft;
use FireflyIII\Events\Security\MFABackupNoLeft;
use FireflyIII\Events\Security\MFAManyFailedAttempts;
use FireflyIII\Events\Security\MFAUsedBackupCode;
use FireflyIII\Events\Security\User\UserHasFewMFABackupCodesLeft;
use FireflyIII\Events\Security\User\UserHasNoMFABackupCodesLeft;
use FireflyIII\Events\Security\User\UserHasUsedBackupCode;
use FireflyIII\Events\Security\User\UserKeepsFailingMFA;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
@@ -54,7 +54,7 @@ class TwoFactorController extends Controller
/** @var User $user */
$user = auth()->user();
$siteOwner = config('firefly.site_owner');
$title = (string) trans('firefly.two_factor_forgot_title');
$title = (string)trans('firefly.two_factor_forgot_title');
return view('auth.lost-two-factor', ['user' => $user, 'siteOwner' => $siteOwner, 'title' => $title]);
}
@@ -67,7 +67,7 @@ class TwoFactorController extends Controller
{
/** @var array $mfaHistory */
$mfaHistory = Preferences::get('mfa_history', [])->data;
$mfaCode = (string) $request->get('one_time_password');
$mfaCode = (string)$request->get('one_time_password');
// is in history? then refuse to use it.
if ($this->inMFAHistory($mfaCode, $mfaHistory)) {
@@ -88,7 +88,7 @@ class TwoFactorController extends Controller
if (3 === $counter || 10 === $counter) {
// do not reset MFA failure counter, but DO send a warning to the user.
Log::channel('audit')->info(sprintf('User "%s" has had %d failed MFA attempts.', $user->email, $counter));
event(new MFAManyFailedAttempts($user, $counter));
event(new UserKeepsFailingMFA($user, $counter));
}
unset($user);
}
@@ -116,7 +116,7 @@ class TwoFactorController extends Controller
// send user notification.
$user = auth()->user();
Log::channel('audit')->info(sprintf('User "%s" has used a backup code.', $user->email));
event(new MFAUsedBackupCode($user));
event(new UserHasUsedBackupCode($user));
return redirect(route('home'));
}
@@ -168,7 +168,7 @@ class TwoFactorController extends Controller
private function addToMFAFailureCounter(): void
{
$preference = (int) Preferences::get('mfa_failure_count', 0)->data;
$preference = (int)Preferences::get('mfa_failure_count', 0)->data;
++$preference;
Log::channel('audit')->info(sprintf('MFA failure count is set to %d.', $preference));
Preferences::set('mfa_failure_count', $preference);
@@ -176,7 +176,7 @@ class TwoFactorController extends Controller
private function getMFAFailureCounter(): int
{
$value = (int) Preferences::get('mfa_failure_count', 0)->data;
$value = (int)Preferences::get('mfa_failure_count', 0)->data;
Log::channel('audit')->info(sprintf('MFA failure count is %d.', $value));
return $value;
@@ -230,13 +230,13 @@ class TwoFactorController extends Controller
if (count($newList) <= 3 && count($newList) > 0) {
$user = auth()->user();
Log::channel('audit')->info(sprintf('User "%s" has used a backup code. They have %d backup codes left.', $user->email, count($newList)));
event(new MFABackupFewLeft($user, count($newList)));
event(new UserHasFewMFABackupCodesLeft($user, count($newList)));
}
// if the list is empty, send notification
if (0 === count($newList)) {
$user = auth()->user();
Log::channel('audit')->info(sprintf('User "%s" has used their last backup code.', $user->email));
event(new MFABackupNoLeft($user));
event(new UserHasNoMFABackupCodesLeft($user));
}
Preferences::set('mfa_recovery', $newList);

View File

@@ -24,12 +24,11 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Bill;
use FireflyIII\Support\Facades\Navigation;
use Illuminate\Support\Facades\Log;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Bill;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\ObjectGroup\OrganisesObjectGroups;
use FireflyIII\Support\Facades\Navigation;
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
use FireflyIII\Transformers\BillTransformer;
use FireflyIII\User;
@@ -38,6 +37,7 @@ use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Application;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\ParameterBag;
/**
@@ -82,22 +82,18 @@ class IndexController extends Controller
$parameters = new ParameterBag();
// sub one day from temp start so the last paid date is one day before it should be.
$tempStart = clone $start;
// 2023-06-23 do not sub one day from temp start, fix is in BillTransformer::payDates instead
// $tempStart->subDay();
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new SubscriptionEnrichment();
$enrichment->setUser($admin);
$enrichment->setStart($tempStart);
$enrichment->setStart($start->clone());
$enrichment->setEnd($end);
$collection = $enrichment->enrich($collection);
$parameters->set('start', $tempStart);
$parameters->set('start', $start->clone());
$parameters->set('end', $end);
$parameters->set('convertToPrimary', $this->convertToPrimary);
$parameters->set('primaryCurrency', $this->primaryCurrency);
@@ -152,16 +148,28 @@ class IndexController extends Controller
private function getSums(array $bills): array
{
Log::debug(sprintf('now in getSums(count:%d)', count($bills)));
$sums = [];
$range = Navigation::getViewRange(true);
/** @var array $group */
foreach ($bills as $groupOrder => $group) {
Log::debug(sprintf('Summing up group "%s"', $group['object_group_title']));
if (0 === count($group['bills'])) {
Log::debug('Group has no subscriptions, continue');
continue;
}
Log::debug(sprintf('Group has %d subscription(s)', count($group['bills'])));
/** @var array $bill */
foreach ($group['bills'] as $bill) {
if (false === $bill['active']) {
Log::debug(sprintf('Skip subscription #%d, inactive.', $bill['id']));
continue;
}
Log::debug(sprintf('Now at subscription #%d.', $bill['id']));
$currencyId = $bill['currency_id'];
$sums[$groupOrder][$currencyId] ??= [
@@ -175,26 +183,32 @@ class IndexController extends Controller
'period' => $range,
'per_period' => '0',
];
Log::debug(sprintf('Start with avg:%s, total_left_to_pay:%s, per_period:%s', $sums[$groupOrder][$currencyId]['avg'], $sums[$groupOrder][$currencyId]['total_left_to_pay'], $sums[$groupOrder][$currencyId]['per_period']));
// only fill in avg when bill is active.
if (null !== $bill['next_expected_match']) {
$avg = bcdiv(bcadd((string)$bill['amount_min'], (string)$bill['amount_max']), '2');
$avg = bcmul($avg, (string)count($bill['pay_dates']));
$sums[$groupOrder][$currencyId]['avg'] = bcadd($sums[$groupOrder][$currencyId]['avg'], $avg);
}
// only fill in total_left_to_pay when bill is not yet paid.
if (count($bill['paid_dates']) < count($bill['pay_dates'])) {
$count = count($bill['pay_dates']) - count($bill['paid_dates']);
if ($count > 0) {
$avg = bcdiv(bcadd((string)$bill['amount_min'], (string)$bill['amount_max']), '2');
$avg = bcmul($avg, (string)$count);
$sums[$groupOrder][$currencyId]['total_left_to_pay'] = bcadd($sums[$groupOrder][$currencyId]['total_left_to_pay'], $avg);
Log::debug(sprintf('next expected match is "%s", avg is now %s', $bill['next_expected_match'], $sums[$groupOrder][$currencyId]['avg']));
// only fill in total_left_to_pay when bill is not yet paid.
// #11474 and when it is expected in the current period
if (count($bill['paid_dates']) < count($bill['pay_dates'])) {
$count = count($bill['pay_dates']) - count($bill['paid_dates']);
if ($count > 0) {
$avg = bcdiv(bcadd((string)$bill['amount_min'], (string)$bill['amount_max']), '2');
$avg = bcmul($avg, (string)$count);
$sums[$groupOrder][$currencyId]['total_left_to_pay'] = bcadd($sums[$groupOrder][$currencyId]['total_left_to_pay'], $avg);
Log::debug(sprintf('Bill has %d dates that need payment, total left to pay is now %s', $count, $sums[$groupOrder][$currencyId]['total_left_to_pay']), $bill['pay_dates']);
}
}
}
$perPeriod = $this->amountPerPeriod($bill, $range);
Log::debug(sprintf('Add amount %s to per_period', $perPeriod));
// fill in per period regardless:
$sums[$groupOrder][$currencyId]['per_period'] = bcadd($sums[$groupOrder][$currencyId]['per_period'], $this->amountPerPeriod($bill, $range));
$sums[$groupOrder][$currencyId]['per_period'] = bcadd($sums[$groupOrder][$currencyId]['per_period'], $perPeriod);
}
}

View File

@@ -29,7 +29,6 @@ use FireflyIII\Support\Facades\Navigation;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\Bill;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
@@ -185,7 +184,7 @@ class ShowController extends Controller
/** @var AttachmentTransformer $transformer */
$transformer = app(AttachmentTransformer::class);
$attachments = $collection->each(
static fn (Attachment $attachment) => $transformer->transform($attachment)
$transformer->transform(...)
);
}

View File

@@ -113,7 +113,9 @@ class DebugController extends Controller
// also do some recalculations.
Artisan::call('correction:recalculates-liabilities');
AccountBalanceCalculator::recalculateAll(false);
if (true === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
AccountBalanceCalculator::recalculateAll(false);
}
try {
Artisan::call('twig:clean');

View File

@@ -41,7 +41,7 @@ class IndexController extends Controller
// translations:
$this->middleware(
function ($request, $next) {
static function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-exchange');
app('view')->share('title', (string) trans('firefly.header_exchange_rates'));

View File

@@ -24,16 +24,16 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Profile;
use FireflyIII\Support\Facades\Preferences;
use Carbon\Carbon;
use FireflyIII\Events\Security\DisabledMFA;
use FireflyIII\Events\Security\EnabledMFA;
use FireflyIII\Events\Security\MFANewBackupCodes;
use FireflyIII\Events\Security\User\UserHasDisabledMFA;
use FireflyIII\Events\Security\User\UserHasEnabledMFA;
use FireflyIII\Events\Security\User\UserHasGeneratedNewBackupCodes;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Http\Requests\ExistingTokenFormRequest;
use FireflyIII\Http\Requests\TokenFormRequest;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
@@ -71,7 +71,7 @@ class MfaController extends Controller
$this->middleware(
static function ($request, $next) {
app('view')->share('title', (string) trans('firefly.profile'));
app('view')->share('title', (string)trans('firefly.profile'));
app('view')->share('mainTitleIcon', 'fa-user');
return $next($request);
@@ -131,7 +131,7 @@ class MfaController extends Controller
// send user notification.
$user = auth()->user();
Log::channel('audit')->info(sprintf('User "%s" has generated new backup codes.', $user->email));
event(new MFANewBackupCodes($user));
event(new UserHasGeneratedNewBackupCodes($user));
return view('profile.mfa.backup-codes-post')->with(['codes' => $codes]);
@@ -150,7 +150,7 @@ class MfaController extends Controller
return redirect(route('profile.index'));
}
$subTitle = (string) trans('firefly.mfa_index_title');
$subTitle = (string)trans('firefly.mfa_index_title');
$subTitleIcon = 'fa-calculator';
return view('profile.mfa.disable-mfa')->with(['subTitle' => $subTitle, 'subTitleIcon' => $subTitleIcon, 'enabledMFA' => $enabledMFA]);
@@ -178,8 +178,8 @@ class MfaController extends Controller
$repository->setMFACode($user, null);
Preferences::mark();
session()->flash('success', (string) trans('firefly.pref_two_factor_auth_disabled'));
session()->flash('info', (string) trans('firefly.pref_two_factor_auth_remove_it'));
session()->flash('success', (string)trans('firefly.pref_two_factor_auth_disabled'));
session()->flash('info', (string)trans('firefly.pref_two_factor_auth_remove_it'));
// also logout current 2FA tokens.
$cookieName = config('google2fa.cookie_name', 'google2fa_token');
@@ -187,7 +187,7 @@ class MfaController extends Controller
// send user notification.
Log::channel('audit')->info(sprintf('User "%s" has disabled MFA', $user->email));
event(new DisabledMFA($user));
event(new UserHasDisabledMFA($user));
return redirect(route('profile.index'));
}
@@ -210,7 +210,7 @@ class MfaController extends Controller
// If FF3 already has a secret, just set the two-factor auth enabled to 1,
// and let the user continue with the existing secret.
if ($enabledMFA) {
session()->flash('info', (string) trans('firefly.2fa_already_enabled'));
session()->flash('info', (string)trans('firefly.2fa_already_enabled'));
return redirect(route('profile.index'));
}
@@ -257,13 +257,13 @@ class MfaController extends Controller
if (is_array($secret)) {
$secret = null;
}
$secret = (string) $secret;
$secret = (string)$secret;
$repository->setMFACode($user, $secret);
Preferences::delete('temp-mfa-secret');
session()->flash('success', (string) trans('firefly.saved_preferences'));
session()->flash('success', (string)trans('firefly.saved_preferences'));
Preferences::mark();
// also save the code so replay attack is prevented.
@@ -280,7 +280,7 @@ class MfaController extends Controller
// send user notification.
Log::channel('audit')->info(sprintf('User "%s" has enabled MFA', $user->email));
event(new EnabledMFA($user));
event(new UserHasEnabledMFA($user));
return redirect(route('profile.mfa.backup-codes'));
}
@@ -335,7 +335,7 @@ class MfaController extends Controller
return redirect(route('profile.index'));
}
$subTitle = (string) trans('firefly.mfa_index_title');
$subTitle = (string)trans('firefly.mfa_index_title');
$subTitleIcon = 'fa-calculator';
$enabledMFA = null !== auth()->user()->mfa_secret;

View File

@@ -52,6 +52,7 @@ use Illuminate\View\View;
use Laravel\Passport\ClientRepository;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use SensitiveParameter;
/**
* Class ProfileController.
@@ -91,7 +92,7 @@ class ProfileController extends Controller
*
* @throws FireflyException
*/
public function confirmEmailChange(UserRepositoryInterface $repository, string $token): Redirector|RedirectResponse
public function confirmEmailChange(UserRepositoryInterface $repository, #[SensitiveParameter] string $token): Redirector|RedirectResponse
{
if (!$this->internalAuth) {
throw new FireflyException(trans('firefly.external_user_mgt_disabled'));
@@ -388,7 +389,7 @@ class ProfileController extends Controller
*
* @throws FireflyException
*/
public function undoEmailChange(UserRepositoryInterface $repository, string $token, string $hash): Redirector|RedirectResponse
public function undoEmailChange(UserRepositoryInterface $repository, #[SensitiveParameter] string $token, string $hash): Redirector|RedirectResponse
{
if (!$this->internalAuth) {
throw new FireflyException(trans('firefly.external_user_mgt_disabled'));

View File

@@ -157,7 +157,7 @@ class CreateRecurringTransactions implements ShouldQueue
private function filterRecurrences(Collection $recurrences): Collection
{
return $recurrences->filter(
fn (Recurrence $recurrence): bool => $this->validRecurrence($recurrence)
$this->validRecurrence(...)
);
}

View File

@@ -25,8 +25,8 @@ declare(strict_types=1);
namespace FireflyIII\Jobs;
use Carbon\Carbon;
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
use FireflyIII\Events\Model\Subscription\SubscriptionNeedsExtensionOrRenewal;
use FireflyIII\Events\Model\Subscription\SubscriptionsAreOverdueForPayment;
use FireflyIII\Models\Bill;
use FireflyIII\Support\Facades\Navigation;
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
@@ -143,7 +143,7 @@ class WarnAboutBills implements ShouldQueue
{
$diff = $this->getDiff($bill, $field);
Log::debug('Will now send warning!');
event(new WarnUserAboutBill($bill, $field, $diff));
event(new SubscriptionNeedsExtensionOrRenewal($bill, $field, $diff));
}
public function setDate(Carbon $date): void
@@ -197,8 +197,8 @@ class WarnAboutBills implements ShouldQueue
private function sendOverdueAlerts(User $user, array $overdue): void
{
if (count($overdue) > 0) {
Log::debug(sprintf('Will now send warning about overdue bill for user #%d.', $user->id));
event(new WarnUserAboutOverdueSubscriptions($user, $overdue));
Log::debug(sprintf('Will now send warning about overdue subscription(s) for user #%d.', $user->id));
event(new SubscriptionsAreOverdueForPayment($user, $overdue));
}
}
}

View File

@@ -1,8 +1,9 @@
<?php
declare(strict_types=1);
/*
* PiggyBankEventHandler.php
* Copyright (c) 2023 james@firefly-iii.org
* CreatesPiggyBankEventForChangedAmount.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,43 +21,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Listeners\Model\PiggyBank;
namespace FireflyIII\Handlers\Events\Model;
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
use FireflyIII\Events\Model\PiggyBank\ChangedName;
use FireflyIII\Models\Account;
use FireflyIII\Events\Model\PiggyBank\PiggyBankAmountIsChanged;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleAction;
use FireflyIII\Models\TransactionGroup;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
/**
* Class PiggyBankEventHandler
*/
class PiggyBankEventHandler
class CreatesPiggyBankEventForChangedAmount implements ShouldQueue
{
public function changedPiggyBankName(ChangedName $event): void
{
// loop all accounts, collect all user's rules.
/** @var Account $account */
foreach ($event->piggyBank->accounts as $account) {
/** @var Rule $rule */
foreach ($account->user->rules as $rule) {
/** @var RuleAction $ruleAction */
foreach ($rule->ruleActions()->where('action_type', 'update_piggy')->get() as $ruleAction) {
if ($event->oldName === $ruleAction->action_value) {
$ruleAction->action_value = $event->newName;
$ruleAction->save();
}
}
}
}
}
public function changePiggyAmount(ChangedAmount $event): void
public function handle(PiggyBankAmountIsChanged $event): void
{
// find journal if group is present.
$journal = $event->transactionJournal;

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/*
* UpdatesRulesForChangedPiggyBankName.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Listeners\Model\PiggyBank;
use FireflyIII\Events\Model\PiggyBank\PiggyBankNameIsChanged;
use FireflyIII\Models\Account;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleAction;
use Illuminate\Contracts\Queue\ShouldQueue;
class UpdatesRulesForChangedPiggyBankName implements ShouldQueue
{
public function handle(PiggyBankNameIsChanged $event): void
{
// loop all accounts, collect all user's rules.
/** @var Account $account */
foreach ($event->piggyBank->accounts as $account) {
/** @var Rule $rule */
foreach ($account->user->rules as $rule) {
/** @var RuleAction $ruleAction */
foreach ($rule->ruleActions()->where('action_type', 'update_piggy')->get() as $ruleAction) {
if ($event->oldName === $ruleAction->action_value) {
$ruleAction->action_value = $event->newName;
$ruleAction->save();
}
}
}
}
}
}

View File

@@ -1,8 +1,9 @@
<?php
declare(strict_types=1);
/*
* RuleHandler.php
* Copyright (c) 2023 james@firefly-iii.org
* NotifiesUserAboutFailedRuleAction.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,24 +21,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events\Model;
namespace FireflyIII\Listeners\Model\Rule;
use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray;
use FireflyIII\Events\Model\Rule\RuleActionFailedOnObject;
use FireflyIII\Notifications\User\RuleActionFailed;
use FireflyIII\Support\Facades\Preferences;
use GuzzleHttp\Exception\ClientException;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
/**
* Class RuleHandler
*/
class RuleHandler
class NotifiesUserAboutFailedRuleAction implements ShouldQueue
{
public function ruleActionFailedOnArray(RuleActionFailedOnArray $event): void
public function handle(RuleActionFailedOnArray|RuleActionFailedOnObject $event): void
{
$ruleAction = $event->ruleAction;
$rule = $ruleAction->rule;
@@ -52,9 +49,12 @@ class RuleHandler
$error = $event->error;
$user = $ruleAction->rule->user;
$mainMessage = trans('rules.main_message', ['rule' => $rule->title, 'action' => $ruleAction->action_type, 'group' => $journal['transaction_group_id'], 'error' => $error]);
$groupTitle = $journal['description'] ?? '';
$groupLink = route('transactions.show', [$journal['transaction_group_id']]);
$groupId = is_array($journal) ? $journal['transaction_group_id'] : $journal->transaction_group_id;
$groupTitle = is_array($journal) ? ($journal['description'] ?? '') : ($journal->description ?? '');
$mainMessage = trans('rules.main_message', ['rule' => $rule->title, 'action' => $ruleAction->action_type, 'group' => $groupId, 'error' => $error]);
$groupLink = route('transactions.show', [$groupId]);
$ruleTitle = $rule->title;
$ruleLink = route('rules.edit', [$rule->id]);
$params = [$mainMessage, $groupTitle, $groupLink, $ruleTitle, $ruleLink];
@@ -65,33 +65,4 @@ class RuleHandler
Log::error(sprintf('[a] Error sending notification that the rule action failed: %s', $e->getMessage()));
}
}
public function ruleActionFailedOnObject(RuleActionFailedOnObject $event): void
{
$ruleAction = $event->ruleAction;
$rule = $ruleAction->rule;
/** @var bool $preference */
$preference = Preferences::getForUser($rule->user, 'notification_rule_action_failures', true)->data;
if (false === $preference) {
return;
}
Log::debug('Now in ruleActionFailedOnObject');
$journal = $event->journal;
$error = $event->error;
$user = $ruleAction->rule->user;
$mainMessage = trans('rules.main_message', ['rule' => $rule->title, 'action' => $ruleAction->action_type, 'group' => $journal->transaction_group_id, 'error' => $error]);
$groupTitle = $journal->description ?? '';
$groupLink = route('transactions.show', [$journal->transaction_group_id]);
$ruleTitle = $rule->title;
$ruleLink = route('rules.edit', [$rule->id]);
$params = [$mainMessage, $groupTitle, $groupLink, $ruleTitle, $ruleLink];
try {
Notification::send($user, new RuleActionFailed($params));
} catch (ClientException $e) {
Log::error(sprintf('[b] Error sending notification that the rule action failed: %s', $e->getMessage()));
}
}
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/*
* NotifiesAboutExtensionOrRenewal.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Listeners\Model\Subscription;
use Exception;
use FireflyIII\Events\Model\Subscription\SubscriptionNeedsExtensionOrRenewal;
use FireflyIII\Notifications\User\BillReminder;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesAboutExtensionOrRenewal implements ShouldQueue
{
public function handle(SubscriptionNeedsExtensionOrRenewal $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$subscription = $event->subscription;
/** @var bool $preference */
$preference = Preferences::getForUser($subscription->user, 'notification_bill_reminder', true)->data;
if (true === $preference) {
Log::debug('Subscription reminder is true!');
try {
Notification::send($subscription->user, new BillReminder($subscription, $event->field, $event->diff));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
return;
}
Log::debug('User has disabled subscription reminders.');
}
}

View File

@@ -1,8 +1,9 @@
<?php
declare(strict_types=1);
/*
* BillEventHandler.php
* Copyright (c) 2022 james@firefly-iii.org
* NotifiesAboutOverdueSubscription.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,35 +21,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
namespace FireflyIII\Listeners\Model\Subscription;
use Exception;
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
use FireflyIII\Events\Model\Subscription\SubscriptionsAreOverdueForPayment;
use FireflyIII\Models\Bill;
use FireflyIII\Notifications\User\BillReminder;
use FireflyIII\Notifications\User\SubscriptionsOverdueReminder;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use function Safe\json_encode;
/**
* Class BillEventHandler
*/
class BillEventHandler
class NotifiesAboutOverdueSubscriptions implements ShouldQueue
{
public function warnAboutOverdueSubscriptions(WarnUserAboutOverdueSubscriptions $event): void
public function handle(SubscriptionsAreOverdueForPayment $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
// make sure user does not get the warning twice.
$overdue = $event->overdue;
$user = $event->user;
$toBeWarned = [];
Log::debug(sprintf('%d bills to warn about.', count($overdue)));
Log::debug(sprintf('%d subscriptions to warn about.', count($overdue)));
foreach ($overdue as $item) {
/** @var Bill $bill */
$bill = $item['bill'];
@@ -62,12 +55,12 @@ class BillEventHandler
$toBeWarned[] = $item;
}
unset($bill);
Log::debug(sprintf('Now %d bills to warn about.', count($toBeWarned)));
Log::debug(sprintf('Now %d subscription(s) to warn about.', count($toBeWarned)));
/** @var bool $sendNotification */
$sendNotification = Preferences::getForUser($user, 'notification_bill_reminder', true)->data;
if (false === $sendNotification) {
Log::debug('User has disabled bill reminders.');
Log::debug('User has disabled subscription reminders.');
return;
}
@@ -105,39 +98,4 @@ class BillEventHandler
}
public function warnAboutBill(WarnUserAboutBill $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$bill = $event->bill;
/** @var bool $preference */
$preference = Preferences::getForUser($bill->user, 'notification_bill_reminder', true)->data;
if (true === $preference) {
Log::debug('Bill reminder is true!');
try {
Notification::send($bill->user, new BillReminder($bill, $event->field, $event->diff));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
return;
}
Log::debug('User has disabled bill reminders.');
}
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/*
* NotifiesOwnerAboutUnknownUser.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Listeners\Security\System;
use Exception;
use FireflyIII\Events\Security\System\UnknownUserTriedLogin;
use FireflyIII\Notifications\Admin\UnknownUserLoginAttempt;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesOwnerAboutUnknownUser
{
public function handle(UnknownUserTriedLogin $event): void
{
try {
$owner = new OwnerNotifiable();
Notification::send($owner, new UnknownUserLoginAttempt($event->address));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/*
* NotifiesUserAboutDisabledMFA.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasDisabledMFA;
use FireflyIII\Notifications\Security\DisabledMFANotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutDisabledMFA implements ShouldQueue
{
public function handle(UserHasDisabledMFA $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$user = $event->user;
try {
Notification::send($user, new DisabledMFANotification($user));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
/*
* NotifiesUserAboutEnabledMFA.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasEnabledMFA;
use FireflyIII\Notifications\Security\EnabledMFANotification;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutEnabledMFA
{
public function handle(UserHasEnabledMFA $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$user = $event->user;
try {
Notification::send($user, new EnabledMFANotification($user));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/*
* NotifiesUserAboutFailedLogin.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserFailedLoginAttempt;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutFailedLogin
{
public function handle(UserFailedLoginAttempt $event): void
{
try {
Notification::send($event->user, new \FireflyIII\Notifications\Security\UserFailedLoginAttempt($event->user));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/*
* NotifiesUserAboutFewCodesLeft.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasFewMFABackupCodesLeft;
use FireflyIII\Notifications\Security\MFABackupFewLeftNotification;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutFewCodesLeft
{
public function handle(UserHasFewMFABackupCodesLeft $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$user = $event->user;
$count = $event->count;
try {
Notification::send($user, new MFABackupFewLeftNotification($user, $count));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
/*
* NotifiesUserAboutNewBackupCodes.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasGeneratedNewBackupCodes;
use FireflyIII\Notifications\Security\NewBackupCodesNotification;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutNewBackupCodes
{
public function handle(UserHasGeneratedNewBackupCodes $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$user = $event->user;
try {
Notification::send($user, new NewBackupCodesNotification($user));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
/*
* NotifiesUserAboutNoCodesLeft.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasNoMFABackupCodesLeft;
use FireflyIII\Notifications\Security\MFABackupNoLeftNotification;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutNoCodesLeft
{
public function handle(UserHasNoMFABackupCodesLeft $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$user = $event->user;
try {
Notification::send($user, new MFABackupNoLeftNotification($user));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/*
* NotifiesUserAboutRepeatedMFAFailures.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserKeepsFailingMFA;
use FireflyIII\Notifications\Security\MFAManyFailedAttemptsNotification;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutRepeatedMFAFailures
{
public function handle(UserKeepsFailingMFA $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$user = $event->user;
$count = $event->count;
try {
Notification::send($user, new MFAManyFailedAttemptsNotification($user, $count));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
/*
* NotifiesUserAboutUsedBackupCode.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasUsedBackupCode;
use FireflyIII\Notifications\Security\MFAUsedBackupCodeNotification;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutUsedBackupCode
{
public function handle(UserHasUsedBackupCode $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$user = $event->user;
try {
Notification::send($user, new MFAUsedBackupCodeNotification($user));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
}

View File

@@ -53,6 +53,6 @@ class AccountMeta extends Model
protected function data(): Attribute
{
return Attribute::make(get: fn (mixed $value): string => (string)json_decode((string)$value, true), set: fn (mixed $value): array => ['data' => json_encode($value)]);
return Attribute::make(get: static fn (mixed $value): string => (string)json_decode((string)$value, true), set: static fn (mixed $value): array => ['data' => json_encode($value)]);
}
}

View File

@@ -103,16 +103,16 @@ class AvailableBudget extends Model
protected function endDate(): Attribute
{
return Attribute::make(
get: fn (string $value): Carbon => Carbon::parse($value),
set: fn (Carbon $value): string => $value->format('Y-m-d'),
get: static fn (string $value): Carbon => Carbon::parse($value),
set: static fn (Carbon $value): string => $value->format('Y-m-d'),
);
}
protected function startDate(): Attribute
{
return Attribute::make(
get: fn (string $value): Carbon => Carbon::parse($value),
set: fn (Carbon $value): string => $value->format('Y-m-d'),
get: static fn (string $value): Carbon => Carbon::parse($value),
set: static fn (Carbon $value): string => $value->format('Y-m-d'),
);
}

View File

@@ -52,6 +52,6 @@ class Configuration extends Model
*/
protected function data(): Attribute
{
return Attribute::make(get: fn ($value): mixed => json_decode((string)$value), set: fn ($value): array => ['data' => json_encode($value)]);
return Attribute::make(get: static fn ($value): mixed => json_decode((string)$value), set: static fn ($value): array => ['data' => json_encode($value)]);
}
}

View File

@@ -113,7 +113,7 @@ class Rule extends Model
protected function description(): Attribute
{
return Attribute::make(set: fn ($value): array => ['description' => e($value)]);
return Attribute::make(set: static fn ($value): array => ['description' => e($value)]);
}
protected function order(): Attribute

View File

@@ -57,7 +57,7 @@ class TransactionJournalMeta extends Model
protected function data(): Attribute
{
return Attribute::make(get: fn ($value): mixed => json_decode((string)$value, false), set: function ($value): array {
return Attribute::make(get: static fn ($value): mixed => json_decode((string)$value, false), set: static function ($value): array {
$data = json_encode($value);
return ['data' => $data, 'hash' => hash('sha256', $data)];

View File

@@ -49,7 +49,7 @@ class AppServiceProvider extends ServiceProvider
$headers = [
'Cache-Control' => 'no-store',
];
$uuid = (string) request()->header('X-Trace-Id');
$uuid = (string)request()->header('X-Trace-Id');
if ('' !== trim($uuid) && (1 === preg_match('/^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$/i', trim($uuid)))) {
$headers['X-Trace-Id'] = $uuid;
}

View File

@@ -27,12 +27,6 @@ use FireflyIII\Events\ActuallyLoggedIn;
use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Events\DestroyedTransactionGroup;
use FireflyIII\Events\DetectedNewIPAddress;
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
use FireflyIII\Events\Model\PiggyBank\ChangedName;
use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray;
use FireflyIII\Events\Model\Rule\RuleActionFailedOnObject;
use FireflyIII\Events\Model\TransactionGroup\TriggeredStoredTransactionGroup;
use FireflyIII\Events\NewVersionAvailable;
use FireflyIII\Events\Preferences\UserGroupChangedPrimaryCurrency;
@@ -41,15 +35,6 @@ use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Events\RequestedReportOnJournals;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\RequestedVersionCheckStatus;
use FireflyIII\Events\Security\DisabledMFA;
use FireflyIII\Events\Security\EnabledMFA;
use FireflyIII\Events\Security\MFABackupFewLeft;
use FireflyIII\Events\Security\MFABackupNoLeft;
use FireflyIII\Events\Security\MFAManyFailedAttempts;
use FireflyIII\Events\Security\MFANewBackupCodes;
use FireflyIII\Events\Security\MFAUsedBackupCode;
use FireflyIII\Events\Security\UnknownUserAttemptedLogin;
use FireflyIII\Events\Security\UserAttemptedLogin;
use FireflyIII\Events\StoredAccount;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Events\Test\OwnerTestNotificationChannel;
@@ -73,145 +58,145 @@ class EventServiceProvider extends ServiceProvider
protected $listen
= [
// is a User related event.
RegisteredUser::class => [
RegisteredUser::class => [
'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationMail',
'FireflyIII\Handlers\Events\UserEventHandler@sendAdminRegistrationNotification',
'FireflyIII\Handlers\Events\UserEventHandler@attachUserRole',
'FireflyIII\Handlers\Events\UserEventHandler@createGroupMembership',
'FireflyIII\Handlers\Events\UserEventHandler@createExchangeRates',
],
UserAttemptedLogin::class => [
'FireflyIII\Handlers\Events\UserEventHandler@sendLoginAttemptNotification',
],
// UserAttemptedLogin::class => [
// 'FireflyIII\Handlers\Events\UserEventHandler@sendLoginAttemptNotification',
// ],
// is a User related event.
Login::class => [
Login::class => [
'FireflyIII\Handlers\Events\UserEventHandler@checkSingleUserIsAdmin',
'FireflyIII\Handlers\Events\UserEventHandler@demoUserBackToEnglish',
],
ActuallyLoggedIn::class => [
ActuallyLoggedIn::class => [
'FireflyIII\Handlers\Events\UserEventHandler@storeUserIPAddress',
],
DetectedNewIPAddress::class => [
DetectedNewIPAddress::class => [
'FireflyIII\Handlers\Events\UserEventHandler@notifyNewIPAddress',
],
RequestedVersionCheckStatus::class => [
RequestedVersionCheckStatus::class => [
'FireflyIII\Handlers\Events\VersionCheckEventHandler@checkForUpdates',
],
RequestedReportOnJournals::class => [
RequestedReportOnJournals::class => [
'FireflyIII\Handlers\Events\AutomationHandler@reportJournals',
],
// is a User related event.
RequestedNewPassword::class => [
RequestedNewPassword::class => [
'FireflyIII\Handlers\Events\UserEventHandler@sendNewPassword',
],
UserTestNotificationChannel::class => [
UserTestNotificationChannel::class => [
'FireflyIII\Handlers\Events\UserEventHandler@sendTestNotification',
],
// is a User related event.
UserChangedEmail::class => [
UserChangedEmail::class => [
'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeConfirmMail',
'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeUndoMail',
],
// admin related
OwnerTestNotificationChannel::class => [
OwnerTestNotificationChannel::class => [
'FireflyIII\Handlers\Events\AdminEventHandler@sendTestNotification',
],
NewVersionAvailable::class => [
NewVersionAvailable::class => [
'FireflyIII\Handlers\Events\AdminEventHandler@sendNewVersion',
],
InvitationCreated::class => [
InvitationCreated::class => [
'FireflyIII\Handlers\Events\AdminEventHandler@sendInvitationNotification',
'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationInvite',
],
UnknownUserAttemptedLogin::class => [
'FireflyIII\Handlers\Events\AdminEventHandler@sendLoginAttemptNotification',
],
// UnknownUserAttemptedLogin::class => [
// 'FireflyIII\Handlers\Events\AdminEventHandler@sendLoginAttemptNotification',
// ],
// is a Transaction Journal related event.
StoredTransactionGroup::class => [
StoredTransactionGroup::class => [
'FireflyIII\Handlers\Events\StoredGroupEventHandler@runAllHandlers',
],
TriggeredStoredTransactionGroup::class => [
TriggeredStoredTransactionGroup::class => [
'FireflyIII\Handlers\Events\StoredGroupEventHandler@triggerRulesManually',
],
// is a Transaction Journal related event.
UpdatedTransactionGroup::class => [
UpdatedTransactionGroup::class => [
'FireflyIII\Handlers\Events\UpdatedGroupEventHandler@runAllHandlers',
],
DestroyedTransactionGroup::class => [
DestroyedTransactionGroup::class => [
'FireflyIII\Handlers\Events\DestroyedGroupEventHandler@runAllHandlers',
],
// API related events:
AccessTokenCreated::class => [
AccessTokenCreated::class => [
'FireflyIII\Handlers\Events\APIEventHandler@accessTokenCreated',
],
// Webhook related event:
RequestedSendWebhookMessages::class => [
RequestedSendWebhookMessages::class => [
'FireflyIII\Handlers\Events\WebhookEventHandler@sendWebhookMessages',
],
// account related events:
StoredAccount::class => [
StoredAccount::class => [
'FireflyIII\Handlers\Events\StoredAccountEventHandler@recalculateCredit',
],
UpdatedAccount::class => [
UpdatedAccount::class => [
'FireflyIII\Handlers\Events\UpdatedAccountEventHandler@recalculateCredit',
],
// bill related events:
WarnUserAboutBill::class => [
'FireflyIII\Handlers\Events\BillEventHandler@warnAboutBill',
],
WarnUserAboutOverdueSubscriptions::class => [
'FireflyIII\Handlers\Events\BillEventHandler@warnAboutOverdueSubscriptions',
],
// subscription related events:
// SubscriptionNeedsExtensionOrRenewal::class => [
// 'FireflyIII\Handlers\Events\BillEventHandler@warnAboutBill',
// ],
// WarnUserAboutOverdueSubscriptions::class => [
// 'FireflyIII\Handlers\Events\BillEventHandler@warnAboutOverdueSubscriptions',
// ],
// audit log events:
TriggeredAuditLog::class => [
TriggeredAuditLog::class => [
'FireflyIII\Handlers\Events\AuditEventHandler@storeAuditEvent',
],
// piggy bank related events:
ChangedAmount::class => [
'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changePiggyAmount',
],
ChangedName::class => [
'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changedPiggyBankName',
],
// PiggyBankAmountIsChanged::class => [
// 'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changePiggyAmount',
// ],
// ChangedName::class => [
// 'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changedPiggyBankName',
// ],
// rule actions
RuleActionFailedOnArray::class => [
'FireflyIII\Handlers\Events\Model\RuleHandler@ruleActionFailedOnArray',
],
RuleActionFailedOnObject::class => [
'FireflyIII\Handlers\Events\Model\RuleHandler@ruleActionFailedOnObject',
],
// RuleActionFailedOnArray::class => [
// 'FireflyIII\Handlers\Events\Model\RuleHandler@ruleActionFailedOnArray',
// ],
// RuleActionFailedOnObject::class => [
// 'FireflyIII\Handlers\Events\Model\RuleHandler@ruleActionFailedOnObject',
// ],
// security related
EnabledMFA::class => [
'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFAEnabledMail',
],
DisabledMFA::class => [
'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFADisabledMail',
],
MFANewBackupCodes::class => [
'FireflyIII\Handlers\Events\Security\MFAHandler@sendNewMFABackupCodesMail',
],
MFAUsedBackupCode::class => [
'FireflyIII\Handlers\Events\Security\MFAHandler@sendUsedBackupCodeMail',
],
MFABackupFewLeft::class => [
'FireflyIII\Handlers\Events\Security\MFAHandler@sendBackupFewLeftMail',
],
MFABackupNoLeft::class => [
'FireflyIII\Handlers\Events\Security\MFAHandler@sendBackupNoLeftMail',
],
MFAManyFailedAttempts::class => [
'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFAFailedAttemptsMail',
],
// EnabledMFA::class => [
// 'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFAEnabledMail',
// ],
// DisabledMFA::class => [
// 'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFADisabledMail',
// ],
// MFANewBackupCodes::class => [
// 'FireflyIII\Handlers\Events\Security\MFAHandler@sendNewMFABackupCodesMail',
// ],
// MFAUsedBackupCode::class => [
// 'FireflyIII\Handlers\Events\Security\MFAHandler@sendUsedBackupCodeMail',
// ],
// MFABackupFewLeft::class => [
// 'FireflyIII\Handlers\Events\Security\MFAHandler@sendBackupFewLeftMail',
// ],
// MFABackupNoLeft::class => [
// 'FireflyIII\Handlers\Events\Security\MFAHandler@sendBackupNoLeftMail',
// ],
// MFAManyFailedAttempts::class => [
// 'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFAFailedAttemptsMail',
// ],
// preferences
UserGroupChangedPrimaryCurrency::class => [
UserGroupChangedPrimaryCurrency::class => [
'FireflyIII\Handlers\Events\PreferencesEventHandler@resetPrimaryCurrencyAmounts',
],
];

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Providers;
use FireflyIII\Support\Search\OperatorQuerySearch;
use FireflyIII\Support\Search\QueryParser\GdbotsQueryParser;
use FireflyIII\Support\Search\QueryParser\QueryParser;
use FireflyIII\Support\Search\QueryParser\QueryParserInterface;
use FireflyIII\Support\Search\SearchInterface;
@@ -49,16 +48,7 @@ class SearchServiceProvider extends ServiceProvider
public function register(): void
{
$this->app->bind(
static function (): QueryParserInterface {
return app(QueryParser::class);
// 2025-12-20 ignore this setting.
// $implementation = config('search.query_parser');
//
// return match ($implementation) {
// 'new' => app(QueryParser::class),
// default => app(GdbotsQueryParser::class),
// };
}
static fn (): QueryParserInterface => app(QueryParser::class)
);
$this->app->bind(

View File

@@ -514,7 +514,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
{
$query = sprintf('%%%s%%', $query);
return $this->user->bills()->whereLike('name', $query)->take($limit)->get();
return $this->user->bills()->orderBy('name', 'ASC')->whereLike('name', $query)->take($limit)->get();
}
public function setObjectGroup(Bill $bill, string $objectGroupTitle): Bill

View File

@@ -331,7 +331,7 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
public function searchCategory(string $query, int $limit): Collection
{
$search = $this->user->categories();
$search = $this->user->categories()->orderBy('name', 'ASC');
if ('' !== $query) {
$search->whereLike('name', sprintf('%%%s%%', $query));
}

View File

@@ -361,7 +361,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
public function searchCurrency(string $search, int $limit): Collection
{
$query = TransactionCurrency::where('enabled', true);
$query = TransactionCurrency::where('enabled', true)->orderBy('code', 'ASC');
if ('' !== $search) {
$query->whereLike('name', sprintf('%%%s%%', $search));
}

View File

@@ -55,12 +55,12 @@ class ExchangeRateRepository implements ExchangeRateRepositoryInterface, UserGro
// orderBy('date', 'DESC')->toRawSql();
return
$this->userGroup->currencyExchangeRates()
->where(function (Builder $q1) use ($from, $to): void {
$q1->where(function (Builder $q) use ($from, $to): void {
->where(static function (Builder $q1) use ($from, $to): void {
$q1->where(static function (Builder $q) use ($from, $to): void {
$q->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id)
;
})->orWhere(function (Builder $q) use ($from, $to): void {
})->orWhere(static function (Builder $q) use ($from, $to): void {
$q->where('from_currency_id', $to->id)
->where('to_currency_id', $from->id)
;

View File

@@ -182,6 +182,7 @@ class JournalRepository implements JournalRepositoryInterface, UserGroupInterfac
{
$query = $this->user->transactionJournals()
->orderBy('date', 'DESC')
->orderBy('description', 'ASC')
;
if ('' !== $search) {
$query->whereLike('description', sprintf('%%%s%%', $search));

View File

@@ -25,8 +25,8 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\PiggyBank;
use Exception;
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
use FireflyIII\Events\Model\PiggyBank\ChangedName;
use FireflyIII\Events\Model\PiggyBank\PiggyBankAmountIsChanged;
use FireflyIII\Events\Model\PiggyBank\PiggyBankNameIsChanged;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\PiggyBankFactory;
use FireflyIII\Models\Account;
@@ -68,7 +68,7 @@ trait ModifiesPiggyBanks
{
$currentAmount = $this->getCurrentAmount($piggyBank, $account);
$pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot;
$pivot->current_amount = bcsub((string) $currentAmount, $amount);
$pivot->current_amount = bcsub((string)$currentAmount, $amount);
$pivot->native_current_amount = null;
// also update native_current_amount.
@@ -82,7 +82,7 @@ trait ModifiesPiggyBanks
$pivot->save();
Log::debug('ChangedAmount: removeAmount [a]: Trigger change for negative amount.');
event(new ChangedAmount($piggyBank, bcmul($amount, '-1'), $journal, null));
event(new PiggyBankAmountIsChanged($piggyBank, bcmul($amount, '-1'), $journal, null));
return true;
}
@@ -91,7 +91,7 @@ trait ModifiesPiggyBanks
{
$currentAmount = $this->getCurrentAmount($piggyBank, $account);
$pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot;
$pivot->current_amount = bcadd((string) $currentAmount, $amount);
$pivot->current_amount = bcadd((string)$currentAmount, $amount);
$pivot->native_current_amount = null;
// also update native_current_amount.
@@ -105,7 +105,7 @@ trait ModifiesPiggyBanks
$pivot->save();
Log::debug('ChangedAmount: addAmount [b]: Trigger change for positive amount.');
event(new ChangedAmount($piggyBank, $amount, $journal, null));
event(new PiggyBankAmountIsChanged($piggyBank, $amount, $journal, null));
return true;
}
@@ -123,13 +123,13 @@ trait ModifiesPiggyBanks
if (0 !== bccomp($piggyBank->target_amount, '0')) {
$leftToSave = bcsub($piggyBank->target_amount, (string) $savedSoFar);
$maxAmount = 1 === bccomp((string) $leftOnAccount, $leftToSave) ? $leftToSave : $leftOnAccount;
$leftToSave = bcsub($piggyBank->target_amount, (string)$savedSoFar);
$maxAmount = 1 === bccomp((string)$leftOnAccount, $leftToSave) ? $leftToSave : $leftOnAccount;
Log::debug(sprintf('Left to save: %s', $leftToSave));
Log::debug(sprintf('Maximum amount: %s', $maxAmount));
}
$compare = bccomp($amount, (string) $maxAmount);
$compare = bccomp($amount, (string)$maxAmount);
$result = $compare <= 0;
Log::debug(sprintf('Compare <= 0? %d, so canAddAmount is %s', $compare, var_export($result, true)));
@@ -141,7 +141,7 @@ trait ModifiesPiggyBanks
{
$savedSoFar = $this->getCurrentAmount($piggyBank, $account);
return bccomp($amount, (string) $savedSoFar) <= 0;
return bccomp($amount, (string)$savedSoFar) <= 0;
}
/**
@@ -178,11 +178,11 @@ trait ModifiesPiggyBanks
if (-1 === bccomp($difference, '0')) {
Log::debug('ChangedAmount: addAmount [c]: Trigger change for negative amount.');
event(new ChangedAmount($piggyBank, $difference, null, null));
event(new PiggyBankAmountIsChanged($piggyBank, $difference, null, null));
}
if (1 === bccomp($difference, '0')) {
Log::debug('ChangedAmount: addAmount [d]: Trigger change for positive amount.');
event(new ChangedAmount($piggyBank, $difference, null, null));
event(new PiggyBankAmountIsChanged($piggyBank, $difference, null, null));
}
return $piggyBank;
@@ -234,9 +234,9 @@ trait ModifiesPiggyBanks
// if the piggy bank is now smaller than the sum of the money saved,
// remove money from all accounts until the piggy bank is the right amount.
$currentAmount = $this->getCurrentAmount($piggyBank);
if (1 === bccomp((string) $currentAmount, (string)$piggyBank->target_amount) && 0 !== bccomp((string)$piggyBank->target_amount, '0')) {
if (1 === bccomp((string)$currentAmount, (string)$piggyBank->target_amount) && 0 !== bccomp((string)$piggyBank->target_amount, '0')) {
Log::debug(sprintf('Current amount is %s, target amount is %s', $currentAmount, $piggyBank->target_amount));
$difference = bcsub((string)$piggyBank->target_amount, (string) $currentAmount);
$difference = bcsub((string)$piggyBank->target_amount, (string)$currentAmount);
// an amount will be removed, create "negative" event:
// Log::debug(sprintf('ChangedAmount: is triggered with difference "%s"', $difference));
@@ -283,7 +283,7 @@ trait ModifiesPiggyBanks
private function updateProperties(PiggyBank $piggyBank, array $data): PiggyBank
{
if (array_key_exists('name', $data) && '' !== $data['name']) {
event(new ChangedName($piggyBank, $piggyBank->name, $data['name']));
event(new PiggyBankNameIsChanged($piggyBank, $piggyBank->name, $data['name']));
$piggyBank->name = $data['name'];
}
if (array_key_exists('transaction_currency_id', $data) && is_int($data['transaction_currency_id'])) {

View File

@@ -24,7 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\PiggyBank;
use Carbon\Carbon;
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
use FireflyIII\Events\Model\PiggyBank\PiggyBankAmountIsChanged;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\PiggyBankFactory;
use FireflyIII\Models\Account;
@@ -431,7 +431,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
'objectGroups',
]
)
->orderBy('piggy_banks.order', 'ASC')->distinct()
->orderBy('piggy_banks.order', 'ASC')->orderBy('piggy_banks.name', 'ASC')->distinct()
;
if ('' !== $query) {
$search->whereLike('piggy_banks.name', sprintf('%%%s%%', $query));
@@ -448,7 +448,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
$piggyBank->piggyBankEvents()->delete();
foreach ($piggyBank->accounts as $account) {
if (0 !== bccomp('0', (string) $account->pivot->current_amount)) {
event(new ChangedAmount($piggyBank, $account->pivot->current_amount, null, null));
event(new PiggyBankAmountIsChanged($piggyBank, $account->pivot->current_amount, null, null));
}
}
}

View File

@@ -39,6 +39,7 @@ use Illuminate\Database\QueryException;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Override;
use SensitiveParameter;
/**
* Class UserRepository.
@@ -74,7 +75,7 @@ class UserRepository implements UserRepositoryInterface
return true;
}
public function changePassword(User $user, string $password): bool
public function changePassword(User $user, #[SensitiveParameter] string $password): bool
{
$user->password = bcrypt($password);
$user->save();
@@ -254,7 +255,7 @@ class UserRepository implements UserRepositoryInterface
if (!$user instanceof User) {
throw new FireflyException('User is not a User object.');
}
$now = today(config('app.timezone'));
$now = now(config('app.timezone'));
$now->addDays(2);
$invitee = new InvitedUser();
$invitee->user()->associate($user);

View File

@@ -30,6 +30,7 @@ use FireflyIII\Models\UserGroup;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
use SensitiveParameter;
/**
* Interface UserRepositoryInterface.
@@ -64,7 +65,7 @@ interface UserRepositoryInterface
/**
* @return mixed
*/
public function changePassword(User $user, string $password);
public function changePassword(User $user, #[SensitiveParameter] string $password);
public function changeStatus(User $user, bool $isBlocked, string $code): bool;

View File

@@ -25,12 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Services\Internal\Destroy;
use Illuminate\Support\Facades\Log;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalLink;
use FireflyIII\Models\TransactionJournalMeta;
use Illuminate\Support\Facades\DB;
/**
* Class JournalDestroyService
@@ -41,51 +36,6 @@ class JournalDestroyService
{
Log::debug(sprintf('Now in %s', __METHOD__));
/** @var Transaction $transaction */
foreach ($journal->transactions()->get() as $transaction) {
Log::debug(sprintf('Will now delete transaction #%d', $transaction->id));
$transaction->delete();
}
// also delete journal_meta entries.
/** @var TransactionJournalMeta $meta */
foreach ($journal->transactionJournalMeta()->get() as $meta) {
Log::debug(sprintf('Will now delete meta-entry #%d', $meta->id));
$meta->delete();
}
// also delete attachments.
/** @var Attachment $attachment */
foreach ($journal->attachments()->get() as $attachment) {
$attachment->delete();
}
// delete all from 'budget_transaction_journal'
DB::table('budget_transaction_journal')
->where('transaction_journal_id', $journal->id)->delete()
;
// delete all from 'category_transaction_journal'
DB::table('category_transaction_journal')
->where('transaction_journal_id', $journal->id)->delete()
;
// delete all from 'tag_transaction_journal'
DB::table('tag_transaction_journal')
->where('transaction_journal_id', $journal->id)->delete()
;
// delete all links:
TransactionJournalLink::where('source_id', $journal->id)->delete();
TransactionJournalLink::where('destination_id', $journal->id)->delete();
// delete all notes
$journal->notes()->delete();
// update events
// TODO move to repository
$journal->piggyBankEvents()->update(['transaction_journal_id' => null]);
$journal->delete();
// delete group, if group is empty:

View File

@@ -27,6 +27,7 @@ use Illuminate\Support\Facades\Log;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use SensitiveParameter;
/**
* Class PwndVerifierV2.
@@ -36,7 +37,7 @@ class PwndVerifierV2 implements Verifier
/**
* Verify the given password against (some) service.
*/
public function validPassword(string $password): bool
public function validPassword(#[SensitiveParameter] string $password): bool
{
// Yes SHA1 is unsafe but in this context its fine.
$hash = sha1($password);

View File

@@ -23,6 +23,8 @@ declare(strict_types=1);
namespace FireflyIII\Services\Password;
use SensitiveParameter;
/**
* Interface Verifier.
*/
@@ -31,5 +33,5 @@ interface Verifier
/**
* Verify the given password against (some) service.
*/
public function validPassword(string $password): bool;
public function validPassword(#[SensitiveParameter] string $password): bool;
}

Some files were not shown because too many files have changed in this diff Show More