Compare commits

...

106 Commits

Author SHA1 Message Date
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
github-actions[bot]
490c421ae5 Merge pull request #11957 from firefly-iii/develop
🤖 Automatically merge the PR into the main branch.
2026-03-15 17:36:55 +01:00
github-actions[bot]
48e5adbbfd Merge pull request #11956 from firefly-iii/release-1773592601
🤖 Automatically merge the PR into the develop branch.
2026-03-15 17:36:50 +01:00
JC5
b123f7e6f1 🤖 Auto commit for release 'v6.5.6' on 2026-03-15 2026-03-15 17:36:42 +01:00
github-actions[bot]
bff351bad2 Merge pull request #11955 from firefly-iii/release-1773592137
🤖 Automatically merge the PR into the develop branch.
2026-03-15 17:29:07 +01:00
JC5
2f0a3238c3 🤖 Auto commit for release 'develop' on 2026-03-15 2026-03-15 17:28:57 +01:00
James Cole
dc88781607 Add missing translation. 2026-03-15 17:23:05 +01:00
James Cole
d6c2698eae Fix https://github.com/firefly-iii/firefly-iii/issues/11954 and https://github.com/firefly-iii/firefly-iii/issues/11953 2026-03-15 17:15:22 +01:00
github-actions[bot]
aee804940b Merge pull request #11952 from firefly-iii/develop
🤖 Automatically merge the PR into the main branch.
2026-03-15 08:05:57 +01:00
github-actions[bot]
b53a756e5a Merge pull request #11951 from firefly-iii/release-1773558345
🤖 Automatically merge the PR into the develop branch.
2026-03-15 08:05:52 +01:00
JC5
ff5d83eba5 🤖 Auto commit for release 'v6.5.5' on 2026-03-15 2026-03-15 08:05:46 +01:00
James Cole
4c10c4a26f Change title for changelog. 2026-03-15 08:00:28 +01:00
James Cole
f75817b44d Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-03-15 07:51:38 +01:00
github-actions[bot]
9ad5dfd45b Merge pull request #11950 from firefly-iii/release-1773557001
🤖 Automatically merge the PR into the develop branch.
2026-03-15 07:43:27 +01:00
JC5
9e3b8e6232 🤖 Auto commit for release 'develop' on 2026-03-15 2026-03-15 07:43:21 +01:00
James Cole
6c3b4a77b5 Small changes 2026-03-15 07:42:27 +01:00
James Cole
34b5d9fcf6 Update changelog. 2026-03-15 07:37:41 +01:00
James Cole
12bbc91dca Some minor code cleanup. 2026-03-15 06:48:11 +01:00
James Cole
a77ff6a51f Expand changelog with fixed issues. 2026-03-14 20:44:58 +01:00
James Cole
62eb054c7f Fix class/id value. 2026-03-14 20:31:16 +01:00
James Cole
13536a2f65 Clean up some phpstan issues. 2026-03-14 20:26:38 +01:00
github-actions[bot]
19dece287e Merge pull request #11949 from firefly-iii/release-1773489415
🤖 Automatically merge the PR into the develop branch.
2026-03-14 12:57:02 +01:00
JC5
897e1f773e 🤖 Auto commit for release 'develop' on 2026-03-14 2026-03-14 12:56:55 +01:00
James Cole
5788e18d6d Fix inline CSS. 2026-03-14 12:39:33 +01:00
github-actions[bot]
c8c4816fe8 Merge pull request #11948 from firefly-iii/release-1773486711
🤖 Automatically merge the PR into the develop branch.
2026-03-14 12:12:01 +01:00
JC5
aa57252b11 🤖 Auto commit for release 'develop' on 2026-03-14 2026-03-14 12:11:52 +01:00
James Cole
58e4c26a87 Fix call to budget repos. 2026-03-14 12:05:45 +01:00
github-actions[bot]
ac323f11c4 Merge pull request #11946 from firefly-iii/release-1773474241
🤖 Automatically merge the PR into the develop branch.
2026-03-14 08:44:10 +01:00
JC5
a907f9b2f7 🤖 Auto commit for release 'develop' on 2026-03-14 2026-03-14 08:44:01 +01:00
James Cole
8abd2a6604 Add text about AI reports. 2026-03-14 08:36:39 +01:00
James Cole
0780390ff6 Update changelog. 2026-03-14 07:46:47 +01:00
James Cole
b9d1ed28a5 Update dev mode settings. 2026-03-14 07:31:16 +01:00
James Cole
b8ebcdf1a8 Remove CSS, only chartJS remains 2026-03-14 06:40:23 +01:00
James Cole
ac8dbbff6c Remove CSS and unused files. 2026-03-14 06:25:11 +01:00
James Cole
fea89c5231 Remove and replace inline styles. 2026-03-14 06:16:53 +01:00
James Cole
654f2ee489 Remove inline CSS 2026-03-14 06:02:40 +01:00
James Cole
5c18624cf9 Fix https://github.com/firefly-iii/firefly-iii/issues/11944 2026-03-14 05:37:30 +01:00
James Cole
27ba8e842a Remove CSS 2026-03-13 20:47:42 +01:00
James Cole
e504ee204a Remove styles. 2026-03-13 20:44:51 +01:00
James Cole
a3a332643c Replace styles with classes. 2026-03-13 20:35:39 +01:00
James Cole
464a89f305 Remove lots of style attributes. 2026-03-13 20:22:50 +01:00
James Cole
062c2323e3 Clean up and expand css styles. 2026-03-13 19:49:56 +01:00
James Cole
a37f995872 Can be returned directly. 2026-03-13 17:36:03 +01:00
James Cole
a5b6315cb8 Clean up transaction link code. 2026-03-13 17:35:39 +01:00
github-actions[bot]
ff568653c8 Merge pull request #11940 from firefly-iii/release-1773385883
🤖 Automatically merge the PR into the develop branch.
2026-03-13 08:11:30 +01:00
JC5
21447a9b2f 🤖 Auto commit for release 'develop' on 2026-03-13 2026-03-13 08:11:23 +01:00
James Cole
238bfc819e Catch a null pointer. 2026-03-13 08:05:04 +01:00
James Cole
3fab4668fc Move copyright. 2026-03-13 04:14:46 +01:00
James Cole
190050d6cf Rate limit mail message. 2026-03-13 04:12:32 +01:00
James Cole
45d623e0c1 Fix some linting issues 2026-03-13 04:05:38 +01:00
257 changed files with 2425 additions and 1467 deletions

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,50 @@
<!--
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
Wanna talk to me? Open a GitHub Issue, Discussion, or email me: james@firefly-iii.org
See also: https://docs.firefly-iii.org/explanation/support/#contributing-code
👀 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.
Thanks again, 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!
-->

25
.github/security.md vendored
View File

@@ -3,13 +3,12 @@
Firefly III is an application to manage your personal finances. As such, the developer has adopted this security
disclosure and response policy to ensure that critical issues are responsibly handled.
## Supported Versions
## Supported versions
Only the latest Firefly III release is maintained. Applicable fixes, including security fixes, will not backported to
older release branches. Please refer to [releases.md](https://github.com/firefly-iii/firefly-iii/blob/main/releases.md)
for details.
older release branches. Please refer to [releases.md](https://github.com/firefly-iii/firefly-iii/blob/main/releases.md) for details.
## Reporting a Vulnerability - Private Disclosure Process
## Reporting a vulnerability - private disclosure process
Security is of the highest importance and all security vulnerabilities or suspected security vulnerabilities should be
reported to Firefly III privately, to minimize attacks against current users of Firefly III before they are fixed.
@@ -28,7 +27,7 @@ within 3 business days, including a detailed plan to investigate the issue and a
the meantime. Do not report non-security-impacting bugs through this channel.
Use [GitHub issues](https://github.com/firefly-iii/firefly-iii/issues/new/choose) instead.
### Proposed Email Content
### Proposed email content
Provide a descriptive subject line and in the body of the email include the following information:
@@ -47,7 +46,7 @@ Provide a descriptive subject line and in the body of the email include the foll
* When you know of or suspect a potential vulnerability on another project that is used by Firefly III. For example
Firefly III has a dependency on Docker, MySQL, etc.
## Patch, Release, and Disclosure
## Patch, release, and disclosure
The Firefly III developer will respond to vulnerability reports as follows:
@@ -75,7 +74,7 @@ The Firefly III developer will respond to vulnerability reports as follows:
8. Once the fix is confirmed, the developer will patch the vulnerability in the next patch or minor release. Upon
release of the patched version of Firefly III, we will follow the **Public Disclosure Process**.
### Public Disclosure Process
### Public disclosure process
The developer publishes a public [advisory](https://github.com/firefly-iii/firefly-iii/security/advisories) to the
Firefly III community via GitHub. In most cases, additional communication via Mastodon, Gitter and other channels will
@@ -97,6 +96,18 @@ III to provide a hardened Firefly III environment. We will not act on any securi
safe defaults. Over time, we will work towards improved safe-by-default configuration, taking into account backwards
compatibility.
## Security scanning through automated means
There is some additional guidance for security vulnerabilities or suspected security vulnerabilities that have been
found with the full or partial support of AI coding agents, large language models and other code-scanning tools. These reports are often not applicable, not actually a vulnerability, or just plain wrong. This takes time away from responding to
*actual* security vulnerabilities or suspected security vulnerabilities. If you use automated means to search for security vulnerabilities in the Firefly III code base, please take care to:
1. manually validate the results before you submit a report,
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,7 @@ 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
- 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

@@ -185,6 +185,8 @@ final class DestroyController extends Controller
/** @var BudgetRepositoryInterface $budgetRepository */
$budgetRepository = app(BudgetRepositoryInterface::class);
$budgetRepository->destroyAll();
$abRepository->cleanup();
}
private function destroyCategories(): void

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

@@ -1,10 +1,8 @@
<?php
declare(strict_types=1);
/*
* TriggerController.php
* Copyright (c) 2025 james@firefly-iii.org
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -22,6 +20,8 @@ declare(strict_types=1);
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Models\Recurrence;
use FireflyIII\Api\V1\Controllers\Controller;

View File

@@ -98,7 +98,7 @@ final class TriggerController extends Controller
$enrichment->setUser($rule->user);
$transactions = $enrichment->enrich($transactions);
$paginator = new LengthAwarePaginator($transactions, $count, 31337, $this->parameters->get('page'));
$paginator = new LengthAwarePaginator($transactions, $count, 31_337, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.rules.test', [$rule->id]).$this->buildParams());
// resulting list is presented as JSON thing.

View File

@@ -105,7 +105,7 @@ final class TriggerController extends Controller
$enrichment->setUser($group->user);
$transactions = $enrichment->enrich($transactions);
$paginator = new LengthAwarePaginator($transactions, $count, 31337, $this->parameters->get('page'));
$paginator = new LengthAwarePaginator($transactions, $count, 31_337, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.rule-groups.test', [$group->id]).$this->buildParams());
// resulting list is presented as JSON thing.

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

@@ -71,6 +71,7 @@ final class DestroyController extends Controller
if (false === $linkType->editable) {
throw new FireflyException('200020: Link type cannot be changed.');
}
$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

@@ -81,6 +81,7 @@ final class BasicController extends Controller
$this->accountRepository->setUser($user);
$this->abRepository->setUser($user);
$this->opsRepository->setUser($user);
$this->abRepository->cleanup();
return $next($request);
});
@@ -322,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

@@ -1,7 +1,5 @@
<?php
declare(strict_types=1);
/*
* BatchController.php
* Copyright (c) 2026 james@firefly-iii.org
@@ -22,6 +20,8 @@ declare(strict_types=1);
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\System;
use FireflyIII\Api\V1\Controllers\Controller;

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

@@ -1,10 +1,8 @@
<?php
declare(strict_types=1);
/*
* AutocompleteApiRequest.php
* Copyright (c) 2025 james@firefly-iii.org
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -22,6 +20,8 @@ declare(strict_types=1);
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests\Autocomplete;
use FireflyIII\Api\V1\Requests\AggregateFormRequest;

View File

@@ -28,7 +28,7 @@ use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Contracts\Validation\Validator;
use FireflyIII\Validation\FireflyValidator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Log;
@@ -69,9 +69,9 @@ class ChartRequest extends FormRequest
];
}
public function withValidator(Validator $validator): void
public function withValidator(FireflyValidator $validator): void
{
$validator->after(static function (Validator $validator): void {
$validator->after(static function (FireflyValidator $validator): void {
// validate transaction query data.
$data = $validator->getData();
if (!array_key_exists('accounts', $data)) {

View File

@@ -27,7 +27,7 @@ namespace FireflyIII\Api\V1\Requests\Data\Bulk;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Contracts\Validation\Validator;
use FireflyIII\Validation\FireflyValidator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Log;
@@ -61,9 +61,9 @@ class MoveTransactionsRequest extends FormRequest
* Configure the validator instance with special rules for after the basic validation rules.
* TODO this is duplicate.
*/
public function withValidator(Validator $validator): void
public function withValidator(FireflyValidator $validator): void
{
$validator->after(function (Validator $validator): void {
$validator->after(function (FireflyValidator $validator): void {
// validate start before end only if both are there.
$data = $validator->getData();
if (array_key_exists('original_account', $data) && array_key_exists('destination_account', $data)) {
@@ -75,7 +75,7 @@ class MoveTransactionsRequest extends FormRequest
}
}
private function validateMove(Validator $validator): void
private function validateMove(FireflyValidator $validator): void
{
$data = $validator->getData();
$repository = app(AccountRepositoryInterface::class);

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

@@ -33,7 +33,7 @@ use FireflyIII\Rules\UniqueIban;
use FireflyIII\Support\Request\AppendsLocationData;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Contracts\Validation\Validator;
use FireflyIII\Validation\FireflyValidator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Log;
@@ -121,9 +121,9 @@ class UpdateRequest extends FormRequest
/**
* Configure the validator instance with special rules for after the basic validation rules.
*/
public function withValidator(Validator $validator): void
public function withValidator(FireflyValidator $validator): void
{
$validator->after(function (Validator $validator): void {
$validator->after(function (FireflyValidator $validator): void {
// validate start before end only if both are there.
$data = $validator->getData();

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

@@ -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

@@ -45,7 +45,7 @@ class StoreRequest extends FormRequest
public function getAll(): array
{
$active = true;
$order = 31337;
$order = 31_337;
if (null !== $this->get('active')) {
$active = $this->boolean('active');
}

View File

@@ -25,7 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests\Models\TransactionCurrency;
use FireflyIII\Api\V1\Requests\ApiRequest;
use Illuminate\Contracts\Validation\Validator;
use FireflyIII\Validation\FireflyValidator;
class CurrencyCodeRequest extends ApiRequest
{
@@ -34,10 +34,10 @@ class CurrencyCodeRequest extends ApiRequest
return ['code' => sprintf('exists:transaction_currencies,code|%s', $this->required)];
}
public function withValidator(Validator $validator): void
public function withValidator(FireflyValidator $validator): void
{
$validator->after(function (Validator $validator): void {
if (!$validator->valid()) {
$validator->after(function (FireflyValidator $validator): void {
if (0 === count($validator->valid())) {
return;
}
$code = $this->convertString('code', '');

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

@@ -29,6 +29,8 @@ use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface;
use FireflyIII\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
@@ -92,6 +94,18 @@ class RemovesLinksToDeletedObjects extends Command
if (count($deletedCategories) > 0) {
$this->cleanupCategories($deletedCategories);
}
// count and clean up available budgets in currencies with no budget limits.
// this is not entirely the place for it but OK.
/** @var AvailableBudgetRepositoryInterface $repository */
$repository = app(AvailableBudgetRepositoryInterface::class);
/** @var User $user */
foreach (User::get() as $user) {
$repository->setUser($user);
$repository->cleanup();
}
$this->friendlyNeutral('Validated links to deleted objects.');
}

View File

@@ -75,7 +75,10 @@ class RollbacksSingleMigration extends Command
}
if ($res) {
DB::table('migrations')->where('id', (int) $entry->id)->delete();
DB::table('migrations')
->where('id', (int) $entry->id)
->delete()
;
$this->friendlyInfo(sprintf('Database migration #%d ("%s") is deleted.', $entry->id, $entry->migration));
$this->friendlyLine('');
$this->friendlyLine('Try running "php artisan migrate" now.');

View File

@@ -0,0 +1,79 @@
<?php
/*
* SendTestEmail.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/>.
*/
declare(strict_types=1);
namespace FireflyIII\Console\Commands;
use FireflyIII\Events\Test\OwnerTestsNotificationChannel;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Console\Command;
class SendTestEmail extends Command
{
use ShowsFriendlyMessages;
use VerifiesAccessToken;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:send-test-email
{--user=1 : The user ID.}
{--token= : The user\'s access token.}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send test email';
/**
* Execute the console command.
*/
public function handle(): int
{
$user = $this->getUser();
if (!$user->hasRole('owner')) {
$this->friendlyError((string) trans('firefly.must_be_owner'));
return Command::FAILURE;
}
/** @var int $lastNotification */
$lastNotification = FireflyConfig::get('last_test_notification', 123)->data;
if ((time() - $lastNotification) < 120) {
$this->friendlyError((string) trans('firefly.test_rate_limited'));
return Command::FAILURE;
}
$owner = new OwnerNotifiable();
event(new OwnerTestsNotificationChannel('email', $owner));
FireflyConfig::set('last_test_notification', time());
return Command::SUCCESS;
}
}

View File

@@ -107,7 +107,10 @@ class RepairsPostgresSequences extends Command
$this->friendlyLine(sprintf('Checking the next id sequence for table "%s".', $tableToCheck));
$highestId = DB::table($tableToCheck)->select(DB::raw('MAX(id)'))->first();
$nextId = DB::table($tableToCheck)->select(DB::raw(sprintf('nextval(\'%s_id_seq\')', $tableToCheck)))->first();
$nextId = DB::table($tableToCheck)
->select(DB::raw(sprintf('nextval(\'%s_id_seq\')', $tableToCheck)))
->first()
;
if (null === $nextId) {
$this->friendlyInfo(sprintf('nextval is NULL for table "%s", go to next table.', $tableToCheck));
@@ -117,7 +120,10 @@ class RepairsPostgresSequences extends Command
if ($nextId->nextval < $highestId->max) {
DB::select(sprintf('SELECT setval(\'%s_id_seq\', %d)', $tableToCheck, $highestId->max));
$highestId = DB::table($tableToCheck)->select(DB::raw('MAX(id)'))->first();
$nextId = DB::table($tableToCheck)->select(DB::raw(sprintf('nextval(\'%s_id_seq\')', $tableToCheck)))->first();
$nextId = DB::table($tableToCheck)
->select(DB::raw(sprintf('nextval(\'%s_id_seq\')', $tableToCheck)))
->first()
;
if ($nextId->nextval > $highestId->max) {
$this->friendlyInfo(sprintf('Table "%s" autoincrement corrected.', $tableToCheck));
}

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

@@ -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

@@ -250,6 +250,7 @@ class Handler extends ExceptionHandler
'json' => request()->acceptsJson(),
'method' => request()->method(),
'headers' => $headers,
// @mago-expect lint:no-request-all
'post' => 'POST' === request()->method() ? json_encode(request()->all()) : '',
];

View File

@@ -219,7 +219,7 @@ class AccountFactory
'user_group_id' => $this->user->user_group_id,
'account_type_id' => $type->id,
'name' => $data['name'],
'order' => 25000,
'order' => 25_000,
'virtual_balance' => $virtualBalance,
'active' => $active,
'iban' => $data['iban'],

View File

@@ -159,7 +159,7 @@ class PiggyBankFactory
}
// no amount set, use previous amount
$toBeLinked[$account->id] = ['current_amount' => $toBeLinked[$account->id]['current_amount']];
$toBeLinked[$account->id] = ['current_amount' => $toBeLinked[$account->id]['current_amount'] ?? '0'];
Log::debug(sprintf('[b] Will link account #%d with amount %s', $account->id, $toBeLinked[$account->id]['current_amount']));
// create event:
@@ -246,7 +246,7 @@ class PiggyBankFactory
$piggyBankData['target_date_tz'] = $piggyBankData['target_date']?->format('e');
$piggyBankData['account_id'] = null;
$piggyBankData['transaction_currency_id'] = $this->getCurrency($data)->id;
$piggyBankData['order'] = 131337;
$piggyBankData['order'] = 131_337;
try {
/** @var PiggyBank $piggyBank */

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

@@ -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

@@ -124,8 +124,16 @@ final class NotificationController extends Controller
return redirect(route('settings.notification.index'));
}
$all = $request->all();
$channel = $all['test_submit'] ?? '';
/** @var int $lastNotification */
$lastNotification = FireflyConfig::get('last_test_notification', 123)->data;
if ((time() - $lastNotification) < 120) {
session()->flash('error', (string) trans('firefly.test_rate_limited'));
return redirect(route('settings.notification.index'));
}
$all = $request->only(['channel']);
$channel = $all['test_submit'] ?? '';
switch ($channel) {
default:
@@ -142,6 +150,7 @@ final class NotificationController extends Controller
event(new OwnerTestsNotificationChannel($channel, $owner));
session()->flash('success', (string) trans('firefly.notification_test_executed', ['channel' => $channel]));
}
FireflyConfig::set('last_test_notification', time());
return redirect(route('settings.notification.index'));
}

View File

@@ -33,7 +33,6 @@ use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\User;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

View File

@@ -31,7 +31,6 @@ use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Http\Controllers\CreateStuff;
use FireflyIII\User;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\RedirectResponse;
@@ -84,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,7 +172,7 @@ final class BudgetLimitController extends Controller
// return empty array:
return response()->json([]);
}
if ((int) $amount > 268435456) { // intentional cast to integer
if ((int) $amount > 268_435_456) { // intentional cast to integer
$amount = '268435456';
}
if (-1 === bccomp($amount, '0')) {
@@ -232,7 +232,7 @@ final class BudgetLimitController extends Controller
if ('' === $amount) {
$amount = '0';
}
if ((int) $amount > 268435456) { // 268 million, intentional integer
if ((int) $amount > 268_435_456) { // 268 million, intentional integer
$amount = '268435456';
}
// sanity check on amount:
@@ -254,8 +254,8 @@ final class BudgetLimitController extends Controller
$amount = bcmul($amount, '-1');
}
$notes = (string) $request->get('notes');
if (strlen($notes) > 32768) {
$notes = substr($notes, 0, 32768);
if (strlen($notes) > 32_768) {
$notes = substr($notes, 0, 32_768);
}
$limit = $this->blRepository->update($budgetLimit, ['amount' => $amount, 'notes' => $notes]);

View File

@@ -99,6 +99,7 @@ abstract class Controller extends BaseController
$logoutUrl = config('firefly.custom_logout_url');
// overrule v2 layout back to v1.
if ('true' === request()->get('force_default_layout') && 'v2' === config('view.layout')) {
// config('view.layout','v1');
Config::set('view.layout', 'v1');

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');
@@ -160,7 +161,7 @@ final class DebugController extends Controller
}
if ('' !== $logContent) {
// last few lines
$logContent = 'Truncated from this point <----|'.substr($logContent, -16384);
$logContent = 'Truncated from this point <----|'.substr($logContent, -16_384);
}
return view('debug', ['table' => $table, 'now' => $now, 'logContent' => $logContent]);
@@ -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

@@ -60,6 +60,7 @@ final class BudgetController extends Controller
$this->abRepository = app(AvailableBudgetRepositoryInterface::class);
$this->blRepository = app(BudgetLimitRepositoryInterface::class);
$this->repository->cleanupBudgets();
$this->abRepository->cleanup();
return $next($request);
});

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

@@ -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,7 +240,10 @@ 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)) {
@@ -252,10 +255,11 @@ final class PreferencesController extends Controller
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,7 +366,7 @@ final class PreferencesController extends Controller
public function testNotification(Request $request): mixed
{
$all = $request->all();
$all = $request->only(['channel']);
$channel = $all['channel'] ?? '';
switch ($channel) {

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

@@ -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

@@ -26,13 +26,11 @@ namespace FireflyIII\Http\Middleware;
use Closure;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Exceptions\Handler;
use FireflyIII\User;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use League\OAuth2\Server\Exception\OAuthServerException;
/**
* Class Authenticate
@@ -100,7 +98,6 @@ class Authenticate
throw new FireflyException('This point is generally unreachable.');
//
// exit('five');
// foreach ($guards as $guard) {
// exit('six');

View File

@@ -205,7 +205,12 @@ class InterestingMessage
// send message about newly created transaction group.
/** @var null|TransactionGroup $group */
$group = auth()->user()->transactionGroups()->with(['transactionJournals', 'transactionJournals.transactionType'])->find((int) $transactionGroupId);
$group = auth()
->user()
->transactionGroups()
->with(['transactionJournals', 'transactionJournals.transactionType'])
->find((int) $transactionGroupId)
;
if (null === $group) {
return;

View File

@@ -0,0 +1,66 @@
<?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;
/**
* Class IsAdmin.
*/
class IsAdminApi
{
/**
* Handle an incoming request. Must be admin.
*
* @param null|string $guard
*
* @return mixed
*/
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')) {
throw new AuthorizationException();
}
return $next($request);
}
}

View File

@@ -58,8 +58,10 @@ class SecureHeaders
"default-src 'none'",
"object-src 'none'",
sprintf("script-src 'unsafe-eval' 'strict-dynamic' 'nonce-%1s'", $nonce),
"style-src 'unsafe-inline' 'self'",
// sprintf("style-src 'self' 'nonce-%1s'", $nonce), // safe variant
"style-src 'self' 'unsafe-inline'", // unsafe variant
"base-uri 'self'",
// "form-action 'self'", // safe
"font-src 'self' data:",
sprintf("connect-src 'self' %s", $trackingScriptSrc),
sprintf("img-src 'self' data: 'nonce-%1s' ", $nonce),
@@ -68,14 +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' https://firefly.sd.internal/_debugbar/assets", $nonce),
"style-src 'unsafe-inline' 'self' https://10.0.0.15:5173/",
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
sprintf("style-src 'self' 'unsafe-inline' https://%s:5173/", $ip), // unsafe variant
"base-uri '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),
"form-action 'self'",
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'",
];
@@ -89,7 +94,7 @@ class SecureHeaders
$customUrl = $logoutUrl;
}
if (null !== $route && 'oauth/authorize' !== $route->uri) {
if ('' !== $customUrl && null !== $route && 'oauth/authorize' !== $route->uri) {
$csp[] = sprintf("form-action 'self' %s", $customUrl);
}

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

@@ -68,7 +68,7 @@ class ChecksForNewVersion implements ShouldQueue
$now = Carbon::now()->getTimestamp();
$diff = $now - $lastCheckTime->data;
Log::debug(sprintf('Last check time is %d, current time is %d, difference is %d', $lastCheckTime->data, $now, $diff));
if ($diff < 604800) {
if ($diff < 604_800) {
Log::debug(sprintf('Checked for updates less than a week ago (on %s).', Carbon::createFromTimestamp($lastCheckTime->data)->format('Y-m-d H:i:s')));
return;
@@ -121,7 +121,7 @@ class ChecksForNewVersion implements ShouldQueue
$now = Carbon::now()->getTimestamp();
$diff = $now - $lastCheckTime->data;
Log::debug(sprintf('Last warning time is %d, current time is %d, difference is %d', $lastCheckTime->data, $now, $diff));
if ($diff < (604800 * 4)) {
if ($diff < (604_800 * 4)) {
Log::debug(sprintf(
'Warned about updates less than four weeks ago (on %s).',
Carbon::createFromTimestamp($lastCheckTime->data)->format('Y-m-d H:i:s')

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

@@ -31,6 +31,9 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property User $user
*/
class GroupMembership extends Model
{
use ReturnsIntegerIdTrait;

View File

@@ -73,7 +73,11 @@ class Preference extends Model
// try again with ID, but this time don't care about the preferred user_group_id
if (null === $preference) {
$preference = $user->preferences()->where('id', (int) $value)->first();
$preference = $user
->preferences()
->where('id', (int) $value)
->first()
;
}
if (null !== $preference) {
/** @var Preference $preference */

View File

@@ -32,6 +32,9 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use function Safe\json_decode;
use function Safe\json_encode;
/**
* @property TransactionJournal $transactionJournal
*/
class TransactionJournalMeta extends Model
{
use ReturnsIntegerIdTrait;

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
use Override;
/**

View File

@@ -27,6 +27,8 @@ use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\UserGroup;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
/**
@@ -37,6 +39,7 @@ use Illuminate\Support\Collection;
* @method getUser()
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUserGroupById(int $userGroupId)
* @method setUser(null|Authenticatable|User $user)
*/
interface AttachmentRepositoryInterface
{

View File

@@ -638,9 +638,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 +650,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 +665,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 = [];

View File

@@ -69,6 +69,21 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
}
$exists[$key] = true;
}
// grab budget limit currencies.
$currencies = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->where('budgets.user_id', $this->user->id)
->distinct()
->get(['budget_limits.transaction_currency_id'])
->pluck('transaction_currency_id')
->toArray()
;
// delete available budgets without these currencies.
$this->user
->availableBudgets()
->whereNotIn('transaction_currency_id', $currencies)
->delete()
;
}
/**

View File

@@ -256,8 +256,14 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
foreach ($budgets as $budget) {
DB::table('budget_transaction')->where('budget_id', $budget->id)->delete();
DB::table('budget_transaction_journal')->where('budget_id', $budget->id)->delete();
RecurrenceTransactionMeta::where('name', 'budget_id')->where('value', (string) $budget->id)->delete();
RuleAction::where('action_type', 'set_budget')->where('action_value', (string) $budget->id)->delete();
RecurrenceTransactionMeta::where('name', 'budget_id')
->where('value', (string) $budget->id)
->delete()
;
RuleAction::where('action_type', 'set_budget')
->where('action_value', (string) $budget->id)
->delete()
;
$budget->delete();
}
Log::channel('audit')->info('Delete all budgets through destroyAll');
@@ -362,6 +368,15 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function getBudgets(): Collection
{
if (null === $this->user) {
return $this->userGroup
->budgets()
->orderBy('order', 'ASC')
->orderBy('name', 'ASC')
->get()
;
}
return $this->user
->budgets()
->orderBy('order', 'ASC')

View File

@@ -86,7 +86,10 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
}
// is being used in accounts:
$meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((string) $currency->id))->count();
$meta = AccountMeta::where('name', 'currency_id')
->where('data', json_encode((string) $currency->id))
->count()
;
if ($meta > 0) {
Log::info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
@@ -94,7 +97,10 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
}
// second search using integer check.
$meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((int) $currency->id))->count();
$meta = AccountMeta::where('name', 'currency_id')
->where('data', json_encode((int) $currency->id))
->count()
;
if ($meta > 0) {
Log::info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));

View File

@@ -116,8 +116,15 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface, UserGroupInterf
$links = $linkType->transactionJournalLinks()->get(['source_id', 'destination_id']);
$sources = $links->pluck('source_id')->toArray();
$destinations = $links->pluck('destination_id')->toArray();
$joined = array_unique(array_merge($sources, $destinations));
return array_unique(array_merge($sources, $destinations));
return $this->user
->transactionJournals()
->whereIn('id', $joined)
->get(['transaction_journals.id'])
->pluck('id')
->toArray()
;
}
/**

View File

@@ -25,7 +25,10 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\PeriodStatistic;
use Carbon\Carbon;
use FireflyIII\Models\Account;
use FireflyIII\Models\Category;
use FireflyIII\Models\PeriodStatistic;
use FireflyIII\Models\Tag;
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Builder;
@@ -38,7 +41,7 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface, U
{
use UserGroupTrait;
public function allInRangeForModel(Model $model, Carbon $start, Carbon $end): Collection
public function allInRangeForModel(Account|Category|Tag $model, Carbon $start, Carbon $end): Collection
{
return $model->primaryPeriodStatistics()->where('start', '>=', $start)->where('end', '<=', $end)->get();
}

View File

@@ -25,13 +25,16 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\PeriodStatistic;
use Carbon\Carbon;
use FireflyIII\Models\Account;
use FireflyIII\Models\Category;
use FireflyIII\Models\PeriodStatistic;
use FireflyIII\Models\Tag;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
interface PeriodStatisticRepositoryInterface
{
public function allInRangeForModel(Model $model, Carbon $start, Carbon $end): Collection;
public function allInRangeForModel(Account|Category|Tag $model, Carbon $start, Carbon $end): Collection;
public function allInRangeForPrefix(string $prefix, Carbon $start, Carbon $end): Collection;
@@ -41,6 +44,8 @@ interface PeriodStatisticRepositoryInterface
public function deleteStatisticsForPrefix(string $prefix, Collection $dates): void;
public function deleteStatisticsForType(string $class, Collection $objects, Collection $dates): void;
public function findPeriodStatistic(Model $model, Carbon $start, Carbon $end, string $type): Collection;
public function findPeriodStatistics(Model $model, Carbon $start, Carbon $end, array $types): Collection;

View File

@@ -327,7 +327,7 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
$rule->userGroup()->associate($this->user->userGroup);
$rule->rule_group_id = $ruleGroup->id;
$rule->order = 31337;
$rule->order = 31_337;
$rule->active = array_key_exists('active', $data) ? $data['active'] : true;
$rule->strict = array_key_exists('strict', $data) ? $data['strict'] : false;
$rule->stop_processing = array_key_exists('stop_processing', $data) ? $data['stop_processing'] : false;

View File

@@ -382,7 +382,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
'user_group_id' => $this->user->user_group_id,
'title' => $data['title'],
'description' => $data['description'],
'order' => 31337,
'order' => 31_337,
'active' => array_key_exists('active', $data) ? $data['active'] : true,
]);
$newRuleGroup->save();

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Services\Internal\Recalculate;
use FireflyIII\Events\Model\Account\UpdatedExistingAccount;
use FireflyIII\Handlers\Observer\TransactionObserver;
use FireflyIII\Models\Account;
use FireflyIII\Models\AutoBudget;
@@ -36,14 +37,16 @@ use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\Builder as DatabaseBuilder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
@@ -57,14 +60,52 @@ class PrimaryAmountRecalculationService
/** @var UserGroupRepositoryInterface $repository */
$repository = app(UserGroupRepositoryInterface::class);
Preferences::mark();
/** @var UserGroup $userGroup */
foreach ($repository->getAll() as $userGroup) {
Log::debug('Resetting primary currency amounts for all objects.');
$this->resetGenericTables($userGroup);
$this->resetPiggyBanks($userGroup);
$this->resetBudgets($userGroup);
$this->resetTransactions($userGroup);
Log::debug('Have now reset all primary amounts to NULL.');
$this->recalculateForGroup($userGroup);
}
}
public function recalculateForGroup(UserGroup $userGroup): void
{
Log::debug(sprintf('Now recalculating primary amounts for user group #%d', $userGroup->id));
// do a check with the group's currency so we can skip some stuff.
$currency = Amount::getPrimaryCurrencyByUserGroup($userGroup);
$this->recalculateAccounts($userGroup, $currency);
$this->recalculatePiggyBanks($userGroup, $currency);
$this->recalculateBudgets($userGroup, $currency);
$this->recalculateAvailableBudgets($userGroup, $currency);
$this->recalculateBills($userGroup, $currency);
$this->calculateTransactions($userGroup, $currency);
}
public function recalculateForGroupAndCurrency(UserGroup $userGroup, TransactionCurrency $limitCurrency): void
{
// do a check with the group's currency so we can skip some stuff.
$currency = Amount::getPrimaryCurrencyByUserGroup($userGroup);
if ($limitCurrency->id === $currency->id) {
Log::debug(sprintf('Can skip recalculation because user requested the same currencies (%s).', $limitCurrency->code));
return;
}
$this->recalculateAccountsForCurrency($userGroup, $currency, $limitCurrency);
$this->recalculatePiggyBanks($userGroup, $currency);
$this->recalculateBudgets($userGroup, $currency);
$this->recalculateAvailableBudgets($userGroup, $currency);
$this->recalculateBills($userGroup, $currency);
$this->calculateTransactionsForCurrency($userGroup, $currency, $limitCurrency);
}
private function calculateTransactions(UserGroup $userGroup, TransactionCurrency $currency): void
{
// custom query because of the potential size of this update.
@@ -86,7 +127,10 @@ class PrimaryAmountRecalculationService
->get(['transactions.id'])
;
TransactionObserver::$recalculate = false;
Log::debug(sprintf('Count of set is %d', $set->count()));
foreach ($set as $item) {
Log::debug(sprintf('Touch transaction #%d', $item->id));
// here we are.
/** @var null|Transaction $transaction */
$transaction = Transaction::find($item->id);
@@ -96,9 +140,42 @@ class PrimaryAmountRecalculationService
Log::debug(sprintf('Recalculated %d transactions.', $set->count()));
}
private function recalculateAccounts(UserGroup $userGroup): void
private function calculateTransactionsForCurrency(UserGroup $userGroup, TransactionCurrency $currency, TransactionCurrency $limitCurrency): void
{
$set = $userGroup
Log::debug(sprintf('Now in calculateTransactionsForCurrency(#%d, %s, %s)', $userGroup->id, $currency->code, $limitCurrency->code));
// custom query because of the potential size of this update.
$set = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(static function (DatabaseBuilder $q1) use ($currency): void {
$q1->where(static function (DatabaseBuilder $q2) use ($currency): void {
$q2->whereNot('transactions.transaction_currency_id', $currency->id)->whereNull('transactions.foreign_currency_id');
})->orWhere(static function (DatabaseBuilder $q3) use ($currency): void {
$q3->whereNot('transactions.transaction_currency_id', $currency->id)->whereNot('transactions.foreign_currency_id', $currency->id);
});
})
// must be in the limit currency.
->where('transactions.transaction_currency_id', $limitCurrency->id)
->orWhere('transactions.foreign_currency_id', $limitCurrency->id)
->get(['transactions.id'])
;
TransactionObserver::$recalculate = false;
Log::debug(sprintf('Count of set is %d', $set->count()));
foreach ($set as $item) {
Log::debug(sprintf('Touch transaction #%d', $item->id));
// here we are.
/** @var null|Transaction $transaction */
$transaction = Transaction::find($item->id);
$transaction?->touch();
}
TransactionObserver::$recalculate = true;
Log::debug(sprintf('Recalculated %d transactions.', $set->count()));
}
private function collectAccounts(UserGroup $userGroup): Collection
{
return $userGroup
->accounts()
->where(static function (EloquentBuilder $q): void {
$q->whereNotNull('virtual_balance');
@@ -113,14 +190,59 @@ class PrimaryAmountRecalculationService
})
->get()
;
}
/**
* Only recalculate accounts that have a virtual balance.
*/
private function recalculateAccounts(UserGroup $userGroup, TransactionCurrency $groupCurrency): void
{
Log::debug(sprintf('recalculateAccounts(#%d, %s)', $userGroup->id, $groupCurrency->code));
$set = $this->collectAccounts($userGroup);
/** @var Account $account */
foreach ($set as $account) {
$currencyId = (int) $account->accountMeta()->where('name', 'currency_id')->first()->data;
if ($groupCurrency->id === $currencyId) {
Log::debug(sprintf('Account "%s" is in group currency %s. Skip.', $account->name, $groupCurrency->code));
continue;
}
Log::debug(sprintf('Account "%s" is NOT in group currency %s, so do it.', $account->name, $groupCurrency->code));
$account->touch();
}
Log::debug(sprintf('Recalculated %d accounts for user group #%d.', $set->count(), $userGroup->id));
}
/**
* Only recalculate accounts that have a virtual balance.
*/
private function recalculateAccountsForCurrency(UserGroup $userGroup, TransactionCurrency $groupCurrency, TransactionCurrency $limitCurrency): void
{
Log::debug(sprintf('recalculateAccountsForCurrency(#%d, %s, %s)', $userGroup->id, $groupCurrency->code, $limitCurrency->code));
$set = $this->collectAccounts($userGroup);
/** @var Account $account */
foreach ($set as $account) {
$currencyId = (int) $account->accountMeta()->where('name', 'currency_id')->first()->data;
if ($groupCurrency->id === $currencyId) {
Log::debug(sprintf('Account "%s" is in group currency %s. Skip.', $account->name, $groupCurrency->code));
continue;
}
if ($limitCurrency->id !== $currencyId) {
Log::debug(sprintf('Account "%s" is NOT in limit currency %s, skip.', $account->name, $limitCurrency->code));
continue;
}
Log::debug(sprintf('Account "%s" is NOT in group currency %s, so do it.', $account->name, $groupCurrency->code));
// TODO it is bad form to call an event from an event but OK.
event(new UpdatedExistingAccount($account, []));
}
Log::debug(sprintf('Recalculated %d accounts for user group #%d.', $set->count(), $userGroup->id));
}
private function recalculateAutoBudgets(Budget $budget, TransactionCurrency $currency): void
{
$set = $budget->autoBudgets()->where('transaction_currency_id', '!=', $currency->id)->get();
@@ -180,21 +302,6 @@ class PrimaryAmountRecalculationService
Log::debug(sprintf('Recalculated %d budgets.', $set->count()));
}
private function recalculateForGroup(UserGroup $userGroup): void
{
Log::debug(sprintf('Now recalculating primary amounts for user group #%d', $userGroup->id));
$this->recalculateAccounts($userGroup);
// do a check with the group's currency so we can skip some stuff.
$currency = Amount::getPrimaryCurrencyByUserGroup($userGroup);
$this->recalculatePiggyBanks($userGroup, $currency);
$this->recalculateBudgets($userGroup, $currency);
$this->recalculateAvailableBudgets($userGroup, $currency);
$this->recalculateBills($userGroup, $currency);
$this->calculateTransactions($userGroup, $currency);
}
private function recalculatePiggyBankEvents(PiggyBank $piggyBank): void
{
$set = $piggyBank->piggyBankEvents()->get();
@@ -204,6 +311,9 @@ class PrimaryAmountRecalculationService
Log::debug(sprintf('Recalculated %d piggy bank events.', $set->count()));
}
/**
* This method collects ALL piggy banks, but only processes those that do not have the userGroup's primary currency.
*/
private function recalculatePiggyBanks(UserGroup $userGroup, TransactionCurrency $currency): void
{
$converter = new ExchangeRateConverter();
@@ -233,4 +343,104 @@ class PrimaryAmountRecalculationService
}
Log::debug(sprintf('Recalculated %d piggy banks for user group #%d.', $set->count(), $userGroup->id));
}
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 resetGenericTables(UserGroup $userGroup): void
{
$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', $userGroup->id)->update([$column => null]);
}
}
}
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

@@ -695,6 +695,7 @@ class JournalUpdateService
$source = $this->getSourceTransaction();
$dest = $this->getDestinationTransaction();
$foreignCurrency = $source->foreignCurrency;
$oldForeignCurrency = $foreignCurrency;
$originalSourceAmount = $source->foreign_amount;
// find currency in data array
@@ -773,11 +774,15 @@ class JournalUpdateService
$this->transactionJournal,
'update_foreign_amount',
[
'currency_symbol' => $recordCurrency->symbol,
'decimal_places' => $recordCurrency->decimal_places,
'currency_symbol' => $oldForeignCurrency->symbol,
'decimal_places' => $oldForeignCurrency->decimal_places,
'amount' => $originalSourceAmount,
],
['currency_symbol' => $recordCurrency->symbol, 'decimal_places' => $recordCurrency->decimal_places, 'amount' => $value]
[
'currency_symbol' => $recordCurrency->symbol,
'decimal_places' => $recordCurrency->decimal_places,
'amount' => $value,
]
)
);
}

View File

@@ -225,21 +225,27 @@ class Amount
*/
public function getAmountFromJournalObject(TransactionJournal $journal): string
{
// Log::debug(sprintf('Get amount from journal #%d', $journal->id));
$convertToPrimary = $this->convertToPrimary();
$currency = $this->getPrimaryCurrency();
$field = $convertToPrimary && $currency->id !== $journal->transaction_currency_id ? 'pc_amount' : 'amount';
$field = $convertToPrimary && $currency->id !== $journal->transaction_currency_id ? 'native_amount' : 'amount';
/** @var null|Transaction $sourceTransaction */
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
if (null === $sourceTransaction) {
// Log::debug('Return zero!');
return '0';
}
$amount = $sourceTransaction->{$field} ?? '0';
// Log::debug(sprintf('Amount is %s', $amount));
if ((int) $sourceTransaction->foreign_currency_id === $currency->id) {
// use foreign amount instead!
$amount = (string) $sourceTransaction->foreign_amount; // hard coded to be foreign amount.
// Log::debug(sprintf('Amount is now %s', $amount));
}
// Log::debug(sprintf('Final return is %s', $amount));
return $amount;
}

View File

@@ -41,7 +41,10 @@ class UserGroupAccount implements BinderInterface
if (auth()->check()) {
/** @var User $user */
$user = auth()->user();
$account = Account::where('id', (int) $value)->where('user_group_id', $user->user_group_id)->first();
$account = Account::where('id', (int) $value)
->where('user_group_id', $user->user_group_id)
->first()
;
if (null !== $account) {
return $account;
}

View File

@@ -41,7 +41,10 @@ class UserGroupBill implements BinderInterface
if (auth()->check()) {
/** @var User $user */
$user = auth()->user();
$currency = Bill::where('id', (int) $value)->where('user_group_id', $user->user_group_id)->first();
$currency = Bill::where('id', (int) $value)
->where('user_group_id', $user->user_group_id)
->first()
;
if (null !== $currency) {
return $currency;
}

View File

@@ -38,7 +38,10 @@ class UserGroupExchangeRate implements BinderInterface
if (auth()->check()) {
/** @var User $user */
$user = auth()->user();
$rate = CurrencyExchangeRate::where('id', (int) $value)->where('user_group_id', $user->user_group_id)->first();
$rate = CurrencyExchangeRate::where('id', (int) $value)
->where('user_group_id', $user->user_group_id)
->first()
;
if (null !== $rate) {
return $rate;
}

View File

@@ -38,7 +38,10 @@ class UserGroupTransaction implements BinderInterface
if (auth()->check()) {
/** @var User $user */
$user = auth()->user();
$group = TransactionGroup::where('id', (int) $value)->where('user_group_id', $user->user_group_id)->first();
$group = TransactionGroup::where('id', (int) $value)
->where('user_group_id', $user->user_group_id)
->first()
;
if (null !== $group) {
return $group;
}

View File

@@ -171,7 +171,7 @@ class FrontpageChartGenerator
$direction = $array['sum_float'] < 0 ? 'spent' : 'earned';
$key = sprintf('%s-%d', $direction, $array['currency_id']);
$category = $array['name'];
$amount = $array['sum_float'] < 0 ? $array['sum_float'] * -1 : $array['sum_float'];
$amount = $array['sum_float'] < 0 ? -$array['sum_float'] : $array['sum_float'];
$currencyData[$key]['entries'][$category] = $amount;
}

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