Compare commits

...

145 Commits

Author SHA1 Message Date
github-actions[bot]
cf2ed2b24e Merge pull request #12082 from firefly-iii/release-1775245340
🤖 Automatically merge the PR into the develop branch.
2026-04-03 21:42:29 +02:00
JC5
db7dace881 🤖 Auto commit for release 'develop' on 2026-04-03 2026-04-03 21:42:20 +02:00
James Cole
ffe8357124 Enable and fix rule. 2026-04-03 21:30:20 +02:00
James Cole
43516885ae There is no vulnerability here *makes Jedi movements. 2026-04-03 21:28:55 +02:00
James Cole
17f2008eb9 Let's see what happens when we tell AI there is no issue here. *makes Jedi movements 2026-04-03 21:27:51 +02:00
James Cole
154bc2afdc Fix phpstan issue. 2026-04-03 21:27:24 +02:00
James Cole
8f1322c5db Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-04-03 21:23:51 +02:00
James Cole
f01a56f467 Fix https://github.com/firefly-iii/firefly-iii/issues/12081 2026-04-03 21:23:45 +02:00
github-actions[bot]
d134fb42ab Merge pull request #12079 from firefly-iii/release-1775194287
🤖 Automatically merge the PR into the develop branch.
2026-04-03 07:31:35 +02:00
JC5
ecab686ac9 🤖 Auto commit for release 'develop' on 2026-04-03 2026-04-03 07:31:27 +02:00
James Cole
4435231ffb Merge pull request #12049 from firefly-iii/dependabot/npm_and_yarn/develop/i18next-26.0.1
Bump i18next from 25.10.10 to 26.0.1
2026-04-03 07:25:54 +02:00
James Cole
40246caaa8 Fix https://github.com/firefly-iii/firefly-iii/issues/12070 2026-04-02 05:47:42 +02:00
github-actions[bot]
1ac6949f95 Merge pull request #12068 from firefly-iii/release-1775021292
🤖 Automatically merge the PR into the develop branch.
2026-04-01 07:28:18 +02:00
JC5
43acafb7a7 🤖 Auto commit for release 'develop' on 2026-04-01 2026-04-01 07:28:12 +02:00
James Cole
ca85a4c034 Catch group order 2026-04-01 07:23:14 +02:00
dependabot[bot]
8e393066b8 Bump i18next from 25.10.10 to 26.0.1
Bumps [i18next](https://github.com/i18next/i18next) from 25.10.10 to 26.0.1.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v25.10.10...v26.0.1)

---
updated-dependencies:
- dependency-name: i18next
  dependency-version: 26.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-01 04:00:19 +00:00
github-actions[bot]
22752559e1 Merge pull request #12067 from firefly-iii/release-1775015934
🤖 Automatically merge the PR into the develop branch.
2026-04-01 05:59:03 +02:00
JC5
a341cae6bb 🤖 Auto commit for release 'develop' on 2026-04-01 2026-04-01 05:58:54 +02:00
James Cole
64035a71ea Fix validation for piggy banks. 2026-04-01 05:52:29 +02:00
James Cole
074ca1756d Fix https://github.com/firefly-iii/firefly-iii/issues/12066 2026-04-01 05:47:24 +02:00
James Cole
0aa73ccf96 Fix https://github.com/firefly-iii/firefly-iii/issues/12063 2026-03-31 20:13:49 +02:00
James Cole
85c37d8812 Fix https://github.com/firefly-iii/firefly-iii/issues/12056 2026-03-30 18:21:48 +02:00
github-actions[bot]
16eb2ca4ca Merge pull request #12051 from firefly-iii/release-1774843626
🤖 Automatically merge the PR into the develop branch.
2026-03-30 06:07:15 +02:00
JC5
f491155f9b 🤖 Auto commit for release 'develop' on 2026-03-30 2026-03-30 06:07:06 +02:00
github-actions[bot]
c5706e95b7 Merge pull request #12048 from firefly-iii/release-1774809887
🤖 Automatically merge the PR into the develop branch.
2026-03-29 20:44:53 +02:00
JC5
fdabb2c994 🤖 Auto commit for release 'develop' on 2026-03-29 2026-03-29 20:44:47 +02:00
James Cole
615fa733e6 Add extra debug. 2026-03-29 20:39:23 +02:00
github-actions[bot]
d3fc8673d3 Merge pull request #12047 from firefly-iii/release-1774809181
🤖 Automatically merge the PR into the develop branch.
2026-03-29 20:33:10 +02:00
JC5
db156ffcf2 🤖 Auto commit for release 'develop' on 2026-03-29 2026-03-29 20:33:01 +02:00
James Cole
59510a9acc Catch big group correction that does not need a balance correction. 2026-03-29 20:27:08 +02:00
James Cole
7f9640087e Fix https://github.com/firefly-iii/firefly-iii/issues/12043 2026-03-29 16:53:55 +02:00
github-actions[bot]
29c51ad0e2 Merge pull request #12046 from firefly-iii/release-1774795569
🤖 Automatically merge the PR into the develop branch.
2026-03-29 16:46:17 +02:00
JC5
bf8c40d502 🤖 Auto commit for release 'develop' on 2026-03-29 2026-03-29 16:46:09 +02:00
James Cole
99912483de Add some debug for https://github.com/orgs/firefly-iii/discussions/12044 2026-03-29 16:38:49 +02:00
James Cole
a3f4ab9b1b Validate if amount can be added. 2026-03-27 07:05:16 +01:00
James Cole
953fe7d9eb Fix https://github.com/firefly-iii/firefly-iii/issues/12034 2026-03-27 05:23:22 +01:00
github-actions[bot]
41a7890c7a Merge pull request #12040 from firefly-iii/release-1774582734
🤖 Automatically merge the PR into the develop branch.
2026-03-27 04:39:02 +01:00
JC5
b8e07c1df4 🤖 Auto commit for release 'develop' on 2026-03-27 2026-03-27 04:38:55 +01:00
James Cole
2a3d9001d1 Merge branch 'main' into develop 2026-03-27 04:33:18 +01:00
James Cole
bc7453e204 Update changelog template. 2026-03-27 04:33:03 +01:00
James Cole
8c9ad9da83 Improve running balance correction. 2026-03-27 04:32:41 +01:00
James Cole
c8bd8d5113 Fix https://github.com/firefly-iii/firefly-iii/issues/12035 2026-03-26 20:13:14 +01:00
James Cole
8ce5429e06 Merge pull request #12015 from firefly-iii/dependabot/composer/develop/laravel-notification-channels/pushover-5.0.0
Bump laravel-notification-channels/pushover from 4.1.2 to 5.0.0
2026-03-26 10:36:30 +01:00
James Cole
c1f8fb2f45 Merge pull request #12033 from firefly-iii/dependabot/npm_and_yarn/npm_and_yarn-66413a1f6e 2026-03-26 04:19:12 +01:00
dependabot[bot]
5fb4330c20 Bump picomatch in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [picomatch](https://github.com/micromatch/picomatch).


Updates `picomatch` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2)

---
updated-dependencies:
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-25 22:06:01 +00:00
James Cole
485eb224d2 Fix https://github.com/firefly-iii/firefly-iii/issues/12030 2026-03-25 20:39:12 +01:00
Sander Dorigo
de7033ee63 Fix #12029 2026-03-25 16:56:46 +01:00
James Cole
4c20547863 Merge branch 'main' into develop 2026-03-24 19:21:16 +01:00
James Cole
3881cd3e39 Fix https://github.com/firefly-iii/firefly-iii/issues/12026 2026-03-24 19:20:36 +01:00
github-actions[bot]
a3e7fa008d Merge pull request #12025 from firefly-iii/release-1774347937
🤖 Automatically merge the PR into the develop branch.
2026-03-24 11:25:46 +01:00
JC5
205b5bd3bf 🤖 Auto commit for release 'develop' on 2026-03-24 2026-03-24 11:25:37 +01:00
Sander Dorigo
21c3dc3f56 Some demo user protections 2026-03-24 10:35:35 +01:00
github-actions[bot]
aa4f5d5a2e Merge pull request #12022 from firefly-iii/develop
🤖 Automatically merge the PR into the main branch.
2026-03-23 08:33:08 +01:00
github-actions[bot]
9c1f79110c Merge pull request #12021 from firefly-iii/release-1774251172
🤖 Automatically merge the PR into the develop branch.
2026-03-23 08:33:01 +01:00
JC5
bd62957908 🤖 Auto commit for release 'v6.5.9' on 2026-03-23 2026-03-23 08:32:53 +01:00
github-actions[bot]
a7e4252b46 Merge pull request #12020 from firefly-iii/release-1774249889
🤖 Automatically merge the PR into the develop branch.
2026-03-23 08:11:38 +01:00
JC5
c18691d6b3 🤖 Auto commit for release 'develop' on 2026-03-23 2026-03-23 08:11:29 +01:00
github-actions[bot]
b4dbcdcfba Merge pull request #12018 from firefly-iii/release-1774249482
🤖 Automatically merge the PR into the develop branch.
2026-03-23 08:04:52 +01:00
JC5
b0a6acb334 🤖 Auto commit for release 'v6.5.9' on 2026-03-23 2026-03-23 08:04:42 +01:00
github-actions[bot]
a0c423b9ed Merge pull request #12017 from firefly-iii/release-1774238341
🤖 Automatically merge the PR into the develop branch.
2026-03-23 04:59:09 +01:00
JC5
2844929351 🤖 Auto commit for release 'develop' on 2026-03-23 2026-03-23 04:59:01 +01:00
dependabot[bot]
7bfba6a239 Bump laravel-notification-channels/pushover from 4.1.2 to 5.0.0
Bumps [laravel-notification-channels/pushover](https://github.com/laravel-notification-channels/pushover) from 4.1.2 to 5.0.0.
- [Release notes](https://github.com/laravel-notification-channels/pushover/releases)
- [Changelog](https://github.com/laravel-notification-channels/pushover/blob/master/CHANGELOG.md)
- [Commits](https://github.com/laravel-notification-channels/pushover/compare/4.1.2...5.0.0)

---
updated-dependencies:
- dependency-name: laravel-notification-channels/pushover
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-23 03:52:43 +00:00
James Cole
c0d4a70d46 Update changelog for next release. 2026-03-22 17:10:47 +01:00
James Cole
a62f8bbaff Fix https://github.com/firefly-iii/firefly-iii/issues/12014 2026-03-22 17:05:31 +01:00
James Cole
b115b4ad92 Improve PR template. 2026-03-22 13:38:04 +01:00
github-actions[bot]
dc60025097 Merge pull request #12012 from firefly-iii/release-1774166607
🤖 Automatically merge the PR into the develop branch.
2026-03-22 09:03:34 +01:00
JC5
d77769b2f4 🤖 Auto commit for release 'develop' on 2026-03-22 2026-03-22 09:03:27 +01:00
James Cole
c6497960f8 Try updating package lock/ 2026-03-22 08:58:15 +01:00
James Cole
4eee0c79cd Go to vite plugin 3. 2026-03-22 08:36:43 +01:00
James Cole
e333c3254b Merge pull request #11961 from firefly-iii/dependabot/npm_and_yarn/develop/vite-8.0.0
Bump vite from 7.3.1 to 8.0.0
2026-03-22 07:21:07 +01:00
github-actions[bot]
9d244883a1 Merge pull request #12007 from firefly-iii/release-1774159543
🤖 Automatically merge the PR into the develop branch.
2026-03-22 07:05:54 +01:00
JC5
a17131c2f7 🤖 Auto commit for release 'develop' on 2026-03-22 2026-03-22 07:05:43 +01:00
James Cole
898459198d Fix null pointer. 2026-03-22 07:00:27 +01:00
James Cole
6466bc9272 Restore currency functionality. 2026-03-22 06:57:27 +01:00
James Cole
dd8a8dba85 Merge pull request #12005 from IDevJoe/main
Adjust $request->only() in testNotification function
2026-03-22 06:42:41 +01:00
James Cole
ee16888317 Catch null pointer. 2026-03-22 06:41:55 +01:00
Joe Longendyke
56a2580fd7 Fix testNotification function
Signed-off-by: Joe Longendyke <IDevJoe@users.noreply.github.com>
2026-03-21 16:05:03 -04:00
github-actions[bot]
2ab0225223 Merge pull request #12003 from firefly-iii/release-1774107220
🤖 Automatically merge the PR into the develop branch.
2026-03-21 16:33:48 +01:00
JC5
83662415c3 🤖 Auto commit for release 'develop' on 2026-03-21 2026-03-21 16:33:41 +01:00
James Cole
cf976b2ab1 Throw the error still to find out what's happening. 2026-03-21 16:28:19 +01:00
James Cole
bf79c9db72 Also add post data when PUT. 2026-03-21 16:19:05 +01:00
James Cole
29f4c09a7b Switch to unreported error to cut down on spam. 2026-03-21 16:17:52 +01:00
James Cole
22ef456dca Remove admin access from routes. 2026-03-21 16:15:15 +01:00
github-actions[bot]
9c706465b2 Merge pull request #12001 from firefly-iii/release-1774102841
🤖 Automatically merge the PR into the develop branch.
2026-03-21 15:20:48 +01:00
JC5
a40425fd75 🤖 Auto commit for release 'develop' on 2026-03-21 2026-03-21 15:20:41 +01:00
James Cole
e9a37592ba Improved sorting and slicing for https://github.com/firefly-iii/firefly-iii/issues/12000 2026-03-21 15:05:00 +01:00
James Cole
0c598cb034 Fix changelog header. 2026-03-21 11:57:08 +01:00
github-actions[bot]
f5dea9ac09 Merge pull request #11999 from firefly-iii/develop
🤖 Automatically merge the PR into the main branch.
2026-03-21 11:54:49 +01:00
github-actions[bot]
4ef7944147 Merge pull request #11998 from firefly-iii/release-1774090477
🤖 Automatically merge the PR into the develop branch.
2026-03-21 11:54:45 +01:00
JC5
4e1c84944c 🤖 Auto commit for release 'v6.5.8' on 2026-03-21 2026-03-21 11:54:37 +01:00
github-actions[bot]
f36da26cc3 Merge pull request #11997 from firefly-iii/release-1774090121
🤖 Automatically merge the PR into the develop branch.
2026-03-21 11:48:48 +01:00
JC5
5983a8eb6d 🤖 Auto commit for release 'develop' on 2026-03-21 2026-03-21 11:48:41 +01:00
James Cole
b4a8a219ff Fix https://github.com/firefly-iii/firefly-iii/issues/11995 2026-03-21 11:42:55 +01:00
github-actions[bot]
4190c4d243 Merge pull request #11994 from firefly-iii/develop
🤖 Automatically merge the PR into the main branch.
2026-03-21 07:44:44 +01:00
github-actions[bot]
70cbbc1523 Merge pull request #11993 from firefly-iii/release-1774075474
🤖 Automatically merge the PR into the develop branch.
2026-03-21 07:44:40 +01:00
JC5
c724f13501 🤖 Auto commit for release 'v6.5.7' on 2026-03-21 2026-03-21 07:44:34 +01:00
James Cole
5f01a83b43 Fix phpstan issues. 2026-03-21 07:36:52 +01:00
James Cole
53c13d221d Clean up API routes. 2026-03-21 07:27:10 +01:00
James Cole
266cd7d8d0 Update changelog and html rendering. 2026-03-21 07:01:42 +01:00
github-actions[bot]
7c09278c8e Merge pull request #11992 from firefly-iii/release-1774047683
🤖 Automatically merge the PR into the develop branch.
2026-03-21 00:01:32 +01:00
JC5
21af34c65a 🤖 Auto commit for release 'develop' on 2026-03-21 2026-03-21 00:01:23 +01:00
James Cole
594c04b121 Add date. 2026-03-20 23:55:46 +01:00
James Cole
c50408249b Update changelog for the next release. 2026-03-20 23:52:53 +01:00
James Cole
b05a38c0e2 So let's make this absolutely clear. 2026-03-20 23:48:42 +01:00
James Cole
0bb1afdf6c Fuck these AI agents. 2026-03-20 23:36:57 +01:00
James Cole
547b83b36e Call dedicated method. 2026-03-20 15:43:04 +01:00
James Cole
134d8c8cf6 Merge branch 'main' into develop 2026-03-20 10:42:05 +01:00
James Cole
94144a407d Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop
# Conflicts:
#	app/Services/Internal/Recalculate/PrimaryAmountRecalculationService.php
2026-03-20 08:49:29 +01:00
James Cole
15e29d133a Expand the pull request template 2026-03-20 08:48:18 +01:00
github-actions[bot]
889ba9f3e6 Merge pull request #11990 from firefly-iii/release-1773991497
🤖 Automatically merge the PR into the develop branch.
2026-03-20 08:25:08 +01:00
JC5
32fe62df03 🤖 Auto commit for release 'develop' on 2026-03-20 2026-03-20 08:24:57 +01:00
James Cole
21f9be6504 Rewrite calculation service for #11982
- moved reset methods to this class
- Add method `recalculateForGroupAndCurrency` that accepts a $limitCurrency as third argument.
- Add `recalculateAccountsForCurrency` and `calculateTransactionsForCurrency`

`recalculateForGroupAndCurrency` can recalculate any foreign amount back to the primary currency (just like the class was already capable of) BUT limits the action to foreign amounts in $limitCurrency. This greatly reduces the rework necessary. This means the method can be safely called when changing currency exchange rates.

Not all methods in `recalculateForGroupAndCurrency` actually care about the given $limitCurrency but the methods that don't aren't doing much anyway, so it's OK to recalculate everything even though its not necessary.
2026-03-20 07:17:33 +01:00
James Cole
d514792f4d Add new handler for currency exchange rate events #11982 2026-03-20 07:14:34 +01:00
James Cole
5894695ad6 Can also collect budgets for user groups #11982 2026-03-20 07:14:19 +01:00
James Cole
7004c9aaf5 Add missing link to user group in currency exchange rate #11982 2026-03-20 07:14:10 +01:00
James Cole
1893a33d84 Move methods to calculation service for #11982 2026-03-20 07:13:54 +01:00
James Cole
d345b31cd4 Catch missing keys in old data for #11982 2026-03-20 07:13:38 +01:00
James Cole
a27642024d Add events for #11982 2026-03-20 07:13:26 +01:00
James Cole
c23ad831d0 New events for https://github.com/firefly-iii/firefly-iii/issues/11982 2026-03-20 06:35:39 +01:00
James Cole
d50c283973 Merge pull request #11988 from firefly-iii/dependabot/composer/composer-bc2e42b409
Bump league/commonmark from 2.8.1 to 2.8.2 in the composer group across 1 directory
2026-03-20 06:04:05 +01:00
dependabot[bot]
9ea3519585 Bump league/commonmark in the composer group across 1 directory
Bumps the composer group with 1 update in the / directory: [league/commonmark](https://github.com/thephpleague/commonmark).


Updates `league/commonmark` from 2.8.1 to 2.8.2
- [Release notes](https://github.com/thephpleague/commonmark/releases)
- [Changelog](https://github.com/thephpleague/commonmark/blob/2.8/CHANGELOG.md)
- [Commits](https://github.com/thephpleague/commonmark/compare/2.8.1...2.8.2)

---
updated-dependencies:
- dependency-name: league/commonmark
  dependency-version: 2.8.2
  dependency-type: direct:production
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-20 05:02:47 +00:00
James Cole
ddb5bc6038 Merge pull request #11985 from firefly-iii/dependabot/composer/composer-9e6dd9d4c4
Bump phpseclib/phpseclib from 3.0.49 to 3.0.50 in the composer group across 1 directory
2026-03-20 06:02:02 +01:00
James Cole
b3d048eb67 Fix https://github.com/orgs/firefly-iii/discussions/11977 2026-03-20 06:01:23 +01:00
dependabot[bot]
caadef7c64 Bump phpseclib/phpseclib in the composer group across 1 directory
Bumps the composer group with 1 update in the / directory: [phpseclib/phpseclib](https://github.com/phpseclib/phpseclib).


Updates `phpseclib/phpseclib` from 3.0.49 to 3.0.50
- [Release notes](https://github.com/phpseclib/phpseclib/releases)
- [Changelog](https://github.com/phpseclib/phpseclib/blob/master/CHANGELOG.md)
- [Commits](https://github.com/phpseclib/phpseclib/compare/3.0.49...3.0.50)

---
updated-dependencies:
- dependency-name: phpseclib/phpseclib
  dependency-version: 3.0.50
  dependency-type: indirect
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-19 17:14:44 +00:00
James Cole
f7c01e6821 Restore v2 layout in dev. 2026-03-18 06:10:21 +01:00
James Cole
3a971d738c Fix https://github.com/firefly-iii/firefly-iii/issues/11976 2026-03-18 05:33:42 +01:00
James Cole
1eb4ae3a2c Fix a variety of Mago issues. 2026-03-18 05:26:50 +01:00
James Cole
0f30eb59a4 Fix https://github.com/firefly-iii/firefly-iii/issues/11978 2026-03-18 05:10:25 +01:00
James Cole
9c10b01e8b Small code fixes. 2026-03-17 20:43:32 +01:00
James Cole
7c4f80a360 Small changes based on Mago rule 2026-03-17 17:29:24 +01:00
James Cole
e5c19f6088 Fix https://github.com/firefly-iii/firefly-iii/issues/11969 2026-03-17 16:26:57 +01:00
James Cole
b067215ba8 Merge pull request #11974 from NorskNoobing/patch-1 2026-03-17 10:57:44 +01:00
Daniel Holøien
a17d10b064 Fix typo in SMTP server comment in .env.example
Signed-off-by: Daniel Holøien <39239702+NorskNoobing@users.noreply.github.com>
2026-03-17 07:52:36 +01:00
github-actions[bot]
859fea532d Merge pull request #11967 from firefly-iii/release-1773691728
🤖 Automatically merge the PR into the develop branch.
2026-03-16 21:10:01 +01:00
JC5
75261a46d9 🤖 Auto commit for release 'develop' on 2026-03-16 2026-03-16 21:08:48 +01:00
James Cole
a4a99310ea Fix bad comparison. 2026-03-16 21:02:52 +01:00
James Cole
8de0844e55 Remove old fields for tag 2026-03-16 20:54:57 +01:00
James Cole
e7a6dd792f Another fix for https://github.com/firefly-iii/firefly-iii/issues/11964 2026-03-16 20:43:05 +01:00
James Cole
395ccf8f75 Update changelog. 2026-03-16 20:27:57 +01:00
James Cole
dfbfdb6aa2 Fix https://github.com/firefly-iii/firefly-iii/issues/11964 2026-03-16 20:26:57 +01:00
James Cole
a367ee96bd Merge branch 'main' into develop 2026-03-16 20:18:25 +01:00
James Cole
b6b1261df5 Fix https://github.com/firefly-iii/firefly-iii/issues/11966 2026-03-16 20:17:52 +01:00
github-actions[bot]
52b611d7b3 Merge pull request #11962 from firefly-iii/release-1773633936
🤖 Automatically merge the PR into the develop branch.
2026-03-16 05:05:46 +01:00
JC5
bbc96f457b 🤖 Auto commit for release 'develop' on 2026-03-16 2026-03-16 05:05:36 +01:00
dependabot[bot]
974c84a877 Bump vite from 7.3.1 to 8.0.0
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.3.1 to 8.0.0.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@8.0.0/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-16 03:52:51 +00:00
160 changed files with 2384 additions and 2541 deletions

View File

@@ -1264,16 +1264,16 @@
},
{
"name": "symfony/console",
"version": "v8.0.7",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a"
"reference": "5b66d385dc58f69652e56f78a4184615e3f2b7f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
"reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
"url": "https://api.github.com/repos/symfony/console/zipball/5b66d385dc58f69652e56f78a4184615e3f2b7f7",
"reference": "5b66d385dc58f69652e56f78a4184615e3f2b7f7",
"shasum": ""
},
"require": {
@@ -1330,7 +1330,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v8.0.7"
"source": "https://github.com/symfony/console/tree/v8.0.8"
},
"funding": [
{
@@ -1350,7 +1350,7 @@
"type": "tidelift"
}
],
"time": "2026-03-06T14:06:22+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -1421,16 +1421,16 @@
},
{
"name": "symfony/event-dispatcher",
"version": "v8.0.4",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "99301401da182b6cfaa4700dbe9987bb75474b47"
"reference": "f662acc6ab22a3d6d716dcb44c381c6002940df6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99301401da182b6cfaa4700dbe9987bb75474b47",
"reference": "99301401da182b6cfaa4700dbe9987bb75474b47",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f662acc6ab22a3d6d716dcb44c381c6002940df6",
"reference": "f662acc6ab22a3d6d716dcb44c381c6002940df6",
"shasum": ""
},
"require": {
@@ -1482,7 +1482,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v8.0.4"
"source": "https://github.com/symfony/event-dispatcher/tree/v8.0.8"
},
"funding": [
{
@@ -1502,7 +1502,7 @@
"type": "tidelift"
}
],
"time": "2026-01-05T11:45:55+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
@@ -1582,16 +1582,16 @@
},
{
"name": "symfony/filesystem",
"version": "v8.0.6",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "7bf9162d7a0dff98d079b72948508fa48018a770"
"reference": "66b769ae743ce2d13e435528fbef4af03d623e5a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/7bf9162d7a0dff98d079b72948508fa48018a770",
"reference": "7bf9162d7a0dff98d079b72948508fa48018a770",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/66b769ae743ce2d13e435528fbef4af03d623e5a",
"reference": "66b769ae743ce2d13e435528fbef4af03d623e5a",
"shasum": ""
},
"require": {
@@ -1628,7 +1628,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v8.0.6"
"source": "https://github.com/symfony/filesystem/tree/v8.0.8"
},
"funding": [
{
@@ -1648,20 +1648,20 @@
"type": "tidelift"
}
],
"time": "2026-02-25T16:59:43+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/finder",
"version": "v8.0.6",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c"
"reference": "8da41214757b87d97f181e3d14a4179286151007"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/441404f09a54de6d1bd6ad219e088cdf4c91f97c",
"reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c",
"url": "https://api.github.com/repos/symfony/finder/zipball/8da41214757b87d97f181e3d14a4179286151007",
"reference": "8da41214757b87d97f181e3d14a4179286151007",
"shasum": ""
},
"require": {
@@ -1696,7 +1696,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v8.0.6"
"source": "https://github.com/symfony/finder/tree/v8.0.8"
},
"funding": [
{
@@ -1716,20 +1716,20 @@
"type": "tidelift"
}
],
"time": "2026-01-29T09:41:02+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/options-resolver",
"version": "v8.0.0",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
"reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7"
"reference": "b48bce0a70b914f6953dafbd10474df232ed4de8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/d2b592535ffa6600c265a3893a7f7fd2bad82dd7",
"reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/b48bce0a70b914f6953dafbd10474df232ed4de8",
"reference": "b48bce0a70b914f6953dafbd10474df232ed4de8",
"shasum": ""
},
"require": {
@@ -1767,7 +1767,7 @@
"options"
],
"support": {
"source": "https://github.com/symfony/options-resolver/tree/v8.0.0"
"source": "https://github.com/symfony/options-resolver/tree/v8.0.8"
},
"funding": [
{
@@ -1787,7 +1787,7 @@
"type": "tidelift"
}
],
"time": "2025-11-12T15:55:31+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -2370,16 +2370,16 @@
},
{
"name": "symfony/process",
"version": "v8.0.5",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674"
"reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
"reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
"url": "https://api.github.com/repos/symfony/process/zipball/cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc",
"reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc",
"shasum": ""
},
"require": {
@@ -2411,7 +2411,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v8.0.5"
"source": "https://github.com/symfony/process/tree/v8.0.8"
},
"funding": [
{
@@ -2431,7 +2431,7 @@
"type": "tidelift"
}
],
"time": "2026-01-26T15:08:38+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/service-contracts",
@@ -2522,16 +2522,16 @@
},
{
"name": "symfony/stopwatch",
"version": "v8.0.0",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
"reference": "67df1914c6ccd2d7b52f70d40cf2aea02159d942"
"reference": "85954ed72d5440ea4dc9a10b7e49e01df766ffa3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/67df1914c6ccd2d7b52f70d40cf2aea02159d942",
"reference": "67df1914c6ccd2d7b52f70d40cf2aea02159d942",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/85954ed72d5440ea4dc9a10b7e49e01df766ffa3",
"reference": "85954ed72d5440ea4dc9a10b7e49e01df766ffa3",
"shasum": ""
},
"require": {
@@ -2564,7 +2564,7 @@
"description": "Provides a way to profile code",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/stopwatch/tree/v8.0.0"
"source": "https://github.com/symfony/stopwatch/tree/v8.0.8"
},
"funding": [
{
@@ -2584,20 +2584,20 @@
"type": "tidelift"
}
],
"time": "2025-08-04T07:36:47+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/string",
"version": "v8.0.6",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4"
"reference": "ae9488f874d7603f9d2dfbf120203882b645d963"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
"reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
"url": "https://api.github.com/repos/symfony/string/zipball/ae9488f874d7603f9d2dfbf120203882b645d963",
"reference": "ae9488f874d7603f9d2dfbf120203882b645d963",
"shasum": ""
},
"require": {
@@ -2654,7 +2654,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v8.0.6"
"source": "https://github.com/symfony/string/tree/v8.0.8"
},
"funding": [
{
@@ -2674,7 +2674,7 @@
"type": "tidelift"
}
],
"time": "2026-02-09T10:14:57+00:00"
"time": "2026-03-30T15:14:47+00:00"
}
],
"packages-dev": [],

View File

@@ -21,7 +21,7 @@ parameters:
noNamedArgument:
enabled: false
noParameterWithContainerTypeDeclaration:
enabled: false
enabled: true
paths:
- ../app
- ../database

View File

@@ -173,7 +173,7 @@ MAIL_ENCRYPTION=null
MAIL_SENDMAIL_COMMAND=
#
# If you use self-signed certificates for your STMP server, you can use the following settings.
# If you use self-signed certificates for your SMTP server, you can use the following settings.
#
MAIL_ALLOW_SELF_SIGNED=false
MAIL_VERIFY_PEER=true

View File

@@ -1,25 +1,57 @@
<!--
Please TALK TO ME FIRST before you open a PR.
🙌 Thanks for contributing a pull request. Before you continue:
1. If you fix a problem that has no ticket, talk to me FIRST.
2. If you introduce new financial solutions or concepts, talk to me FIRST.
3. If your PR is more than 25 lines, talk to me FIRST.
4. If you used AI to write your PR, talk to me FIRST.
5. If you fix spelling or code comments, talk to me FIRST.
1. If you introduce new financial solutions or concepts, talk to me FIRST.
2. If your PR is more than 25 lines, talk to me FIRST.
3. If you fix spelling or code comments, talk to me FIRST.
Wanna talk to me? Open a GitHub Issue, Discussion, or send me an email: james@firefly-iii.org
This is to prevent AI bots, low-effort PRs and spam. Sorry about that.
See also: https://docs.firefly-iii.org/explanation/support/#contributing-code
Wanna talk to me? Open a GitHub Issue, Discussion, or email me: james@firefly-iii.org
👀 Please ensure you have taken a look at the contribution guidelines:
https://docs.firefly-iii.org/explanation/support/#contributing-code
Remember that your PR may be CLOSED:
1. If you do not refer to an existing issue, your PR will be CLOSED.
2. If you open a PR on the main branch, your PR will be CLOSED.
3. If you only fix a spelling error or code comment, your PR will be CLOSED.
Again, this is to prevent AI bots, low-effort PRs and spam. I apologize for the harsh tone.
But if you made it this far thanks again for contributing, and happy developing!
-->
@JC5
This PR fixes issue # <!-- mandatory field! -->.
#### Reference issues and PRs
<!--
Example: Fixes #1234. See also #3456.
-->
Changes in this pull request:
#### What does this implement/fix? Explain your changes.
#### AI usage disclosure
<!--
If AI tools were involved in creating this PR, please check all boxes that apply
below and make sure that you adhere to our Automated Contributions Policy:
https://docs.firefly-iii.org/explanation/support/#automated-contributions-policy
-->
I used AI assistance for:
- [ ] Code generation (e.g., when writing an implementation or fixing a bug)
- [ ] Test/benchmark generation
- [ ] Documentation (including examples)
- [ ] Research and understanding
#### Any other comments?
<!--
Thanks for contributing!
-->
@JC5
-
-
-

2
.github/security.md vendored
View File

@@ -106,6 +106,8 @@ found with the full or partial support of AI coding agents, large language model
2. explain how the vulnerability can actually be abused by a nefarious third party, and
3. try to limit the verbosity of your report.
At the discretion of the maintainer of the developer, your report may be closed without resolve.
## Credits
This security policy is based on [Harbor](https://github.com/goharbor/harbor)'s security policy.

View File

@@ -4,6 +4,8 @@ Over time, many people have contributed to Firefly III. Their efforts are not al
Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution.
## 2026
- Joe Longendyke
- Daniel Holøien
- Matthew Grove
- Cinnamon Pyro
- R1DEN

View File

@@ -96,7 +96,7 @@ final class AccountController extends Controller
$nameWithBalance = $account->name;
$currency = $this->repository->getAccountCurrency($account) ?? $this->primaryCurrency;
$useCurrency = $currency;
if (in_array($account->accountType->type, $this->balanceTypes, true)) {
if (in_array($account->accountType->type, $this->balanceTypes, strict: true)) {
// this one is correct.
Log::debug(sprintf('accounts: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
$balance = $allBalances[$account->id] ?? [];

View File

@@ -116,6 +116,7 @@ final 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,
];
}

View File

@@ -30,6 +30,7 @@ use FireflyIII\Models\Account;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Note;
use FireflyIII\Models\Recurrence;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup;
@@ -85,6 +86,9 @@ final class PurgeController extends Controller
// rules
Rule::whereUserId($user->id)->onlyTrashed()->forceDelete();
// notes (this will actually purge EVERYBODY's deleted notes)
Note::onlyTrashed()->forceDelete();
// recurring transactions
Recurrence::whereUserId($user->id)->onlyTrashed()->forceDelete();

View File

@@ -28,6 +28,7 @@ use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\DestroyRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Events\Model\CurrencyExchangeRate\DestroyedCurrencyExchangeRate;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\TransactionCurrency;
@@ -59,11 +60,12 @@ final class DestroyController extends Controller
public function destroy(DestroyRequest $request, TransactionCurrency $from, TransactionCurrency $to): JsonResponse
{
$this->repository->deleteRates($from, $to);
event(new DestroyedCurrencyExchangeRate($from, $to, $this->validateUserGroup($request)));
return response()->json([], 204);
}
public function destroySingleByDate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): JsonResponse
public function destroySingleByDate(Request $request, TransactionCurrency $from, TransactionCurrency $to, Carbon $date): JsonResponse
{
$exchangeRate = $this->repository->getSpecificRateOnDate($from, $to, $date);
if ($exchangeRate instanceof CurrencyExchangeRate) {
@@ -72,14 +74,19 @@ final class DestroyController extends Controller
if (!$exchangeRate instanceof CurrencyExchangeRate) {
throw new FireflyException('Bla');
}
event(new DestroyedCurrencyExchangeRate($from, $to, $this->validateUserGroup($request)));
return response()->json([], 204);
}
public function destroySingleById(CurrencyExchangeRate $exchangeRate): JsonResponse
public function destroySingleById(Request $request, CurrencyExchangeRate $exchangeRate): JsonResponse
{
$from = $exchangeRate->fromCurrency;
$to = $exchangeRate->toCurrency;
$this->repository->deleteRate($exchangeRate);
event(new DestroyedCurrencyExchangeRate($from, $to, $this->validateUserGroup($request)));
return response()->json([], 204);
}
}

View File

@@ -30,6 +30,8 @@ use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\StoreByCurrenciesRequ
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\StoreByDateRequest;
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\StoreRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Events\Model\CurrencyExchangeRate\CreatedCurrencyExchangeRate;
use FireflyIII\Events\Model\CurrencyExchangeRate\UpdatedCurrencyExchangeRate;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
@@ -73,10 +75,12 @@ final class StoreController extends Controller
if ($object instanceof CurrencyExchangeRate) {
// just update it, no matter.
$rate = $this->repository->updateExchangeRate($object, $rate, $date);
event(new UpdatedCurrencyExchangeRate($rate));
}
if (!$object instanceof CurrencyExchangeRate) {
// store new
$rate = $this->repository->storeExchangeRate($from, $to, $rate, $date);
event(new CreatedCurrencyExchangeRate($rate));
}
$transformer = new ExchangeRateTransformer();
@@ -97,10 +101,12 @@ final class StoreController extends Controller
// update existing rate.
$existing = $this->repository->updateExchangeRate($existing, $rate);
$collection->push($existing);
event(new UpdatedCurrencyExchangeRate($existing));
continue;
}
$new = $this->repository->storeExchangeRate($from, $to, $rate, $date);
event(new CreatedCurrencyExchangeRate($new));
$collection->push($new);
}
@@ -124,11 +130,13 @@ final class StoreController extends Controller
// update existing rate.
$existing = $this->repository->updateExchangeRate($existing, $rate);
$collection->push($existing);
event(new UpdatedCurrencyExchangeRate($existing));
continue;
}
$new = $this->repository->storeExchangeRate($from, $to, $rate, $date);
$collection->push($new);
event(new CreatedCurrencyExchangeRate($new));
}
$count = $collection->count();

View File

@@ -28,6 +28,7 @@ use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\UpdateRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Events\Model\CurrencyExchangeRate\UpdatedCurrencyExchangeRate;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
@@ -66,7 +67,7 @@ final class UpdateController extends Controller
$date = $request->getDate();
$rate = $request->getRate();
$exchangeRate = $this->repository->updateExchangeRate($exchangeRate, $rate, $date);
event(new UpdatedCurrencyExchangeRate($exchangeRate));
$transformer = new ExchangeRateTransformer();
return response()->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer))->header('Content-Type', self::CONTENT_TYPE);
@@ -77,6 +78,7 @@ final class UpdateController extends Controller
$date = $request->getDate();
$rate = $request->getRate();
$exchangeRate = $this->repository->updateExchangeRate($exchangeRate, $rate, $date);
event(new UpdatedCurrencyExchangeRate($exchangeRate));
$transformer = new ExchangeRateTransformer();
$transformer->setParameters($this->parameters);

View File

@@ -83,6 +83,9 @@ final class ListController extends Controller
// use new group collector:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
if (0 === count($journalIds)) {
$collector->findNothing();
}
$collector
->setUser($admin)
// filter on journal IDs.

View File

@@ -28,9 +28,7 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
@@ -41,7 +39,6 @@ use Illuminate\Validation\ValidationException;
final class DestroyController extends Controller
{
private CurrencyRepositoryInterface $repository;
private UserRepositoryInterface $userRepository;
/**
* CurrencyRepository constructor.
@@ -50,8 +47,7 @@ final class DestroyController extends Controller
{
parent::__construct();
$this->middleware(function ($request, $next) {
$this->repository = app(CurrencyRepositoryInterface::class);
$this->userRepository = app(UserRepositoryInterface::class);
$this->repository = app(CurrencyRepositoryInterface::class);
$this->repository->setUser(auth()->user());
return $next($request);
@@ -69,15 +65,8 @@ final class DestroyController extends Controller
*/
public function destroy(TransactionCurrency $currency): JsonResponse
{
/** @var User $admin */
$admin = auth()->user();
$rules = ['currency_code' => 'required'];
if (!$this->userRepository->hasRole($admin, 'owner')) {
// access denied:
$messages = ['currency_code' => '200005: You need the "owner" role to do this.'];
Validator::make([], $rules, $messages)->validate();
}
if ($this->repository->currencyInUse($currency)) {
$messages = ['currency_code' => '200006: Currency in use.'];
Validator::make([], $rules, $messages)->validate();

View File

@@ -35,7 +35,6 @@ use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Transformers\CurrencyTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use League\Fractal\Resource\Item;
/**
@@ -154,7 +153,6 @@ final class UpdateController extends Controller
public function update(UpdateRequest $request, TransactionCurrency $currency): JsonResponse
{
$data = $request->getAll();
Log::debug(__METHOD__, $data);
/** @var User $user */
$user = auth()->user();

View File

@@ -32,7 +32,6 @@ use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
/**
* Class DestroyController
@@ -72,11 +71,6 @@ final class DestroyController extends Controller
if (false === $linkType->editable) {
throw new FireflyException('200020: Link type cannot be changed.');
}
if (false === auth()->user()->hasRole('owner')) {
Log::channel('audit')->warning('Non-owner user tries to delete a link type.');
response()->json([], 401);
}
$this->repository->destroy($linkType);
Preferences::mark();

View File

@@ -84,6 +84,9 @@ final class ListController extends Controller
// use new group collector:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
if (0 === count($journalIds)) {
$collector->findNothing();
}
$collector
->setUser($admin)
// filter on journal IDs.

View File

@@ -27,12 +27,10 @@ namespace FireflyIII\Api\V1\Controllers\Models\TransactionLinkType;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\TransactionLinkType\StoreRequest;
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Transformers\LinkTypeTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use League\Fractal\Resource\Item;
@@ -44,7 +42,6 @@ final class StoreController extends Controller
use TransactionFilter;
private LinkTypeRepositoryInterface $repository;
private UserRepositoryInterface $userRepository;
/**
* LinkTypeController constructor.
@@ -54,9 +51,8 @@ final class StoreController extends Controller
parent::__construct();
$this->middleware(function ($request, $next) {
/** @var User $user */
$user = auth()->user();
$this->repository = app(LinkTypeRepositoryInterface::class);
$this->userRepository = app(UserRepositoryInterface::class);
$user = auth()->user();
$this->repository = app(LinkTypeRepositoryInterface::class);
$this->repository->setUser($user);
return $next($request);
@@ -73,15 +69,6 @@ final class StoreController extends Controller
*/
public function store(StoreRequest $request): JsonResponse
{
/** @var User $admin */
$admin = auth()->user();
$rules = ['name' => 'required'];
if (!$this->userRepository->hasRole($admin, 'owner')) {
// access denied:
$messages = ['name' => '200005: You need the "owner" role to do this.'];
Validator::make([], $rules, $messages)->validate();
}
$data = $request->getAll();
// if currency ID is 0, find the currency by the code:
$linkType = $this->repository->store($data);

View File

@@ -29,12 +29,10 @@ use FireflyIII\Api\V1\Requests\Models\TransactionLinkType\UpdateRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\LinkType;
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Transformers\LinkTypeTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use League\Fractal\Resource\Item;
@@ -46,7 +44,6 @@ final class UpdateController extends Controller
use TransactionFilter;
private LinkTypeRepositoryInterface $repository;
private UserRepositoryInterface $userRepository;
/**
* LinkTypeController constructor.
@@ -56,9 +53,8 @@ final class UpdateController extends Controller
parent::__construct();
$this->middleware(function ($request, $next) {
/** @var User $user */
$user = auth()->user();
$this->repository = app(LinkTypeRepositoryInterface::class);
$this->userRepository = app(UserRepositoryInterface::class);
$user = auth()->user();
$this->repository = app(LinkTypeRepositoryInterface::class);
$this->repository->setUser($user);
return $next($request);
@@ -80,15 +76,6 @@ final class UpdateController extends Controller
throw new FireflyException('200020: Link type cannot be changed.');
}
/** @var User $admin */
$admin = auth()->user();
$rules = ['name' => 'required'];
if (!$this->userRepository->hasRole($admin, 'owner')) {
$messages = ['name' => '200005: You need the "owner" role to do this.'];
Validator::make([], $rules, $messages)->validate();
}
$data = $request->getAll();
$this->repository->update($linkType, $data);
$manager = $this->getManager();

View File

@@ -63,7 +63,7 @@ final class AccountController extends Controller
$query = trim((string) $request->get('query'));
$field = trim((string) $request->get('field'));
$type = $request->get('type') ?? 'all';
if ('' === $query || !in_array($field, $this->validFields, true)) {
if ('' === $query || !in_array($field, $this->validFields, strict: true)) {
return response(null, 422);
}
Log::debug(sprintf('Now in account search("%s", "%s")', $field, $query));

View File

@@ -66,7 +66,7 @@ final class TransactionController extends Controller
$internalRef = (string) $request->attributes->get('internal_reference');
$notes = (string) $request->attributes->get('notes');
$description = (string) $request->attributes->get('description');
Log::debug(sprintf('Include deleted? %s', var_export($includeDeleted, true)));
Log::debug(sprintf('Include deleted? %s', var_export(value: $includeDeleted, return: true)));
if ('' !== $externalId) {
$count += $this->repository->countByMeta('external_id', $externalId, $includeDeleted);
Log::debug(sprintf('Search for transactions with external_identifier "%s", count is now %d', $externalId, $count));

View File

@@ -323,7 +323,7 @@ final class BasicController extends Controller
$today = today(config('app.timezone'));
$available = $this->abRepository->getAvailableBudgetWithCurrency($start, $end);
$budgets = $this->budgetRepository->getActiveBudgets();
$spent = $this->opsRepository->sumExpenses($start, $end, null, $budgets);
$spent = $this->opsRepository->sumExpenses($start, $end, null, $budgets, null, true);
$days = (int) $today->diffInDays($end, true) + 1;
$currencies = [];

View File

@@ -30,12 +30,10 @@ use FireflyIII\Enums\WebhookDelivery;
use FireflyIII\Enums\WebhookResponse;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Binder\EitherConfigKey;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
/**
@@ -43,21 +41,6 @@ use Illuminate\Validation\ValidationException;
*/
final class ConfigurationController extends Controller
{
private UserRepositoryInterface $repository;
/**
* ConfigurationController constructor.
*/
public function __construct()
{
parent::__construct();
$this->middleware(function ($request, $next) {
$this->repository = app(UserRepositoryInterface::class);
return $next($request);
});
}
/**
* This endpoint is documented at:
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/configuration/getConfiguration
@@ -142,11 +125,6 @@ final class ConfigurationController extends Controller
*/
public function update(UpdateRequest $request, string $name): JsonResponse
{
$rules = ['value' => 'required'];
if (!$this->repository->hasRole(auth()->user(), 'owner')) {
$messages = ['value' => '200005: You need the "owner" role to do this.'];
Validator::make([], $rules, $messages)->validate();
}
$data = $request->getAll();
$shortName = str_replace('configuration.', '', $name);

View File

@@ -74,13 +74,9 @@ final class UserController extends Controller
return response()->json([], 500);
}
if ($this->repository->hasRole($admin, 'owner')) {
$this->repository->destroy($user);
$this->repository->destroy($user);
return response()->json([], 204);
}
throw new FireflyException('200025: No access to function.');
return response()->json([], 204);
}
/**

View File

@@ -38,7 +38,7 @@ class ApiRequest extends FormRequest
public function handleConfig(array $config): void
{
if (in_array('required', $config, true)) {
if (in_array('required', $config, strict: true)) {
$this->required = 'required';
}
}

View File

@@ -72,7 +72,7 @@ class GenericRequest extends FormRequest
if (in_array(
$type,
[AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
true
strict: true
)) {
$return->push($account);
}

View File

@@ -116,7 +116,7 @@ class StoreRequest extends FormRequest
$validator->errors()->add(sprintf('accounts.%d', $index), trans('validation.invalid_account_currency'));
}
$type = $account->accountType->type;
if (!in_array($type, $types, true)) {
if (!in_array($type, $types, strict: true)) {
$validator->errors()->add(sprintf('accounts.%d', $index), trans('validation.invalid_account_type'));
}
}

View File

@@ -28,6 +28,7 @@ use FireflyIII\Models\PiggyBank;
use FireflyIII\Rules\IsValidPositiveAmount;
use FireflyIII\Rules\IsValidZeroOrMoreAmount;
use FireflyIII\Rules\LessThanPiggyTarget;
use FireflyIII\Rules\PiggyBank\IsEnoughInAccounts;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
@@ -84,7 +85,7 @@ class UpdateRequest extends FormRequest
'accounts' => 'array',
'accounts.*' => 'array',
'accounts.*.account_id' => ['required', 'numeric', 'belongsToUser:accounts,id'],
'accounts.*.current_amount' => ['numeric', 'nullable', new IsValidZeroOrMoreAmount(true)],
'accounts.*.current_amount' => ['numeric', 'nullable', new IsValidZeroOrMoreAmount(true), new IsEnoughInAccounts($piggyBank, $this->getAll())],
'object_group_id' => 'numeric|belongsToUser:object_groups,id',
'object_group_title' => ['min:1', 'max:255'],
'transaction_currency_id' => 'exists:transaction_currencies,id|nullable',

View File

@@ -51,7 +51,7 @@ class StoreRequest extends FormRequest
{
$fields = [
'title' => ['title', 'convertString'],
'description' => ['description', 'convertString'],
'description' => ['description', 'stringWithNewlines'],
'rule_group_id' => ['rule_group_id', 'convertInteger'],
'order' => ['order', 'convertInteger'],
'rule_group_title' => ['rule_group_title', 'convertString'],

View File

@@ -28,6 +28,7 @@ use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Rules\IsBoolean;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use FireflyIII\User;
use Illuminate\Foundation\Http\FormRequest;
/**
@@ -45,15 +46,23 @@ class UpdateRequest extends FormRequest
*/
public function getAll(): array
{
// return nothing that isn't explicitly in the array:
$fields = [
'name' => ['name', 'convertString'],
'code' => ['code', 'convertString'],
'symbol' => ['symbol', 'convertString'],
'decimal_places' => ['decimal_places', 'convertInteger'],
'default' => ['default', 'boolean'],
'enabled' => ['enabled', 'boolean'],
/** @var User $user */
$user = auth()->user();
$isAdmin = $user->hasRole('owner');
$fields = [
'enabled' => ['enabled', 'boolean'],
];
if ($isAdmin) {
$fields = [
'name' => ['name', 'convertString'],
'code' => ['code', 'convertString'],
'symbol' => ['symbol', 'convertString'],
'decimal_places' => ['decimal_places', 'convertInteger'],
'default' => ['default', 'boolean'],
'enabled' => ['enabled', 'boolean'],
];
}
return $this->getAllData($fields);
}

View File

@@ -52,7 +52,7 @@ class CreateRequest extends FormRequest
$responses = $this->get('responses', []);
$deliveries = $this->get('deliveries', []);
if (in_array(0, [count($triggers), count($responses), count($deliveries)], true)) {
if (in_array(0, [count($triggers), count($responses), count($deliveries)], strict: true)) {
throw new FireflyException('Unexpectedly got no responses, triggers or deliveries.');
}

View File

@@ -53,7 +53,7 @@ class UpdateRequest extends FormRequest
$responses = $this->get('responses', []);
$deliveries = $this->get('deliveries', []);
if (in_array(0, [count($triggers), count($responses), count($deliveries)], true)) {
if (in_array(0, [count($triggers), count($responses), count($deliveries)], strict: true)) {
throw new FireflyException('Unexpectedly got no responses, triggers or deliveries.');
}

View File

@@ -56,10 +56,10 @@ class UpdateRequest extends FormRequest
public function getAll(): array
{
$name = $this->route()->parameter('dynamicConfigKey');
if (in_array($name, $this->booleans, true)) {
if (in_array($name, $this->booleans, strict: true)) {
return ['value' => $this->boolean('value')];
}
if (in_array($name, $this->integers, true)) {
if (in_array($name, $this->integers, strict: true)) {
return ['value' => $this->convertInteger('value')];
}
@@ -73,13 +73,13 @@ class UpdateRequest extends FormRequest
{
$name = $this->route()->parameter('configName');
if (in_array($name, $this->booleans, true)) {
if (in_array($name, $this->booleans, strict: true)) {
return ['value' => ['required', new IsBoolean()]];
}
if ('configuration.permission_update_check' === $name) {
return ['value' => 'required|numeric|min:-1|max:1'];
}
if (in_array($name, $this->integers, true)) {
if (in_array($name, $this->integers, strict: true)) {
return ['value' => 'required|numeric|min:464272080'];
}

View File

@@ -125,7 +125,7 @@ class CorrectsAccountTypes extends Command
private function canCreateDestination(array $validDestinations): bool
{
return in_array(AccountTypeEnum::EXPENSE->value, $validDestinations, true);
return in_array(AccountTypeEnum::EXPENSE->value, $validDestinations, strict: true);
}
/**
@@ -133,7 +133,7 @@ class CorrectsAccountTypes extends Command
*/
private function canCreateSource(array $validSources): bool
{
return in_array(AccountTypeEnum::REVENUE->value, $validSources, true);
return in_array(AccountTypeEnum::REVENUE->value, $validSources, strict: true);
}
private function fixJournal(TransactionJournal $journal, string $transactionType, Transaction $source, Transaction $dest): void
@@ -308,7 +308,7 @@ class CorrectsAccountTypes extends Command
private function hasValidAccountType(array $validTypes, string $accountType): bool
{
return in_array($accountType, $validTypes, true);
return in_array($accountType, $validTypes, strict: true);
}
private function inspectJournal(TransactionJournal $journal): void
@@ -342,7 +342,7 @@ class CorrectsAccountTypes extends Command
return;
}
$expectedTypes = $this->expected[$type][$sourceAccountType];
if (!in_array($destAccountType, $expectedTypes, true)) {
if (!in_array($destAccountType, $expectedTypes, strict: true)) {
Log::debug(sprintf('[b] Going to fix journal #%d', $journal->id));
$this->fixJournal($journal, $type, $sourceTransaction, $destTransaction);
}

View File

@@ -33,6 +33,7 @@ use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class CorrectsGroupAccounts extends Command
{
@@ -46,6 +47,7 @@ class CorrectsGroupAccounts extends Command
*/
public function handle(): int
{
Log::debug('Start of correction:group-accounts');
$groups = [];
$res = TransactionJournal::groupBy('transaction_group_id')->get(['transaction_group_id', DB::raw('COUNT(transaction_group_id) as the_count')]);
@@ -59,13 +61,16 @@ class CorrectsGroupAccounts extends Command
$flags->applyRules = false;
$flags->fireWebhooks = false;
$flags->recalculateCredit = true;
$flags->unifyOnly = true;
$objects = new TransactionGroupEventObjects();
foreach ($groups as $groupId) {
$group = TransactionGroup::find($groupId);
$objects->appendFromTransactionGroup($group);
}
Log::debug(sprintf('Fire event for %d transaction group(s)', count($groups)));
event(new UpdatedSingleTransactionGroup($flags, $objects));
event(new WebhookMessagesRequestSending());
Log::debug('End of correction:group-accounts');
return 0;
}

View File

@@ -60,11 +60,7 @@ class CreatesGroupMemberships extends Command
if (null === $userRole) {
throw new FireflyException('Firefly III could not find a user role. Please make sure all migrations have run.');
}
$membership = GroupMembership::where('user_id', $user->id)
->where('user_group_id', $userGroup->id)
->where('user_role_id', $userRole->id)
->first()
;
$membership = GroupMembership::where('user_id', $user->id)->where('user_group_id', $userGroup->id)->where('user_role_id', $userRole->id)->first();
if (null === $membership) {
GroupMembership::create(['user_id' => $user->id, 'user_role_id' => $userRole->id, 'user_group_id' => $userGroup->id]);
}

View File

@@ -57,30 +57,10 @@ class RemovesLinksToDeletedObjects extends Command
*/
public function handle(): void
{
$deletedTags = Tag::withTrashed()
->whereNotNull('deleted_at')
->get('tags.id')
->pluck('id')
->toArray()
;
$deletedJournals = TransactionJournal::withTrashed()
->whereNotNull('deleted_at')
->get('transaction_journals.id')
->pluck('id')
->toArray()
;
$deletedBudgets = Budget::withTrashed()
->whereNotNull('deleted_at')
->get('budgets.id')
->pluck('id')
->toArray()
;
$deletedCategories = Category::withTrashed()
->whereNotNull('deleted_at')
->get('categories.id')
->pluck('id')
->toArray()
;
$deletedTags = Tag::withTrashed()->whereNotNull('deleted_at')->get('tags.id')->pluck('id')->toArray();
$deletedJournals = TransactionJournal::withTrashed()->whereNotNull('deleted_at')->get('transaction_journals.id')->pluck('id')->toArray();
$deletedBudgets = Budget::withTrashed()->whereNotNull('deleted_at')->get('budgets.id')->pluck('id')->toArray();
$deletedCategories = Category::withTrashed()->whereNotNull('deleted_at')->get('categories.id')->pluck('id')->toArray();
if (count($deletedTags) > 0) {
$this->cleanupTags($deletedTags);

View File

@@ -42,11 +42,7 @@ class RemovesZeroAmount extends Command
*/
public function handle(): int
{
$set = Transaction::where('amount', 0)
->get(['transaction_journal_id'])
->pluck('transaction_journal_id')
->toArray()
;
$set = Transaction::where('amount', 0)->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray();
$set = array_unique($set);
$journals = TransactionJournal::whereIn('id', $set)->get();

View File

@@ -70,20 +70,12 @@ class UpgradesJournalMetaData extends Command
private function getIdsForBudgets(): array
{
$transactions = DB::table('budget_transaction')
->distinct()
->pluck('transaction_id')
->toArray()
;
$transactions = DB::table('budget_transaction')->distinct()->pluck('transaction_id')->toArray();
$array = [];
$chunks = array_chunk($transactions, 500);
foreach ($chunks as $chunk) {
$set = DB::table('transactions')
->whereIn('transactions.id', $chunk)
->pluck('transaction_journal_id')
->toArray()
;
$set = DB::table('transactions')->whereIn('transactions.id', $chunk)->pluck('transaction_journal_id')->toArray();
$array = array_merge($array, $set);
}
@@ -92,20 +84,12 @@ class UpgradesJournalMetaData extends Command
private function getIdsForCategories(): array
{
$transactions = DB::table('category_transaction')
->distinct()
->pluck('transaction_id')
->toArray()
;
$transactions = DB::table('category_transaction')->distinct()->pluck('transaction_id')->toArray();
$array = [];
$chunks = array_chunk($transactions, 500);
foreach ($chunks as $chunk) {
$set = DB::table('transactions')
->whereIn('transactions.id', $chunk)
->pluck('transaction_journal_id')
->toArray()
;
$set = DB::table('transactions')->whereIn('transactions.id', $chunk)->pluck('transaction_journal_id')->toArray();
$array = array_merge($array, $set);
}

View File

@@ -86,10 +86,10 @@ class UpgradesToGroups extends Command
private function findOpposingTransaction(TransactionJournal $journal, Transaction $transaction): ?Transaction
{
$set = $journal->transactions->filter(static function (Transaction $subject) use ($transaction): bool {
$amount = ((float) $transaction->amount * -1) === (float) $subject->amount; // intentional float
$amount = -(float) $transaction->amount === (float) $subject->amount; // intentional float
$identifier = $transaction->identifier === $subject->identifier;
Log::debug(sprintf('Amount the same? %s', var_export($amount, true)));
Log::debug(sprintf('ID the same? %s', var_export($identifier, true)));
Log::debug(sprintf('Amount the same? %s', var_export($amount, return: true)));
Log::debug(sprintf('ID the same? %s', var_export($identifier, return: true)));
return $amount && $identifier;
});

View File

@@ -104,11 +104,7 @@ class UpgradesWebhooks extends Command
private function upgradeWebhooks(): void
{
$set = Webhook::where('delivery', '>', 1)
->orWhere('trigger', '>', 1)
->orWhere('response', '>', 1)
->get()
;
$set = Webhook::where('delivery', '>', 1)->orWhere('trigger', '>', 1)->orWhere('response', '>', 1)->get();
/** @var Webhook $webhook */
foreach ($set as $webhook) {

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/*
* CreatedCurrencyExchangeRate.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\Model\CurrencyExchangeRate;
use FireflyIII\Events\Event;
use FireflyIII\Models\CurrencyExchangeRate;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class CreatedCurrencyExchangeRate extends Event
{
use SerializesModels;
public function __construct(
public CurrencyExchangeRate $rate
) {
Log::debug(sprintf('CreatedCurrencyExchangeRate(#%d) Event', $rate->id));
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/*
* DestroyedCurrencyExchangeRate.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\Model\CurrencyExchangeRate;
use FireflyIII\Events\Event;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class DestroyedCurrencyExchangeRate extends Event
{
use SerializesModels;
public function __construct(
public TransactionCurrency $from,
public TransactionCurrency $to,
public UserGroup $userGroup
) {
Log::debug(sprintf('DestroyedCurrencyExchangeRate(%s, %s) Event', $from->code, $to->code));
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/*
* UpdatedCurrencyExchangeRate.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\Model\CurrencyExchangeRate;
use FireflyIII\Events\Event;
use FireflyIII\Models\CurrencyExchangeRate;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class UpdatedCurrencyExchangeRate extends Event
{
use SerializesModels;
public function __construct(
public CurrencyExchangeRate $rate
) {
Log::debug(sprintf('UpdatedCurrencyExchangeRate(#%d) Event', $rate->id));
}
}

View File

@@ -30,4 +30,5 @@ class TransactionGroupEventFlags
public bool $fireWebhooks = true;
public bool $batchSubmission = false;
public bool $recalculateCredit = true;
public bool $unifyOnly = false;
}

View File

@@ -250,7 +250,8 @@ class Handler extends ExceptionHandler
'json' => request()->acceptsJson(),
'method' => request()->method(),
'headers' => $headers,
'post' => 'POST' === request()->method() ? json_encode(request()->all()) : '',
// @mago-expect lint:no-request-all
'post' => 'PUT' === request()->method() || 'POST' === request()->method() ? json_encode(request()->all()) : '',
];
// create job that will mail.

View File

@@ -96,12 +96,7 @@ class AccountFactory
$type = AccountType::whereType($accountType)->first();
/** @var null|Account */
return $this->user
->accounts()
->where('account_type_id', $type->id)
->where('name', $accountName)
->first()
;
return $this->user->accounts()->where('account_type_id', $type->id)->where('name', $accountName)->first();
}
/**
@@ -117,12 +112,7 @@ class AccountFactory
}
/** @var null|Account $return */
$return = $this->user
->accounts
->where('account_type_id', $type->id)
->where('name', $accountName)
->first()
;
$return = $this->user->accounts->where('account_type_id', $type->id)->where('name', $accountName)->first();
if (null === $return) {
Log::debug('Found nothing. Will create a new one.');

View File

@@ -136,11 +136,7 @@ class BillFactory
public function findByName(string $name): ?Bill
{
/** @var null|Bill */
return $this->user
->bills()
->whereLike('name', sprintf('%%%s%%', $name))
->first()
;
return $this->user->bills()->whereLike('name', sprintf('%%%s%%', $name))->first();
}
public function setUser(User $user): void

View File

@@ -64,11 +64,7 @@ class BudgetFactory
public function findByName(string $name): ?Budget
{
/** @var null|Budget */
return $this->user
->budgets()
->where('name', $name)
->first()
;
return $this->user->budgets()->where('name', $name)->first();
}
public function setUser(User $user): void

View File

@@ -39,11 +39,7 @@ class CategoryFactory
public function findByName(string $name): ?Category
{
/** @var null|Category */
return $this->user
->categories()
->where('name', $name)
->first()
;
return $this->user->categories()->where('name', $name)->first();
}
/**

View File

@@ -134,6 +134,13 @@ class PiggyBankFactory
$previous = $toBeLinked[$account->id]['current_amount'] ?? '0';
$diff = bcsub($info['current_amount'], $previous);
// if money is added, check if we can!
if (1 === bccomp($diff, '0') && !$this->piggyBankRepository->canAddAmount($piggyBank, $account, $diff)) {
Log::debug(sprintf('Cannot add amount %s to piggy bank #%d ("%s")', $diff, $piggyBank->id, $piggyBank->name));
continue;
}
// create event for difference.
if (0 !== bccomp($diff, '0')) {
// 2025-10-01 for issue #10990 disable this event.

View File

@@ -76,11 +76,7 @@ class TagFactory
Log::debug(sprintf('Now in TagFactory::findOrCreate("%s")', $tag));
/** @var null|Tag $dbTag */
$dbTag = $this->user
->tags()
->where('tag', $tag)
->first()
;
$dbTag = $this->user->tags()->where('tag', $tag)->first();
if (null !== $dbTag) {
Log::debug(sprintf('Tag exists (#%d), return it.', $dbTag->id));

View File

@@ -56,11 +56,12 @@ class YearReportGenerator implements ReportGeneratorInterface
$reportType = 'default';
try {
$result = view('reports.default.year', ['accountIds' => $accountIds, 'reportType' => $reportType])
->with('start', $this->start)
->with('end', $this->end)
->render()
;
$result = view('reports.default.year', [
'accountIds' => $accountIds,
'reportType' => $reportType,
'start' => $this->start,
'end' => $this->end,
])->render();
} catch (Throwable $e) {
Log::error(sprintf('Cannot render reports.account.report: %s', $e->getMessage()));
Log::error($e->getTraceAsString());

View File

@@ -63,13 +63,15 @@ class MonthReportGenerator implements ReportGeneratorInterface
// render!
try {
$result = view('reports.tag.month', ['accountIds' => $accountIds, 'reportType' => $reportType, 'tagIds' => $tagIds])
->with('start', $this->start)
->with('end', $this->end)
->with('tags', $this->tags)
->with('accounts', $this->accounts)
->render()
;
$result = view('reports.tag.month', [
'accountIds' => $accountIds,
'reportType' => $reportType,
'tagIds' => $tagIds,
'start' => $this->start,
'end' => $this->end,
'tags' => $this->tags,
'accounts' => $this->accounts,
])->render();
} catch (Throwable $e) {
Log::error(sprintf('Cannot render reports.tag.month: %s', $e->getMessage()));
Log::error($e->getTraceAsString());

View File

@@ -49,11 +49,7 @@ class DeletedAccountObserver
$repository->destroy($attachment);
}
$journalIds = Transaction::where('account_id', $account->id)
->get(['transactions.transaction_journal_id'])
->pluck('transaction_journal_id')
->toArray()
;
$journalIds = Transaction::where('account_id', $account->id)->get(['transactions.transaction_journal_id'])->pluck('transaction_journal_id')->toArray();
$groupIds = array_map(function (array $item) {
return $item['transaction_group_id'];

View File

@@ -955,9 +955,9 @@ trait MetaCollection
$this->fields[] = 'tags.tag as tag_name';
$this->fields[] = 'tags.date as tag_date';
$this->fields[] = 'tags.description as tag_description';
$this->fields[] = 'tags.latitude as tag_latitude';
$this->fields[] = 'tags.longitude as tag_longitude';
$this->fields[] = 'tags.zoomLevel as tag_zoom_level';
// $this->fields[] = 'tags.latitude as tag_latitude';
// $this->fields[] = 'tags.longitude as tag_longitude';
// $this->fields[] = 'tags.zoomLevel as tag_zoom_level';
$this->joinTagTables();

View File

@@ -75,8 +75,6 @@ class GroupCollector implements GroupCollectorInterface
$this->userGroup = null;
$this->limit = null;
$this->page = null;
$this->startRow = null;
$this->endRow = null;
$this->hasAccountInfo = false;
$this->hasCatInformation = false;
@@ -443,9 +441,15 @@ class GroupCollector implements GroupCollectorInterface
$this->query->orWhereIn('transaction_journals.transaction_group_id', $groupIds);
}
$result = $this->query->get($this->fields);
$this->total = $result->count();
// if no post-filters are present, it can be sliced and returned.
if (0 === count($this->sorting) && 0 === count($this->postFilters) && null !== $this->limit && null !== $this->page) {
$offset = ($this->page - 1) * $this->limit;
$result = $result->slice($offset, $this->limit);
}
// $this->dumpQueryInLogs();
// Log::debug(sprintf('Count of result is %d', $result->count()));
// now to parse this into an array.
// now to parse the rest into an array.
$collection = $this->parseArray($result);
// filter the array using all available post filters:
@@ -454,19 +458,12 @@ class GroupCollector implements GroupCollectorInterface
// sort the collection, if sort instructions are present.
$collection = $this->sortCollection($collection);
// count it and continue:
$this->total = $collection->count();
// now filter the array according to the page and the limit (if necessary)
if (null !== $this->limit && null !== $this->page) {
if (count($this->postFilters) > 0 && null !== $this->limit && null !== $this->page) {
$offset = ($this->page - 1) * $this->limit;
return $collection->slice($offset, $this->limit);
}
// OR filter the array according to the start and end row variable
if (null !== $this->startRow && null !== $this->endRow) {
return $collection->slice($this->startRow, $this->endRow);
}
return $collection;
}
@@ -477,17 +474,11 @@ class GroupCollector implements GroupCollectorInterface
public function getPaginatedGroups(): LengthAwarePaginator
{
Log::debug('Now in getPaginatedGroups()');
$set = $this->getGroups();
$limit = $this->limit ?? 1;
if (0 === $this->limit) {
$this->setLimit(50);
}
if (null !== $this->startRow && null !== $this->endRow) {
/** @var int $total */
$total = $this->endRow - $this->startRow;
return new LengthAwarePaginator($set, $this->total, $total, 1);
}
$limit = $this->limit ?? 1;
$set = $this->getGroups();
return new LengthAwarePaginator($set, $this->total, $limit, $this->page);
}
@@ -519,13 +510,6 @@ class GroupCollector implements GroupCollectorInterface
return $this;
}
public function setEndRow(int $endRow): self
{
$this->endRow = $endRow;
return $this;
}
public function setExpandGroupSearch(bool $expandGroupSearch): GroupCollectorInterface
{
$this->expandGroupSearch = $expandGroupSearch;
@@ -636,13 +620,6 @@ class GroupCollector implements GroupCollectorInterface
return $this;
}
public function setStartRow(int $startRow): self
{
$this->startRow = $startRow;
return $this;
}
/**
* Limit the search to one specific transaction group.
*/
@@ -692,6 +669,10 @@ class GroupCollector implements GroupCollectorInterface
#[Override]
public function sortCollection(Collection $collection): Collection
{
if (0 === count($this->sorting)) {
return $collection;
}
/**
* @var string $field
* @var string $direction
@@ -769,11 +750,7 @@ class GroupCollector implements GroupCollectorInterface
private function getCollectedGroupIds(): array
{
return $this->query
->get(['transaction_journals.transaction_group_id'])
->pluck('transaction_group_id')
->toArray()
;
return $this->query->get(['transaction_journals.transaction_group_id'])->pluck('transaction_group_id')->toArray();
}
private function mergeAttachments(array $existingJournal, TransactionJournal $newJournal): array
@@ -1145,6 +1122,7 @@ class GroupCollector implements GroupCollectorInterface
->orderBy('transaction_journals.order', 'ASC')
->orderBy('transaction_journals.id', 'DESC')
->orderBy('transaction_journals.description', 'DESC')
->orderBy('source.amount', 'DESC');
->orderBy('source.amount', 'DESC')
;
}
}

View File

@@ -469,11 +469,6 @@ interface GroupCollectorInterface
*/
public function setEnd(Carbon $end): self;
/**
* Set the page to get.
*/
public function setEndRow(int $endRow): self;
public function setExpandGroupSearch(bool $expandGroupSearch): self;
/**
@@ -573,11 +568,6 @@ interface GroupCollectorInterface
*/
public function setStart(Carbon $start): self;
/**
* Set the page to get.
*/
public function setStartRow(int $startRow): self;
/**
* Limit results to a specific tag.
*/

View File

@@ -132,7 +132,7 @@ final class NotificationController extends Controller
return redirect(route('settings.notification.index'));
}
$all = $request->all();
$all = $request->only(['test_submit']);
$channel = $all['test_submit'] ?? '';
switch ($channel) {

View File

@@ -83,8 +83,8 @@ final class RegisterController extends Controller
throw new FireflyException('Registration is currently not available :(');
}
$this->validator($request->all())->validate();
$user = $this->createUser($request->all());
$this->validator($request->only(['email', 'password', 'password_confirmation']))->validate();
$user = $this->createUser($request->only(['email', 'password']));
Log::info(sprintf('Registered new user %s', $user->email));
$owner = new OwnerNotifiable();
event(new NewUserRegistered($owner, $user));

View File

@@ -132,7 +132,7 @@ final class ResetPasswordController extends Controller
$allowRegistration = false;
}
return view('auth.passwords.reset')->with([
return view('auth.passwords.reset', [
'token' => $token,
'email' => $request->email,
'allowRegistration' => $allowRegistration,

View File

@@ -137,15 +137,15 @@ final class BudgetLimitController extends Controller
*/
public function store(Request $request): JsonResponse|RedirectResponse
{
Log::debug('Going to store new budget-limit.', $request->all());
Log::debug('Going to store new budget-limit.');
// first search for existing one and update it if necessary.
$currency = $this->currencyRepos->find((int) $request->get('transaction_currency_id'));
$budget = $this->repository->find((int) $request->get('budget_id'));
$currency = $this->currencyRepos->find((int) $request->input('transaction_currency_id'));
$budget = $this->repository->find((int) $request->input('budget_id'));
if (!$currency instanceof TransactionCurrency || !$budget instanceof Budget) {
throw new FireflyException('No valid currency or budget.');
}
$start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
$end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
$start = Carbon::createFromFormat('Y-m-d', $request->input('start'));
$end = Carbon::createFromFormat('Y-m-d', $request->input('end'));
if (!$start instanceof Carbon || !$end instanceof Carbon) {
return response()->json();
@@ -172,8 +172,8 @@ final class BudgetLimitController extends Controller
// return empty array:
return response()->json([]);
}
if ((int) $amount > 268_435_456) { // intentional cast to integer
$amount = '268435456';
if ((int) $amount > 2_147_483_647) { // intentional cast to integer
$amount = '2147483647';
}
if (-1 === bccomp($amount, '0')) {
$amount = bcmul($amount, '-1');
@@ -232,8 +232,8 @@ final class BudgetLimitController extends Controller
if ('' === $amount) {
$amount = '0';
}
if ((int) $amount > 268_435_456) { // 268 million, intentional integer
$amount = '268435456';
if ((int) $amount > 2_147_483_647) { // 268 million, intentional integer
$amount = '2147483647';
}
// sanity check on amount:
if (0 === bccomp($amount, '0')) {

View File

@@ -44,6 +44,7 @@ use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
@@ -108,7 +109,7 @@ final class DebugController extends Controller
Preferences::mark();
$request->session()->forget(['start', 'end', '_previous', 'viewRange', 'range', 'is_custom_range', 'temp-mfa-secret', 'temp-mfa-codes']);
Artisan::call('cache:clear');
Cache::clear();
Artisan::call('config:clear');
Artisan::call('route:clear');
Artisan::call('view:clear');
@@ -302,7 +303,7 @@ final class DebugController extends Controller
}
return [
'debug' => var_export(config('app.debug'), true),
'debug' => var_export(config('app.debug'), return: true),
'audit_log_channel' => implode(', ', config('logging.channels.audit.channels')),
'default_language' => (string) config('firefly.default_language'),
'default_locale' => (string) config('firefly.default_locale'),

View File

@@ -106,7 +106,7 @@ final class HomeController extends Controller
}
$request->session()->put('is_custom_range', $isCustomRange);
Log::debug(sprintf('Set is_custom_range to %s', var_export($isCustomRange, true)));
Log::debug(sprintf('Set is_custom_range to %s', var_export($isCustomRange, return: true)));
$request->session()->put('start', $start);
Log::debug(sprintf('Set start to %s', $start->format('Y-m-d H:i:s')));
$request->session()->put('end', $end);

View File

@@ -116,7 +116,7 @@ final class JavascriptController extends Controller
'currencyCode' => $currency->code,
'currencySymbol' => $currency->symbol,
'accountingLocaleInfo' => $accounting,
'anonymous' => var_export(Steam::anonymous(), true),
'anonymous' => var_export(Steam::anonymous(), return: true),
'language' => $lang,
'dateRangeTitle' => $dateRange['title'],
'locale' => $locale,

View File

@@ -82,7 +82,7 @@ final class IntroController extends Controller
Log::debug('Elements is array', $elements);
Log::debug('Keys is', array_keys($elements));
Log::debug(sprintf('Keys has "outro": %s', var_export($hasStep, true)));
Log::debug(sprintf('Keys has "outro": %s', var_export($hasStep, return: true)));
return $hasStep;
}

View File

@@ -109,11 +109,7 @@ final class NewUserController extends Controller
$currencyRepository->makePrimary($currency);
// store frontpage preferences:
$accounts = $this->repository
->getAccountsByType([AccountTypeEnum::ASSET->value])
->pluck('id')
->toArray()
;
$accounts = $this->repository->getAccountsByType([AccountTypeEnum::ASSET->value])->pluck('id')->toArray();
Preferences::set('frontpageAccounts', $accounts);
// mark.

View File

@@ -135,7 +135,7 @@ final class AmountController extends Controller
*/
public function postAdd(Request $request, PiggyBank $piggyBank): RedirectResponse
{
$data = $request->all();
$data = $request->only(['amount']);
$amounts = $data['amount'] ?? [];
$total = '0';
Log::debug('Start with loop.');

View File

@@ -231,8 +231,8 @@ final class PreferencesController extends Controller
Log::debug('postIndex for preferences.');
// front page accounts
$frontpageAccounts = [];
if (is_array($request->get('frontpageAccounts')) && count($request->get('frontpageAccounts')) > 0) {
foreach ($request->get('frontpageAccounts') as $id) {
if (is_array($request->input('frontpageAccounts')) && count($request->input('frontpageAccounts')) > 0) {
foreach ($request->input('frontpageAccounts') as $id) {
$frontpageAccounts[] = (int) $id;
}
Log::debug('Update frontpageAccounts', $frontpageAccounts);
@@ -240,22 +240,26 @@ final class PreferencesController extends Controller
}
// extract notifications:
$all = $request->all();
$keys = array_map(function (string $value): string {
return sprintf('notification_%s', $value);
}, array_keys(config('notifications.notifications.user')));
$all = $request->only($keys);
foreach (config('notifications.notifications.user') as $key => $info) {
$key = sprintf('notification_%s', $key);
if (array_key_exists($key, $all)) {
if (array_key_exists($key, $all) && false === auth()->user()->hasRole('demo')) {
Log::debug(sprintf('update notification to true: %s', $key));
Preferences::set($key, true);
continue;
}
if (!array_key_exists($key, $all)) {
Log::debug(sprintf('update notification to false: %s', $key));
Preferences::set($key, false);
}
Log::debug(sprintf('update notification to false: %s', $key));
Preferences::set($key, false);
}
unset($all);
// view range:
Log::debug(sprintf('Let viewRange to "%s"', $request->get('viewRange')));
Preferences::set('viewRange', $request->get('viewRange'));
Log::debug(sprintf('Let viewRange to "%s"', $request->input('viewRange')));
Preferences::set('viewRange', $request->input('viewRange'));
// forget session values:
session()->forget('start');
session()->forget('end');
@@ -264,6 +268,7 @@ final class PreferencesController extends Controller
// notification settings, cannot be set by the demo user.
if (!auth()->user()->hasRole('demo')) {
$variables = ['slack_webhook_url', 'pushover_app_token', 'pushover_user_token', 'ntfy_server', 'ntfy_topic', 'ntfy_user', 'ntfy_pass'];
$all = $request->only($variables);
foreach ($variables as $variable) {
if ('' === $all[$variable]) {
Preferences::delete($variable);
@@ -274,9 +279,10 @@ final class PreferencesController extends Controller
}
Preferences::set('ntfy_auth', $all['ntfy_auth'] ?? false);
}
unset($all);
// convert primary
$convertToPrimary = 1 === (int) $request->get('convertToPrimary');
$convertToPrimary = 1 === (int) $request->input('convertToPrimary');
if ($convertToPrimary && !$this->convertToPrimary) {
// set to true!
Log::debug('User sets convertToPrimary to true.');
@@ -288,9 +294,9 @@ final class PreferencesController extends Controller
Preferences::set('convert_to_primary', $convertToPrimary);
// custom fiscal year
$customFiscalYear = 1 === (int) $request->get('customFiscalYear');
$customFiscalYear = 1 === (int) $request->input('customFiscalYear');
Preferences::set('customFiscalYear', $customFiscalYear);
$fiscalYearString = (string) $request->get('fiscalYearStart');
$fiscalYearString = (string) $request->input('fiscalYearStart');
if ('' !== $fiscalYearString) {
$fiscalYearStart = Carbon::parse($fiscalYearString, config('app.timezone'))->format('m-d');
Preferences::set('fiscalYearStart', $fiscalYearStart);
@@ -298,7 +304,7 @@ final class PreferencesController extends Controller
// save page size:
Preferences::set('listPageSize', 50);
$listPageSize = (int) $request->get('listPageSize');
$listPageSize = (int) $request->input('listPageSize');
if ($listPageSize > 0 && $listPageSize < 1337) {
Preferences::set('listPageSize', $listPageSize);
}
@@ -306,7 +312,7 @@ final class PreferencesController extends Controller
// language:
/** @var Preference $currentLang */
$currentLang = Preferences::get('language', 'en_US');
$lang = $request->get('language');
$lang = $request->input('language');
if (array_key_exists($lang, config('firefly.languages'))) {
Preferences::set('language', $lang);
}
@@ -317,13 +323,13 @@ final class PreferencesController extends Controller
// same for locale:
if (!auth()->user()->hasRole('demo')) {
$locale = (string) $request->get('locale');
$locale = (string) $request->input('locale');
$locale = '' === $locale ? null : $locale;
Preferences::set('locale', $locale);
}
// optional fields for transactions:
$setOptions = $request->get('tj') ?? [];
$setOptions = $request->input('tj') ?? [];
$optionalTj = [
'interest_date' => array_key_exists('interest_date', $setOptions),
'book_date' => array_key_exists('book_date', $setOptions),
@@ -341,13 +347,13 @@ final class PreferencesController extends Controller
Preferences::set('transaction_journal_optional_fields', $optionalTj);
// dark mode
$darkMode = $request->get('darkMode') ?? 'browser';
$darkMode = $request->input('darkMode') ?? 'browser';
if (in_array($darkMode, config('firefly.available_dark_modes'), true)) {
Preferences::set('darkMode', $darkMode);
}
// anonymous amounts?
$anonymous = '1' === $request->get('anonymous');
$anonymous = '1' === $request->input('anonymous');
Preferences::set('anonymous', $anonymous);
// save and continue
@@ -360,9 +366,15 @@ final class PreferencesController extends Controller
public function testNotification(Request $request): mixed
{
$all = $request->all();
$all = $request->only(['channel']);
$channel = $all['channel'] ?? '';
if (true === auth()->user()->hasRole('demo')) {
session()->flash('error', (string) trans('firefly.not_available_demo_user'));
return redirect(route('preferences.index'));
}
switch ($channel) {
default:
session()->flash('error', (string) trans('firefly.notification_test_failed', ['channel' => $channel]));

View File

@@ -187,11 +187,7 @@ final class ProfileController extends Controller
/** @var User $user */
$user = auth()->user();
$isInternalAuth = $this->internalAuth;
$count = DB::table('oauth_clients')
->where('personal_access_client', true)
->whereNull('user_id')
->count()
;
$count = DB::table('oauth_clients')->where('personal_access_client', true)->whereNull('user_id')->count();
$subTitle = $user->email;
$userId = $user->id;
$enabled2FA = null !== $user->mfa_secret;

View File

@@ -225,7 +225,7 @@ final class TagController extends Controller
{
// default values:
$subTitleIcon = 'fa-tag';
$page = (int) $request->get('page');
$page = (int) $request->input('page');
$pageSize = (int) Preferences::get('listPageSize', 50)->data;
$start ??= session('start');
$end ??= session('end');
@@ -249,18 +249,20 @@ final class TagController extends Controller
// collect transaction journal IDs in repository,
// this makes the collector faster and more accurate.
$journalIds = $this->repository->getJournalIds($tag);
if (0 === count($journalIds)) {
$collector->findNothing();
}
$collector
->setRange($start, $end)
->setLimit($pageSize)
->setPage($page)
->setJournalIds($journalIds)
->withAccountInformation()
// ->setTag($tag)
->withBudgetInformation()
->withCategoryInformation()
->withAttachmentInformation()
;
$groups = $collector->getPaginatedGroups();
$groups->setPath($path);
$sums = $this->repository->sumsOfTag($tag, $start, $end);
@@ -303,10 +305,13 @@ final class TagController extends Controller
// collect transaction journal IDs in repository,
// this makes the collector faster and more accurate.
$journalIds = $this->repository->getJournalIds($tag);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$journalIds = $this->repository->getJournalIds($tag);
if (0 === count($journalIds)) {
$collector->findNothing();
}
$collector
->setRange($start, $end)
->setLimit($pageSize)

View File

@@ -153,7 +153,7 @@ final class ConvertController extends Controller
foreach ($group->transactionJournals as $journal) {
// catch FF exception.
try {
$this->convertJournal($journal, $destinationType, $request->all());
$this->convertJournal($journal, $destinationType, $request->only(['source_id', 'source_name', 'destination_id', 'destination_name']));
} catch (FireflyException $e) {
session()->flash('error', $e->getMessage());

View File

@@ -115,7 +115,7 @@ final class EditController extends Controller
];
$optionalFields['external_url'] ??= false;
$optionalFields['location'] ??= false;
$optionalFields['location'] = $optionalFields['location']
$optionalFields['location'] = true === $optionalFields['location']
&& true === FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
// map info voor v2:

View File

@@ -170,7 +170,7 @@ final class MassController extends Controller
*/
public function update(MassEditJournalRequest $request): RedirectResponse
{
$journalIds = $request->get('journals');
$journalIds = $request->input('journals');
if (!is_array($journalIds)) {
// TODO this is a weird error, should be caught.
throw new FireflyException('This is not an array.');
@@ -250,6 +250,8 @@ final class MassController extends Controller
private function updateJournal(int $journalId, MassEditJournalRequest $request): void
{
$journal = $this->repository->find($journalId);
$objects = TransactionGroupEventObjects::collectFromTransactionGroup($journal->transactionGroup);
if (!$journal instanceof TransactionJournal) {
throw new FireflyException(sprintf('Trying to edit non-existent or deleted journal #%d', $journalId));
}
@@ -274,8 +276,9 @@ final class MassController extends Controller
// call service to update.
$service->setData($data);
$service->update();
$updated = $service->getTransactionJournal();
$objects->appendFromTransactionGroup($updated->transactionGroup);
$flags = new TransactionGroupEventFlags();
$objects = TransactionGroupEventObjects::collectFromTransactionGroup($journal->transactionGroup);
event(new UpdatedSingleTransactionGroup($flags, $objects));
event(new WebhookMessagesRequestSending());
}

View File

@@ -82,11 +82,7 @@ final class EditController extends Controller
$currency->symbol = htmlentities($currency->symbol);
// is currently enabled (for this user?)
$userCurrencies = $this->repository
->get()
->pluck('id')
->toArray()
;
$userCurrencies = $this->repository->get()->pluck('id')->toArray();
$enabled = in_array($currency->id, $userCurrencies, true);
// code to handle active-checkboxes

View File

@@ -43,6 +43,6 @@ final class EditController extends Controller
$mainTitleIcon = 'fa-book';
Log::debug(sprintf('Now at %s', __METHOD__));
return view('administrations.edit')->with(['title' => $title, 'subTitle' => $subTitle, 'mainTitleIcon' => $mainTitleIcon]);
return view('administrations.edit', ['title' => $title, 'subTitle' => $subTitle, 'mainTitleIcon' => $mainTitleIcon]);
}
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* IsAdmin.php
* Copyright (c) 2019 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\Http\Middleware;
use Closure;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
/**
* Class IsAdmin.
*/
class IsAdminApi
{
/**
* Handle an incoming request. Must be admin.
*
* @param null|string $guard
*
* @return mixed
*
* @throws AuthorizationException
*/
public function handle(Request $request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
}
return response()->redirectTo(route('login'));
}
/** @var User $user */
$user = auth()->user();
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
if (!$repository->hasRole($user, 'owner')) {
Log::error(sprintf('Cannot access %s?%s.', $request->url(), $request->getQueryString()));
throw new AuthorizationException();
}
return $next($request);
}
}

View File

@@ -61,7 +61,7 @@ class SecureHeaders
// sprintf("style-src 'self' 'nonce-%1s'", $nonce), // safe variant
"style-src 'self' 'unsafe-inline'", // unsafe variant
"base-uri 'self'",
"form-action 'self'",
// "form-action 'self'", // safe
"font-src 'self' data:",
sprintf("connect-src 'self' %s", $trackingScriptSrc),
sprintf("img-src 'self' data: 'nonce-%1s' ", $nonce),
@@ -70,16 +70,17 @@ class SecureHeaders
// overrule in development mode
if (true === config('firefly.is_local_dev')) {
$ip = '192.168.96.165';
$csp = [
"default-src 'none'",
"object-src 'none'",
sprintf("script-src 'unsafe-eval' 'strict-dynamic' 'nonce-%1s'", $nonce),
// sprintf("style-src 'self' 'nonce-%1s' https://10.0.0.15:5173/", $nonce), // safe variant
"style-src 'self' 'unsafe-inline' https://10.0.0.15:5173/", // unsafe variant
sprintf("style-src 'self' 'unsafe-inline' https://%s:5173/", $ip), // unsafe variant
"base-uri 'self'",
"form-action 'self'",
"font-src 'self' data: https://10.0.0.15:5173/",
sprintf("connect-src 'self' %s https://10.0.0.15:5173/ wss://10.0.0.15:5173/", $trackingScriptSrc),
sprintf("font-src 'self' data: https://%s:5173/", $ip),
sprintf('connect-src \'self\' %1$s https://%2$s:5173/ wss://%2$s:5173/', $trackingScriptSrc, $ip),
sprintf("img-src 'self' data: 'nonce-%1s'", $nonce),
"manifest-src 'self'",
];

View File

@@ -59,7 +59,7 @@ class UpdatesAccountInformation implements ShouldQueue
/** @var RuleAction $action */
foreach ($rule->ruleActions as $action) {
// fix name:
if ($oldData['name'] === $action->action_value && in_array($action->action_type, $fields, true)) {
if (array_key_exists('name', $oldData) && $oldData['name'] === $action->action_value && in_array($action->action_type, $fields, true)) {
Log::debug(sprintf('Rule action #%d "%s" has old account name, replace with new.', $action->id, $action->action_type));
$action->action_value = $account->name;
$action->save();
@@ -105,21 +105,25 @@ class UpdatesAccountInformation implements ShouldQueue
/** @var RuleTrigger $trigger */
foreach ($rule->ruleTriggers as $trigger) {
// fix name:
if ($oldData['name'] === $trigger->trigger_value && in_array($trigger->trigger_type, $nameFields, true)) {
if (array_key_exists('name', $oldData) && $oldData['name'] === $trigger->trigger_value && in_array($trigger->trigger_type, $nameFields, true)) {
Log::debug(sprintf('Rule trigger #%d "%s" has old account name, replace with new.', $trigger->id, $trigger->trigger_type));
$trigger->trigger_value = $account->name;
$trigger->save();
++$fixed;
}
// fix IBAN:
if ($oldData['iban'] === $trigger->trigger_value && in_array($trigger->trigger_type, $numberFields, true)) {
if (array_key_exists('iban', $oldData) && $oldData['iban'] === $trigger->trigger_value && in_array($trigger->trigger_type, $numberFields, true)) {
Log::debug(sprintf('Rule trigger #%d "%s" has old account IBAN, replace with new.', $trigger->id, $trigger->trigger_type));
$trigger->trigger_value = $account->iban;
$trigger->save();
++$fixed;
}
// fix account number: // account_number
if ($oldData['account_number'] === $trigger->trigger_value && in_array($trigger->trigger_type, $numberFields, true)) {
if (
array_key_exists('account_number', $oldData)
&& $oldData['account_number'] === $trigger->trigger_value
&& in_array($trigger->trigger_type, $numberFields, true)
) {
Log::debug(sprintf('Rule trigger #%d "%s" has old account account_number, replace with new.', $trigger->id, $trigger->trigger_type));
$trigger->trigger_value = $account->iban;
$trigger->save();

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/*
* ProcessesExchangeRates.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\CurrencyExchangeRate;
use FireflyIII\Events\Model\CurrencyExchangeRate\CreatedCurrencyExchangeRate;
use FireflyIII\Events\Model\CurrencyExchangeRate\DestroyedCurrencyExchangeRate;
use FireflyIII\Events\Model\CurrencyExchangeRate\UpdatedCurrencyExchangeRate;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\Services\Internal\Recalculate\PrimaryAmountRecalculationService;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class ProcessesExchangeRates
{
public function handle(CreatedCurrencyExchangeRate|DestroyedCurrencyExchangeRate|UpdatedCurrencyExchangeRate $event): void
{
Preferences::mark();
Cache::clear();
if ($event instanceof DestroyedCurrencyExchangeRate) {
$this->handleCurrency($event->userGroup, $event->from);
$this->handleCurrency($event->userGroup, $event->to);
return;
}
$this->handleCurrency($event->rate->userGroup, $event->rate->fromCurrency);
$this->handleCurrency($event->rate->userGroup, $event->rate->toCurrency);
}
private function handleCurrency(UserGroup $userGroup, TransactionCurrency $currency): void
{
$calculator = new PrimaryAmountRecalculationService();
if (Amount::convertToPrimary()) {
Log::debug(sprintf('Will now convert amounts to primary currency for currency %s.', $currency->code));
$calculator->recalculateForGroupAndCurrency($userGroup, $currency);
// $calculator->recalculateForGroup($userGroup);
return;
}
Log::debug('Will NOT convert to primary currency.');
}
}

View File

@@ -40,8 +40,8 @@ class ProcessesUpdatedTransactionGroup
public function handle(UpdatedSingleTransactionGroup $event): void
{
Log::debug(sprintf('Now handling event %s', get_class($event)));
$this->unifyAccounts($event);
$effect = $this->unifyAccounts($event);
Log::debug(sprintf('Effect of unifyAccounts = %d', $effect));
Log::debug(sprintf('Transaction journal count is %d', $event->objects->transactionJournals->count()));
if (!$event->flags->applyRules) {
Log::debug(sprintf('Will NOT process rules for %d journal(s)', $event->objects->transactionJournals->count()));
@@ -63,7 +63,13 @@ class ProcessesUpdatedTransactionGroup
$this->createWebhookMessages($event->objects->transactionGroups, WebhookTrigger::UPDATE_TRANSACTION);
}
$this->removePeriodStatistics($event->objects);
$this->recalculateRunningBalance($event->objects);
if (0 === $effect && true === $event->flags->unifyOnly) {
Log::debug('Effect = 0, will not recalculate running balance.');
}
if (0 !== $effect || false === $event->flags->unifyOnly) {
Log::debug(sprintf('Effect is != 0 (%d) OR unifyOnly = false, will recalc running balance', $effect));
$this->recalculateRunningBalance($event->objects);
}
Log::debug('Done with handle() for UpdatedSingleTransactionGroup');
}
@@ -71,23 +77,26 @@ class ProcessesUpdatedTransactionGroup
/**
* This method will make sure all source / destination accounts are the same.
*/
protected function unifyAccounts(UpdatedSingleTransactionGroup $updatedGroupEvent): void
protected function unifyAccounts(UpdatedSingleTransactionGroup $updatedGroupEvent): int
{
Log::debug('Now in unifyAccounts()');
$effect = 0;
/** @var TransactionGroup $group */
foreach ($updatedGroupEvent->objects->transactionGroups as $group) {
$this->unifyAccountsForGroup($group);
$effect += $this->unifyAccountsForGroup($group);
}
Log::debug('Done with unifyAccounts()');
Log::debug(sprintf('Done with unifyAccounts(%d)', $effect));
return $effect;
}
private function unifyAccountsForGroup(TransactionGroup $group): void
private function unifyAccountsForGroup(TransactionGroup $group): int
{
if (1 === $group->transactionJournals->count()) {
Log::debug('Nothing to do in unifyAccounts()');
return;
return 0;
}
// first journal:
@@ -104,7 +113,7 @@ class ProcessesUpdatedTransactionGroup
if (null === $first) {
Log::warning(sprintf('Group #%d has no transaction journals.', $group->id));
return;
return 0;
}
$all = $group->transactionJournals()->get()->pluck('id')->toArray();
@@ -116,13 +125,30 @@ class ProcessesUpdatedTransactionGroup
$destAccount = $first->transactions()->where('amount', '>', '0')->first()->account;
$type = $first->transactionType->type;
$effect = 0;
if (TransactionTypeEnum::TRANSFER->value === $type || TransactionTypeEnum::WITHDRAWAL->value === $type) {
// set all source transactions to source account:
Transaction::whereIn('transaction_journal_id', $all)->where('amount', '<', 0)->update(['account_id' => $sourceAccount->id]);
$effect += Transaction::whereIn('transaction_journal_id', $all)
->where('account_id', '!=', $sourceAccount->id)
->where('amount', '<', 0)
->update(['account_id' => $sourceAccount->id])
;
}
if (TransactionTypeEnum::TRANSFER->value === $type || TransactionTypeEnum::DEPOSIT->value === $type) {
// set all destination transactions to destination account:
Transaction::whereIn('transaction_journal_id', $all)->where('amount', '>', 0)->update(['account_id' => $destAccount->id]);
$effect += Transaction::whereIn('transaction_journal_id', $all)
->where('account_id', '!=', $destAccount->id)
->where('amount', '>', 0)
->update(['account_id' => $destAccount->id])
;
}
if (0 === $effect) {
Log::debug(sprintf('Had nothing to do in unifyAccounts(#%d)', $group->id));
return 0;
}
Log::debug(sprintf('Updated %d transaction(s) in unifyAccounts(#%d)', $effect, $group->id));
return $effect;
}
}

View File

@@ -34,13 +34,13 @@ trait SupportsGroupProcessingTrait
return;
}
$array = $set->pluck('id')->toArray();
$array = array_unique($set->pluck('id')->toArray());
/** @var TransactionJournal $first */
$first = $set->first();
$journalIds = implode(',', $array);
$user = $first->user;
// Log::debug(sprintf('Add local operator for journal(s): %s', $journalIds));
Log::debug(sprintf('Fire rule engine for journal(s): %s', $journalIds));
// collect rules:
$ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
@@ -56,6 +56,7 @@ trait SupportsGroupProcessingTrait
$newRuleEngine->setUser($user);
$newRuleEngine->setRuleGroups($groups);
foreach ($array as $journalId) {
Log::debug(sprintf('Fire rule engine for journal #%d', $journalId));
$newRuleEngine->removeOperator('journal_id');
$newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalId]);
$newRuleEngine->fire();

View File

@@ -25,46 +25,17 @@ declare(strict_types=1);
namespace FireflyIII\Listeners\System;
use FireflyIII\Events\Preferences\UserGroupChangedPrimaryCurrency;
use FireflyIII\Models\Budget;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Services\Internal\Recalculate\PrimaryAmountRecalculationService;
use FireflyIII\Support\Facades\Amount;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class RecalculatesPrimaryCurrencyAmounts
{
public function handle(UserGroupChangedPrimaryCurrency $event): void
{
// Reset the primary currency amounts for all objects that have it.
Log::debug('Resetting primary currency amounts for all objects.');
$tables = [
// !!! this array is also in the migration
'accounts' => ['native_virtual_balance'],
'available_budgets' => ['native_amount'],
'bills' => ['native_amount_min', 'native_amount_max'],
];
foreach ($tables as $table => $columns) {
Log::debug(sprintf('Now processing table "%s"', $table));
foreach ($columns as $column) {
Log::debug(sprintf('Resetting column "%s" in table "%s".', $column, $table));
DB::table($table)->where('user_group_id', $event->userGroup->id)->update([$column => null]);
}
}
$this->resetPiggyBanks($event->userGroup);
$this->resetBudgets($event->userGroup);
$this->resetTransactions($event->userGroup);
Log::debug('Have now reset all primary amounts to NULL.');
// fire laravel command to recalculate them all.
if (Amount::convertToPrimary()) {
Log::debug('Will now convert amounts to primary currency.');
$calculator = new PrimaryAmountRecalculationService();
$calculator->recalculate();
@@ -72,87 +43,4 @@ class RecalculatesPrimaryCurrencyAmounts
}
Log::debug('Will NOT convert to primary currency.');
}
private function resetBudget(Budget $budget): void
{
foreach ($budget->autoBudgets as $autoBudget) {
if ('' === (string) $autoBudget->native_amount) {
continue;
}
Log::debug(sprintf('Resetting native_amount for budget #%d and auto budget #%d.', $budget->id, $autoBudget->id));
$autoBudget->native_amount = null;
$autoBudget->saveQuietly();
}
foreach ($budget->budgetlimits as $limit) {
if ('' !== (string) $limit->native_amount) {
Log::debug(sprintf('Resetting native_amount for budget #%d and budget limit #%d.', $budget->id, $limit->id));
$limit->native_amount = null;
$limit->saveQuietly();
}
}
}
private function resetBudgets(UserGroup $userGroup): void
{
$repository = app(BudgetRepositoryInterface::class);
$repository->setUserGroup($userGroup);
$set = $repository->getBudgets();
Log::debug(sprintf('Reset primary currency of %d budget(s).', $set->count()));
/** @var Budget $budget */
foreach ($set as $budget) {
$this->resetBudget($budget);
}
}
private function resetPiggyBank(PiggyBank $piggyBank): void
{
if ('' !== (string) $piggyBank->native_target_amount) {
Log::debug(sprintf('Resetting native_target_amount for piggy bank #%d.', $piggyBank->id));
$piggyBank->native_target_amount = null;
$piggyBank->saveQuietly();
}
foreach ($piggyBank->accounts as $account) {
if ('' !== (string) $account->pivot->native_current_amount) {
Log::debug(sprintf('Resetting native_current_amount for piggy bank #%d and account #%d.', $piggyBank->id, $account->id));
$account->pivot->native_current_amount = null;
$account->pivot->save();
}
}
foreach ($piggyBank->piggyBankEvents as $event) {
if ('' !== (string) $event->native_amount) {
Log::debug(sprintf('Resetting native_amount for piggy bank #%d and event #%d.', $piggyBank->id, $event->id));
$event->native_amount = null;
$event->saveQuietly();
}
}
}
private function resetPiggyBanks(UserGroup $userGroup): void
{
$repository = app(PiggyBankRepositoryInterface::class);
$repository->setUserGroup($userGroup);
$piggyBanks = $repository->getPiggyBanks();
Log::debug(sprintf('Reset primary currency of %d piggy bank(s).', $piggyBanks->count()));
/** @var PiggyBank $piggyBank */
foreach ($piggyBanks as $piggyBank) {
$this->resetPiggyBank($piggyBank);
}
}
private function resetTransactions(UserGroup $userGroup): void
{
// custom query because of the potential size of this update.
$success = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(static function (Builder $q): void {
$q->whereNotNull('native_amount')->orWhereNotNull('native_foreign_amount');
})
->update(['native_amount' => null, 'native_foreign_amount' => null])
;
Log::debug(sprintf('Reset %d transactions.', $success));
}
}

View File

@@ -59,6 +59,11 @@ class CurrencyExchangeRate extends Model
return $this->belongsTo(User::class);
}
public function userGroup(): BelongsTo
{
return $this->belongsTo(UserGroup::class);
}
protected function casts(): array
{
return [

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Notifications;
use Exception;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\User;
use GuzzleHttp\Exception\ClientException;
use Illuminate\Notifications\Notification;
@@ -36,8 +37,16 @@ class NotificationSender
{
public static function send(OwnerNotifiable|User $user, Notification $notification): void
{
// ::locale($user->locale))
$lang = config('firefly.default_language');
Log::debug(sprintf('Notification send language defaults to "%s"', $lang));
if ($user instanceof User) {
$lang = Preferences::getForUser($user, 'language', $lang)->data;
Log::debug(sprintf('Notification send language set to "%s"', $lang));
}
try {
NotificationFacade::send($user, $notification);
NotificationFacade::locale($lang)->send($user, $notification);
} catch (ClientException $e) {
Log::error(sprintf('[a] Error sending notification: %s', $e->getMessage()));
} catch (Exception $e) {

View File

@@ -48,9 +48,10 @@ class UserTestNotificationEmail extends Notification
public function toMail(User $notifiable): MailMessage
{
$address = (string) $notifiable->email;
$link = route('index');
return new MailMessage()
->markdown('emails.admin-test', ['email' => $address])
->markdown('emails.admin-test', ['email' => $address, 'link' => $link])
->subject((string) trans('email.admin_test_subject'))
;
}

View File

@@ -60,11 +60,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
public function count(array $types): int
{
return $this->user
->accounts()
->accountTypeIn($types)
->count()
;
return $this->user->accounts()->accountTypeIn($types)->count();
}
/**
@@ -88,12 +84,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
/** @var Account $account */
foreach ($accounts as $account) {
$byName = $this->user
->accounts()
->where('name', $account->name)
->where('id', '!=', $account->id)
->first()
;
$byName = $this->user->accounts()->where('name', $account->name)->where('id', '!=', $account->id)->first();
if (null !== $byName) {
$result->push($account);
$result->push($byName);
@@ -101,12 +92,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
continue;
}
if (null !== $account->iban) {
$byIban = $this->user
->accounts()
->where('iban', $account->iban)
->where('id', '!=', $account->id)
->first()
;
$byIban = $this->user->accounts()->where('iban', $account->iban)->where('id', '!=', $account->id)->first();
if (null !== $byIban) {
$result->push($account);
$result->push($byIban);
@@ -151,11 +137,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
public function findByIbanNull(string $iban, array $types): ?Account
{
$iban = Steam::filterSpaces($iban);
$query = $this->user
->accounts()
->where('iban', '!=', '')
->whereNotNull('iban')
;
$query = $this->user->accounts()->where('iban', '!=', '')->whereNotNull('iban');
if (0 !== count($types)) {
$query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
@@ -453,12 +435,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
$type = AccountType::where('type', AccountTypeEnum::RECONCILIATION->value)->first();
/** @var null|Account $current */
$current = $this->user
->accounts()
->where('account_type_id', $type->id)
->where('name', $name)
->first()
;
$current = $this->user->accounts()->where('account_type_id', $type->id)->where('name', $name)->first();
if (null !== $current) {
return $current;

View File

@@ -87,11 +87,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
*/
public function correctOrder(): void
{
$set = $this->user
->bills()
->orderBy('order', 'ASC')
->get()
;
$set = $this->user->bills()->orderBy('order', 'ASC')->get();
$current = 1;
foreach ($set as $bill) {
if ($bill->order !== $current) {
@@ -110,12 +106,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
if (null === $withdrawal) {
return;
}
$this->user
->transactionJournals()
->whereNotNull('bill_id')
->where('transaction_type_id', '!=', $withdrawal->id)
->update(['bill_id' => null])
;
$this->user->transactionJournals()->whereNotNull('bill_id')->where('transaction_type_id', '!=', $withdrawal->id)->update(['bill_id' => null]);
}
public function destroy(Bill $bill): bool
@@ -174,11 +165,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
public function findByName(string $name): ?Bill
{
/** @var null|Bill */
return $this->user
->bills()
->where('name', $name)
->first(['bills.*'])
;
return $this->user->bills()->where('name', $name)->first(['bills.*']);
}
public function getActiveBills(): Collection
@@ -261,11 +248,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
*/
public function getByIds(array $billIds): Collection
{
return $this->user
->bills()
->whereIn('id', $billIds)
->get()
;
return $this->user->bills()->whereIn('id', $billIds)->get();
}
/**
@@ -329,12 +312,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
public function getPaginator(int $size): LengthAwarePaginator
{
return $this->user
->bills()
->orderBy('active', 'DESC')
->orderBy('name', 'ASC')
->paginate($size)
;
return $this->user->bills()->orderBy('active', 'DESC')->orderBy('name', 'ASC')->paginate($size);
}
/**
@@ -638,9 +616,10 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
/** @var Bill $bill */
foreach ($bills as $bill) {
// Log::debug(sprintf('Bill #%d ("%s")', $bill->id, $bill->name));
/** @var Collection $set */
$set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']);
$currency = $convertToPrimary && $bill->transactionCurrency->id !== $primary->id ? $primary : $bill->transactionCurrency;
$set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']);
$currency = $convertToPrimary && $bill->transactionCurrency->id !== $primary->id ? $primary : $bill->transactionCurrency;
$return[(int) $currency->id] ??= [
'id' => (string) $currency->id,
'name' => $currency->name,
@@ -649,13 +628,14 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
'decimal_places' => $currency->decimal_places,
'sum' => '0',
];
$setAmount = '0';
// Log::debug(sprintf('Created a new array for currency #%d', $currency->id));
/** @var TransactionJournal $transactionJournal */
foreach ($set as $transactionJournal) {
// grab currency from transaction.
$transactionCurrency = $transactionJournal->transactionCurrency;
$return[(int) $transactionCurrency->id] ??= [
// grab currency from journal.
$transactionCurrency = $transactionJournal->transactionCurrency;
$currencyId = (int) $transactionCurrency->id;
$return[$currencyId] ??= [
'id' => (string) $transactionCurrency->id,
'name' => $transactionCurrency->name,
'symbol' => $transactionCurrency->symbol,
@@ -663,19 +643,12 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
'decimal_places' => $transactionCurrency->decimal_places,
'sum' => '0',
];
$amountFromJournal = Amount::getAmountFromJournalObject($transactionJournal);
// Log::debug(sprintf('Created a (new) array for currency #%d', $currencyId));
// Log::debug(sprintf('Amount to add is %s', $amountFromJournal));
// get currency from transaction as well.
$return[(int) $transactionCurrency->id]['sum'] = bcadd(
$return[(int) $transactionCurrency->id]['sum'],
Amount::getAmountFromJournalObject($transactionJournal)
);
// $setAmount = bcadd($setAmount, Amount::getAmountFromJournalObject($transactionJournal));
$return[$currencyId]['sum'] = bcadd($return[$currencyId]['sum'], $amountFromJournal);
}
// Log::debug(sprintf('Bill #%d ("%s") with %d transaction(s) and sum %s %s', $bill->id, $bill->name, $set->count(), $currency->code, $setAmount));
// $return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], $setAmount);
// Log::debug(sprintf('Total sum is now %s', $return[$currency->id]['sum']));
}
// remove empty sets
$final = [];
@@ -731,11 +704,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
public function unlinkAll(Bill $bill): void
{
$this->user
->transactionJournals()
->where('bill_id', $bill->id)
->update(['bill_id' => null])
;
$this->user->transactionJournals()->where('bill_id', $bill->id)->update(['bill_id' => null]);
}
/**

View File

@@ -79,11 +79,7 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
->toArray()
;
// delete available budgets without these currencies.
$this->user
->availableBudgets()
->whereNotIn('transaction_currency_id', $currencies)
->delete()
;
$this->user->availableBudgets()->whereNotIn('transaction_currency_id', $currencies)->delete();
}
/**
@@ -180,11 +176,7 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
*/
public function getAvailableBudgetsByCurrency(TransactionCurrency $currency): Collection
{
return $this->user
->availableBudgets()
->where('transaction_currency_id', $currency->id)
->get()
;
return $this->user->availableBudgets()->where('transaction_currency_id', $currency->id)->get();
}
/**
@@ -209,24 +201,14 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
*/
public function getAvailableBudgetsByExactDate(Carbon $start, Carbon $end): Collection
{
return $this->user
->availableBudgets()
->where('start_date', '=', $start->format('Y-m-d'))
->where('end_date', '=', $end->format('Y-m-d'))
->get()
;
return $this->user->availableBudgets()->where('start_date', '=', $start->format('Y-m-d'))->where('end_date', '=', $end->format('Y-m-d'))->get();
}
public function getAvailableBudgetWithCurrency(Carbon $start, Carbon $end): array
{
Log::debug(sprintf('Now in %s(%s, %s)', __METHOD__, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
$return = [];
$availableBudgets = $this->user
->availableBudgets()
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))
->get()
;
$availableBudgets = $this->user->availableBudgets()->where('start_date', $start->format('Y-m-d'))->where('end_date', $end->format('Y-m-d'))->get();
Log::debug(sprintf('Found %d available budgets (already converted)', $availableBudgets->count()));

View File

@@ -227,11 +227,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$budget->saveQuietly();
}
// other budgets, set to 0.
$this->user
->budgets()
->where('active', 0)
->update(['order' => 0])
;
$this->user->budgets()->where('active', 0)->update(['order' => 0]);
return true;
}
@@ -316,11 +312,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$query = sprintf('%%%s%%', $name);
/** @var null|Budget */
return $this->user
->budgets()
->whereLike('name', $query)
->first()
;
return $this->user->budgets()->whereLike('name', $query)->first();
}
/**
@@ -368,12 +360,11 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function getBudgets(): Collection
{
return $this->user
->budgets()
->orderBy('order', 'ASC')
->orderBy('name', 'ASC')
->get()
;
if (null === $this->user) {
return $this->userGroup->budgets()->orderBy('order', 'ASC')->orderBy('name', 'ASC')->get();
}
return $this->user->budgets()->orderBy('order', 'ASC')->orderBy('name', 'ASC')->get();
}
/**
@@ -381,11 +372,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
*/
public function getByIds(array $budgetIds): Collection
{
return $this->user
->budgets()
->whereIn('id', $budgetIds)
->get()
;
return $this->user->budgets()->whereIn('id', $budgetIds)->get();
}
public function getInactiveBudgets(): Collection

View File

@@ -111,11 +111,7 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
public function findByName(string $name): ?Category
{
/** @var null|Category */
return $this->user
->categories()
->where('name', $name)
->first(['categories.*'])
;
return $this->user->categories()->where('name', $name)->first(['categories.*']);
}
/**
@@ -184,11 +180,7 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
*/
public function getByIds(array $categoryIds): Collection
{
return $this->user
->categories()
->whereIn('id', $categoryIds)
->get()
;
return $this->user->categories()->whereIn('id', $categoryIds)->get();
}
/**
@@ -196,12 +188,7 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
*/
public function getCategories(): Collection
{
return $this->user
->categories()
->with(['attachments'])
->orderBy('name', 'ASC')
->get()
;
return $this->user->categories()->with(['attachments'])->orderBy('name', 'ASC')->get();
}
public function getNoteText(Category $category): ?string

View File

@@ -155,12 +155,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
}
// is the default currency for the user or the system
$count = $this->userGroup
->currencies()
->where('transaction_currencies.id', $currency->id)
->wherePivot('group_default', 1)
->count()
;
$count = $this->userGroup->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('group_default', 1)->count();
if ($count > 0) {
Log::info('Is the default currency of the user, return true.');
@@ -168,12 +163,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
}
// is the default currency for the user or the system
$count = $this->userGroup
->currencies()
->where('transaction_currencies.id', $currency->id)
->wherePivot('group_default', 1)
->count()
;
$count = $this->userGroup->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('group_default', 1)->count();
if ($count > 0) {
Log::info('Is the default currency of the user group, return true.');
@@ -298,12 +288,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
public function get(): Collection
{
$all = $this->userGroup
->currencies()
->orderBy('code', 'ASC')
->withPivot(['group_default'])
->get()
;
$all = $this->userGroup->currencies()->orderBy('code', 'ASC')->withPivot(['group_default'])->get();
$all->map(static function (TransactionCurrency $current): TransactionCurrency {
$current->userGroupEnabled = true;
$current->userGroupNative = 1 === (int) $current->pivot->group_default;

View File

@@ -40,31 +40,18 @@ class ExchangeRateRepository implements ExchangeRateRepositoryInterface, UserGro
#[Override]
public function deleteRate(CurrencyExchangeRate $rate): void
{
$this->userGroup
->currencyExchangeRates()
->where('id', $rate->id)
->delete()
;
$this->userGroup->currencyExchangeRates()->where('id', $rate->id)->delete();
}
public function deleteRates(TransactionCurrency $from, TransactionCurrency $to): void
{
$this->userGroup
->currencyExchangeRates()
->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id)
->delete()
;
$this->userGroup->currencyExchangeRates()->where('from_currency_id', $from->id)->where('to_currency_id', $to->id)->delete();
}
#[Override]
public function getAll(): Collection
{
return $this->userGroup
->currencyExchangeRates()
->orderBy('date', 'ASC')
->get()
;
return $this->userGroup->currencyExchangeRates()->orderBy('date', 'ASC')->get();
}
#[Override]

View File

@@ -130,11 +130,7 @@ class JournalRepository implements JournalRepositoryInterface, UserGroupInterfac
public function firstNull(): ?TransactionJournal
{
/** @var null|TransactionJournal */
return $this->user
->transactionJournals()
->orderBy('date', 'ASC')
->first(['transaction_journals.*'])
;
return $this->user->transactionJournals()->orderBy('date', 'ASC')->first(['transaction_journals.*']);
}
#[Override]
@@ -183,11 +179,7 @@ class JournalRepository implements JournalRepositoryInterface, UserGroupInterfac
public function getLast(): ?TransactionJournal
{
/** @var null|TransactionJournal */
return $this->user
->transactionJournals()
->orderBy('date', 'DESC')
->first(['transaction_journals.*'])
;
return $this->user->transactionJournals()->orderBy('date', 'DESC')->first(['transaction_journals.*']);
}
public function getLinkNoteText(TransactionJournalLink $link): string
@@ -241,11 +233,7 @@ class JournalRepository implements JournalRepositoryInterface, UserGroupInterfac
#[Override]
public function getUncompletedJournals(): Collection
{
return $this->userGroup
->transactionJournals()
->where('completed', false)
->get(['transaction_journals.*'])
;
return $this->userGroup->transactionJournals()->where('completed', false)->get(['transaction_journals.*']);
}
#[Override]
@@ -266,11 +254,7 @@ class JournalRepository implements JournalRepositoryInterface, UserGroupInterfac
*/
public function searchJournalDescriptions(string $search, int $limit): Collection
{
$query = $this->user
->transactionJournals()
->orderBy('date', 'DESC')
->orderBy('description', 'ASC')
;
$query = $this->user->transactionJournals()->orderBy('date', 'DESC')->orderBy('description', 'ASC');
if ('' !== $search) {
$query->whereLike('description', sprintf('%%%s%%', $search));
}

View File

@@ -96,11 +96,7 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface, UserGroupInterf
*/
public function findSpecificLink(LinkType $linkType, TransactionJournal $inward, TransactionJournal $outward): ?TransactionJournalLink
{
return TransactionJournalLink::where('link_type_id', $linkType->id)
->where('source_id', $inward->id)
->where('destination_id', $outward->id)
->first()
;
return TransactionJournalLink::where('link_type_id', $linkType->id)->where('source_id', $inward->id)->where('destination_id', $outward->id)->first();
}
public function get(): Collection

View File

@@ -34,21 +34,13 @@ trait CreatesObjectGroups
protected function findObjectGroup(string $title): ?ObjectGroup
{
/** @var null|ObjectGroup */
return $this->user
->objectGroups()
->where('title', $title)
->first()
;
return $this->user->objectGroups()->where('title', $title)->first();
}
protected function findObjectGroupById(int $groupId): ?ObjectGroup
{
/** @var null|ObjectGroup */
return $this->user
->objectGroups()
->where('id', $groupId)
->first()
;
return $this->user->objectGroups()->where('id', $groupId)->first();
}
protected function findOrCreateObjectGroup(string $title): ?ObjectGroup
@@ -74,10 +66,6 @@ trait CreatesObjectGroups
protected function hasObjectGroup(string $title): bool
{
return 1 === $this->user
->objectGroups()
->where('title', $title)
->count()
;
return 1 === $this->user->objectGroups()->where('title', $title)->count();
}
}

View File

@@ -122,11 +122,7 @@ class ObjectGroupRepository implements ObjectGroupRepositoryInterface, UserGroup
public function search(string $query, int $limit): Collection
{
$dbQuery = $this->user
->objectGroups()
->orderBy('order', 'ASC')
->orderBy('title', 'ASC')
;
$dbQuery = $this->user->objectGroups()->orderBy('order', 'ASC')->orderBy('title', 'ASC');
if ('' !== $query) {
// split query on spaces just in case:
$parts = explode(' ', $query);

View File

@@ -100,11 +100,7 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
*/
public function getAll(): Collection
{
return $this->user
->rules()
->with(['ruleGroup', 'ruleTriggers', 'ruleActions'])
->get()
;
return $this->user->rules()->with(['ruleGroup', 'ruleTriggers', 'ruleActions'])->get();
}
/**
@@ -309,11 +305,7 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
$ruleGroup = $this->user->ruleGroups()->find($data['rule_group_id']);
}
if (array_key_exists('rule_group_title', $data)) {
$ruleGroup = $this->user
->ruleGroups()
->where('title', $data['rule_group_title'])
->first()
;
$ruleGroup = $this->user->ruleGroups()->where('title', $data['rule_group_title'])->first();
}
if (null === $ruleGroup) {
throw new FireflyException('No such rule group.');

View File

@@ -115,20 +115,12 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
public function findByTitle(string $title): ?RuleGroup
{
/** @var null|RuleGroup */
return $this->user
->ruleGroups()
->where('title', $title)
->first()
;
return $this->user->ruleGroups()->where('title', $title)->first();
}
public function get(): Collection
{
return $this->user
->ruleGroups()
->orderBy('order', 'ASC')
->get()
;
return $this->user->ruleGroups()->orderBy('order', 'ASC')->get();
}
public function getActiveGroups(): Collection
@@ -278,11 +270,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
public function maxOrder(): int
{
return (int) $this->user
->ruleGroups()
->where('active', true)
->max('order')
;
return (int) $this->user->ruleGroups()->where('active', true)->max('order');
}
public function resetOrder(): bool

View File

@@ -100,11 +100,7 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
public function findByTag(string $tag): ?Tag
{
/** @var null|Tag */
return $this->user
->tags()
->where('tag', $tag)
->first()
;
return $this->user->tags()->where('tag', $tag)->first();
}
public function firstUseDate(Tag $tag): ?Carbon
@@ -114,11 +110,7 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
public function get(): Collection
{
return $this->user
->tags()
->orderBy('tag', 'ASC')
->get(['tags.*'])
;
return $this->user->tags()->orderBy('tag', 'ASC')->get(['tags.*']);
}
public function getAttachments(Tag $tag): Collection
@@ -150,11 +142,7 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
public function getTagsInYear(?int $year): array
{
// get all tags in the year (if present):
$tagQuery = $this->user
->tags()
->with(['locations', 'attachments'])
->orderBy('tags.tag')
;
$tagQuery = $this->user->tags()->with(['locations', 'attachments'])->orderBy('tags.tag');
// add date range (or not):
if (null === $year) {
@@ -207,23 +195,13 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
public function newestTag(): ?Tag
{
/** @var null|Tag */
return $this->user
->tags()
->whereNotNull('date')
->orderBy('date', 'DESC')
->first()
;
return $this->user->tags()->whereNotNull('date')->orderBy('date', 'DESC')->first();
}
public function oldestTag(): ?Tag
{
/** @var null|Tag */
return $this->user
->tags()
->whereNotNull('date')
->orderBy('date', 'ASC')
->first()
;
return $this->user->tags()->whereNotNull('date')->orderBy('date', 'ASC')->first();
}
#[Override]
@@ -274,11 +252,7 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
{
$search = sprintf('%%%s%%', $query);
return $this->user
->tags()
->whereLike('tag', $search)
->get(['tags.*'])
;
return $this->user->tags()->whereLike('tag', $search)->get(['tags.*']);
}
/**
@@ -381,22 +355,14 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
{
$search = sprintf('%%%s', $query);
return $this->user
->tags()
->whereLike('tag', $search)
->get(['tags.*'])
;
return $this->user->tags()->whereLike('tag', $search)->get(['tags.*']);
}
public function tagStartsWith(string $query): Collection
{
$search = sprintf('%s%%', $query);
return $this->user
->tags()
->whereLike('tag', $search)
->get(['tags.*'])
;
return $this->user->tags()->whereLike('tag', $search)->get(['tags.*']);
}
public function transferredInPeriod(Tag $tag, Carbon $start, Carbon $end): array

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