Compare commits

...

141 Commits

Author SHA1 Message Date
github-actions
f94fdc4979 Auto commit for release 'develop' on 2024-11-06 2024-11-06 19:53:30 +01:00
James Cole
a0a0e28447 Small rewording 2024-11-06 19:44:08 +01:00
James Cole
f6f7783b94 Add command to upgrade 2024-11-06 19:42:09 +01:00
James Cole
d233cc1de8 Update packages. 2024-11-06 19:41:15 +01:00
James Cole
37671499c8 Merge branch 'main' into develop
# Conflicts:
#	composer.lock
2024-11-06 19:39:11 +01:00
James Cole
c83b79998d Undo casts 2024-11-06 19:33:10 +01:00
James Cole
ed842c2b42 Fix and undo some enums 2024-11-06 19:32:32 +01:00
James Cole
8c5f114339 Add enums to types. 2024-11-06 19:22:28 +01:00
James Cole
8b2f1d0b4f Add timezones to existing objects. 2024-11-06 19:22:10 +01:00
James Cole
591a1b3050 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2024-11-06 19:16:56 +01:00
James Cole
42ec3fe02b Merge pull request #9437 from firefly-iii/dependabot/composer/composer-ee86c9e087
Bump the composer group across 1 directory with 2 updates
2024-11-06 19:16:42 +01:00
dependabot[bot]
370a398b5e Bump the composer group across 1 directory with 2 updates
Bumps the composer group with 2 updates in the / directory: [symfony/http-foundation](https://github.com/symfony/http-foundation) and [symfony/process](https://github.com/symfony/process).


Updates `symfony/http-foundation` from 7.1.5 to 7.1.7
- [Release notes](https://github.com/symfony/http-foundation/releases)
- [Changelog](https://github.com/symfony/http-foundation/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/http-foundation/compare/v7.1.5...v7.1.7)

Updates `symfony/process` from 7.1.5 to 7.1.7
- [Release notes](https://github.com/symfony/process/releases)
- [Changelog](https://github.com/symfony/process/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/process/compare/v7.1.5...v7.1.7)

---
updated-dependencies:
- dependency-name: symfony/http-foundation
  dependency-type: indirect
  dependency-group: composer
- dependency-name: symfony/process
  dependency-type: indirect
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-06 18:12:57 +00:00
James Cole
554d89b6e9 Merge pull request #9435 from firefly-iii/dependabot/composer/composer-cdaff7c96f 2024-11-06 19:12:21 +01:00
dependabot[bot]
cb049f5dda Bump symfony/http-client in the composer group across 1 directory
Bumps the composer group with 1 update in the / directory: [symfony/http-client](https://github.com/symfony/http-client).


Updates `symfony/http-client` from 7.1.5 to 7.1.7
- [Release notes](https://github.com/symfony/http-client/releases)
- [Changelog](https://github.com/symfony/http-client/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/http-client/compare/v7.1.5...v7.1.7)

---
updated-dependencies:
- dependency-name: symfony/http-client
  dependency-type: direct:production
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-06 15:27:07 +00:00
James Cole
0728668d41 Add timezone fields. 2024-11-06 14:10:34 +01:00
github-actions
dfc187874e Auto commit for release 'develop' on 2024-11-06 2024-11-06 14:02:38 +01:00
James Cole
225588f3e7 Update changelog and API 2024-11-06 13:50:47 +01:00
James Cole
06cc6c29aa Expand changelog. 2024-11-06 12:08:14 +01:00
James Cole
b2d4469908 Mild code cleanup. 2024-11-06 12:01:29 +01:00
James Cole
c398383905 Add column to fillable array 2024-11-06 11:59:37 +01:00
James Cole
7af9dce33b Add timezone to date objects 2024-11-06 11:57:12 +01:00
James Cole
038790a5d6 Add timezone info to new objects. 2024-11-06 11:12:26 +01:00
James Cole
fb3295bde1 Clean up code. 2024-11-06 11:12:12 +01:00
James Cole
43a4fd2ecb Fix https://github.com/firefly-iii/firefly-iii/issues/9427 2024-11-06 11:12:04 +01:00
James Cole
899c72d068 Add timezone. Cheap and backwards compatible. 2024-11-06 11:11:52 +01:00
James Cole
d118c0d886 Fix date requests 2024-11-06 11:11:38 +01:00
github-actions
6d4004d1ed Auto commit for release 'develop' on 2024-11-04 2024-11-04 04:14:51 +01:00
James Cole
ae60cd5b28 Fix https://github.com/firefly-iii/firefly-iii/issues/9294 2024-11-03 19:42:48 +01:00
github-actions
ab31a72199 Auto commit for release 'develop' on 2024-11-03 2024-11-03 09:01:53 +01:00
James Cole
2c1b9534f3 Sort piggy banks 2024-11-03 08:56:19 +01:00
James Cole
7028cb1546 Some code for https://github.com/firefly-iii/firefly-iii/issues/9294 2024-11-03 08:16:46 +01:00
James Cole
dc1ecf6a42 Fix https://github.com/firefly-iii/firefly-iii/issues/9389 2024-11-03 07:43:55 +01:00
James Cole
3a27f9d02c Expand piggy bank events 2024-11-02 05:17:46 +01:00
James Cole
4b27ab38f8 Fix https://github.com/firefly-iii/firefly-iii/issues/9416 2024-11-02 05:14:03 +01:00
github-actions
40de147611 Auto commit for release 'develop' on 2024-10-28 2024-10-28 04:15:04 +01:00
James Cole
df5756dc86 Remove labels after finding them 2024-10-27 20:47:50 +01:00
James Cole
bb4f90d730 Fix nullpointer 2024-10-27 10:49:55 +01:00
github-actions
d89d46aaec Auto commit for release 'develop' on 2024-10-23 2024-10-23 06:53:06 +02:00
James Cole
304d720c4c Merge branch 'main' into develop 2024-10-21 20:16:12 +02:00
James Cole
7eff160190 Fix https://github.com/firefly-iii/firefly-iii/issues/9303 2024-10-21 20:15:43 +02:00
James Cole
8b2e18ed9d Merge pull request #9383 from firefly-iii/dependabot/github_actions/github/command-1.2.2 2024-10-21 05:46:04 +02:00
github-actions
7001051833 Auto commit for release 'develop' on 2024-10-21 2024-10-21 05:15:16 +02:00
dependabot[bot]
b4b9752c05 Bump github/command from 1.2.1 to 1.2.2
Bumps [github/command](https://github.com/github/command) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/github/command/releases)
- [Commits](https://github.com/github/command/compare/v1.2.1...v1.2.2)

---
updated-dependencies:
- dependency-name: github/command
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 03:13:11 +00:00
James Cole
acadc89eaa Clean up API routes 2024-10-20 17:55:06 +02:00
James Cole
6ff84b8e90 Clean up autocomplete controller (accounts) 2024-10-20 10:16:54 +02:00
James Cole
7f3e3fc3bf Fix https://github.com/firefly-iii/firefly-iii/issues/9360 2024-10-18 07:35:19 +02:00
James Cole
02233fd7a4 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2024-10-14 19:55:54 +02:00
James Cole
50d3db0643 Minor update 2024-10-14 19:55:43 +02:00
James Cole
3751831779 Merge pull request #9349 from firefly-iii/dependabot/composer/develop/phpunit/phpunit-10.5.36
Bump phpunit/phpunit from 10.5.35 to 10.5.36
2024-10-14 09:33:49 +02:00
James Cole
14a24e47fb Merge pull request #9350 from firefly-iii/dependabot/composer/develop/laravel/framework-11.27.2 2024-10-14 09:18:24 +02:00
James Cole
b7e78cb0e6 Merge pull request #9351 from firefly-iii/dependabot/npm_and_yarn/develop/chartjs-chart-sankey-0.13.0 2024-10-14 09:18:06 +02:00
dependabot[bot]
a8f65f42fc Bump chartjs-chart-sankey from 0.12.1 to 0.13.0
Bumps [chartjs-chart-sankey](https://github.com/kurkle/chartjs-chart-sankey) from 0.12.1 to 0.13.0.
- [Release notes](https://github.com/kurkle/chartjs-chart-sankey/releases)
- [Commits](https://github.com/kurkle/chartjs-chart-sankey/compare/v0.12.1...v0.13.0)

---
updated-dependencies:
- dependency-name: chartjs-chart-sankey
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 03:22:09 +00:00
dependabot[bot]
d3385a116d Bump laravel/framework from 11.26.0 to 11.27.2
Bumps [laravel/framework](https://github.com/laravel/framework) from 11.26.0 to 11.27.2.
- [Release notes](https://github.com/laravel/framework/releases)
- [Changelog](https://github.com/laravel/framework/blob/11.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/framework/compare/v11.26.0...v11.27.2)

---
updated-dependencies:
- dependency-name: laravel/framework
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 03:14:55 +00:00
github-actions
e0c446dd13 Auto commit for release 'develop' on 2024-10-14 2024-10-14 05:14:52 +02:00
dependabot[bot]
33d11b4780 Bump phpunit/phpunit from 10.5.35 to 10.5.36
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 10.5.35 to 10.5.36.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/10.5.36/ChangeLog-10.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/10.5.35...10.5.36)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 03:14:44 +00:00
James Cole
07c49d1d04 Update stale action. 2024-10-10 07:20:34 +02:00
James Cole
9463285ac9 Fix https://github.com/orgs/firefly-iii/discussions/9324 2024-10-10 06:32:40 +02:00
James Cole
b41fc43e64 Fix https://github.com/orgs/firefly-iii/discussions/9324 2024-10-10 06:30:05 +02:00
James Cole
562763c938 Add code for login monitoring 2024-10-10 06:17:48 +02:00
James Cole
ec60194110 Add warning when the user fails to use MFA for a few times in a row. https://github.com/firefly-iii/firefly-iii/issues/9183 2024-10-08 14:45:37 +02:00
James Cole
1e472ee095 I know it's bad form to submit a large PR like this but this fixes almost everything in https://github.com/firefly-iii/firefly-iii/issues/9183 and I was too lazy to create a branch for it. 2024-10-08 07:21:23 +02:00
James Cole
5597327448 Merge pull request #9323 from firefly-iii/dependabot/npm_and_yarn/develop/i18next-23.15.2 2024-10-07 05:27:01 +02:00
James Cole
cdd5baf5be Merge pull request #9322 from firefly-iii/dependabot/npm_and_yarn/develop/i18next-http-backend-2.6.2 2024-10-07 05:26:51 +02:00
James Cole
7b5978059b Merge pull request #9321 from firefly-iii/dependabot/npm_and_yarn/develop/vue/compiler-sfc-3.5.11 2024-10-07 05:26:42 +02:00
github-actions
da0b41e45c Auto commit for release 'develop' on 2024-10-07 2024-10-07 05:13:20 +02:00
dependabot[bot]
d0be2afba5 Bump i18next from 23.15.1 to 23.15.2
Bumps [i18next](https://github.com/i18next/i18next) from 23.15.1 to 23.15.2.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v23.15.1...v23.15.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 03:05:15 +00:00
dependabot[bot]
d99851231a Bump i18next-http-backend from 2.6.1 to 2.6.2
Bumps [i18next-http-backend](https://github.com/i18next/i18next-http-backend) from 2.6.1 to 2.6.2.
- [Changelog](https://github.com/i18next/i18next-http-backend/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next-http-backend/compare/v2.6.1...v2.6.2)

---
updated-dependencies:
- dependency-name: i18next-http-backend
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 03:04:57 +00:00
dependabot[bot]
7e02c141f9 Bump @vue/compiler-sfc from 3.5.10 to 3.5.11
Bumps [@vue/compiler-sfc](https://github.com/vuejs/core/tree/HEAD/packages/compiler-sfc) from 3.5.10 to 3.5.11.
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/commits/v3.5.11/packages/compiler-sfc)

---
updated-dependencies:
- dependency-name: "@vue/compiler-sfc"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 03:04:35 +00:00
James Cole
d03960e379 Fix https://github.com/firefly-iii/firefly-iii/issues/8029 2024-10-06 11:32:58 +02:00
James Cole
16d3984ffc Upgrade packages 2024-10-05 17:17:20 +02:00
James Cole
856a194988 Fix https://github.com/firefly-iii/firefly-iii/issues/9106 2024-10-05 11:05:17 +02:00
James Cole
1bff966bfe Fix https://github.com/firefly-iii/firefly-iii/issues/9305 2024-10-05 10:57:57 +02:00
James Cole
1948b6118b Fix https://github.com/firefly-iii/firefly-iii/issues/9236 2024-10-05 10:49:18 +02:00
James Cole
20c25d3ca2 Fix #9225 2024-10-05 10:44:29 +02:00
James Cole
a153735ac3 Fix https://github.com/firefly-iii/firefly-iii/issues/9175 2024-10-05 09:43:53 +02:00
James Cole
62509f7c18 Fix https://github.com/firefly-iii/firefly-iii/issues/9147 2024-10-05 09:41:07 +02:00
James Cole
9b48b67158 Fix filters for https://github.com/orgs/firefly-iii/discussions/9271 2024-10-05 07:45:50 +02:00
github-actions
cbd50634a4 Auto commit for release 'develop' on 2024-09-30 2024-09-30 05:15:15 +02:00
James Cole
f475393bc1 Fix https://github.com/firefly-iii/firefly-iii/issues/9282 2024-09-29 16:04:54 +02:00
github-actions
abcddb09bf Auto commit for release 'v6.1.21' on 2024-09-29 2024-09-29 06:16:33 +02:00
James Cole
cf71a0fc55 Expand changelog 2024-09-29 06:11:57 +02:00
github-actions
78253f9e1e Auto commit for release 'develop' on 2024-09-29 2024-09-29 06:08:10 +02:00
James Cole
ebd0848c7f Expand changelog. 2024-09-29 06:03:58 +02:00
James Cole
c8461eb0b5 Fix https://github.com/firefly-iii/firefly-iii/issues/9281 2024-09-28 20:31:09 +02:00
James Cole
a4cbdeaeac Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop
# Conflicts:
#	app/Support/Models/AccountBalanceCalculator.php
2024-09-28 18:48:19 +02:00
James Cole
3e1ce69d52 Remove spammy debug message 2024-09-28 18:47:39 +02:00
github-actions
08a26b976e Auto commit for release 'develop' on 2024-09-28 2024-09-28 18:35:20 +02:00
James Cole
5fc55381a2 Update changelog. 2024-09-28 18:30:42 +02:00
James Cole
dbf3d24ae7 Fix https://github.com/firefly-iii/firefly-iii/issues/9278 2024-09-28 18:18:53 +02:00
James Cole
cc7c6e02c5 Fix https://github.com/firefly-iii/firefly-iii/issues/9275 2024-09-28 17:43:18 +02:00
James Cole
b45aa85853 Fix https://github.com/firefly-iii/firefly-iii/issues/9278 2024-09-28 17:17:29 +02:00
github-actions
e7526ac5e3 Auto commit for release 'develop' on 2024-09-28 2024-09-28 08:36:26 +02:00
James Cole
441ada70b8 Fix https://github.com/firefly-iii/firefly-iii/issues/9275 2024-09-28 08:26:54 +02:00
github-actions
dedc06a46b Auto commit for release 'v6.1.20' on 2024-09-28 2024-09-28 08:20:08 +02:00
James Cole
b0adf1b277 Update changelog 2024-09-28 08:15:22 +02:00
James Cole
28f65e9f44 Minor updates 2024-09-28 08:09:36 +02:00
James Cole
a013af5f0d Fix https://github.com/firefly-iii/firefly-iii/issues/9155 2024-09-25 17:39:28 +02:00
James Cole
9552701662 Fix https://github.com/firefly-iii/firefly-iii/issues/9168 2024-09-25 17:38:17 +02:00
James Cole
ef52f0aad1 Merge pull request #9265 from firefly-iii/dependabot/composer/develop/phpstan/phpstan-strict-rules-1.6.1
Bump phpstan/phpstan-strict-rules from 1.6.0 to 1.6.1
2024-09-23 07:04:41 +02:00
James Cole
0b6f04905a Merge pull request #9264 from firefly-iii/dependabot/composer/develop/symfony/http-client-7.1.5
Bump symfony/http-client from 7.1.4 to 7.1.5
2024-09-23 07:04:21 +02:00
James Cole
cdb36357d4 Merge pull request #9261 from firefly-iii/dependabot/composer/develop/phpunit/phpunit-10.5.35
Bump phpunit/phpunit from 10.5.34 to 10.5.35
2024-09-23 06:57:19 +02:00
dependabot[bot]
8938622bd9 Bump phpstan/phpstan-strict-rules from 1.6.0 to 1.6.1
Bumps [phpstan/phpstan-strict-rules](https://github.com/phpstan/phpstan-strict-rules) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/phpstan/phpstan-strict-rules/releases)
- [Commits](https://github.com/phpstan/phpstan-strict-rules/compare/1.6.0...1.6.1)

---
updated-dependencies:
- dependency-name: phpstan/phpstan-strict-rules
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 03:11:13 +00:00
dependabot[bot]
b210294aa9 Bump symfony/http-client from 7.1.4 to 7.1.5
Bumps [symfony/http-client](https://github.com/symfony/http-client) from 7.1.4 to 7.1.5.
- [Release notes](https://github.com/symfony/http-client/releases)
- [Changelog](https://github.com/symfony/http-client/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/http-client/compare/v7.1.4...v7.1.5)

---
updated-dependencies:
- dependency-name: symfony/http-client
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 03:11:07 +00:00
github-actions
5b02f20775 Auto commit for release 'develop' on 2024-09-23 2024-09-23 05:11:02 +02:00
dependabot[bot]
fac382a5df Bump phpunit/phpunit from 10.5.34 to 10.5.35
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 10.5.34 to 10.5.35.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/10.5.35/ChangeLog-10.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/10.5.34...10.5.35)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 03:10:37 +00:00
James Cole
88d88bebc9 Fix package thing 2024-09-22 09:33:39 +02:00
James Cole
755fb9c29b Merge branch 'main' into develop
# Conflicts:
#	package-lock.json
2024-09-22 09:32:06 +02:00
James Cole
51a835ab51 Merge pull request #9247 from firefly-iii/dependabot/npm_and_yarn/npm_and_yarn-5751ec22a7
Bump vite from 5.3.4 to 5.3.6 in the npm_and_yarn group across 1 directory
2024-09-22 09:25:53 +02:00
James Cole
c9895ab182 Merge pull request #9245 from firefly-iii/dependabot/npm_and_yarn/develop/date-fns-4.0.0
Bump date-fns from 3.6.0 to 4.0.0
2024-09-22 09:25:16 +02:00
dependabot[bot]
e71d46a4e5 Bump vite in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 5.3.4 to 5.3.6
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.3.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.3.6/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-17 19:46:52 +00:00
dependabot[bot]
8d1d5f37c1 Bump date-fns from 3.6.0 to 4.0.0
Bumps [date-fns](https://github.com/date-fns/date-fns) from 3.6.0 to 4.0.0.
- [Release notes](https://github.com/date-fns/date-fns/releases)
- [Changelog](https://github.com/date-fns/date-fns/blob/main/CHANGELOG.md)
- [Commits](https://github.com/date-fns/date-fns/commits)

---
updated-dependencies:
- dependency-name: date-fns
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 03:37:16 +00:00
github-actions
525a68682d Auto commit for release 'develop' on 2024-09-16 2024-09-16 05:12:48 +02:00
James Cole
715648d0d8 Remove some debug logging. 2024-09-14 11:37:34 +02:00
James Cole
9452e93f22 Fix method visibility 2024-09-14 11:36:57 +02:00
James Cole
a6aa145471 Merge pull request #9239 from jfpedroza/patch-1
Fix webhook index page when Firefly is not served at root
2024-09-14 11:31:24 +02:00
Jhon Pedroza
25aa6dcb59 Fix webhook index page when Firefly is not served at root
Signed-off-by: Jhon Pedroza <jhon@pedroza.me>
2024-09-14 04:04:39 -05:00
github-actions
bb2270b274 Auto commit for release 'develop' on 2024-09-14 2024-09-14 06:17:49 +02:00
James Cole
d7f6b4143e Update patches 2024-09-14 06:13:12 +02:00
James Cole
0cf0e26fa8 Merge branch 'main' into develop
# Conflicts:
#	composer.lock
2024-09-14 05:56:54 +02:00
James Cole
cc23197d60 Rename patch file 2024-09-14 05:54:03 +02:00
James Cole
bc1721d95e Merge pull request #9216 from firefly-iii/dependabot/composer/develop/phpstan/extension-installer-1.4.3 2024-09-10 07:08:11 +02:00
James Cole
0d19173da6 Merge pull request #9223 from firefly-iii/dependabot/composer/composer-72e0df66de 2024-09-10 07:07:49 +02:00
dependabot[bot]
1983f07d3c Bump phpstan/extension-installer from 1.4.2 to 1.4.3
Bumps [phpstan/extension-installer](https://github.com/phpstan/extension-installer) from 1.4.2 to 1.4.3.
- [Release notes](https://github.com/phpstan/extension-installer/releases)
- [Commits](https://github.com/phpstan/extension-installer/compare/1.4.2...1.4.3)

---
updated-dependencies:
- dependency-name: phpstan/extension-installer
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-10 05:07:33 +00:00
James Cole
aad1b91cc2 Merge pull request #9220 from firefly-iii/dependabot/npm_and_yarn/develop/vue/compiler-sfc-3.5.3 2024-09-10 07:07:22 +02:00
James Cole
8cb1057a33 Merge pull request #9219 from firefly-iii/dependabot/npm_and_yarn/develop/postcss-8.4.45 2024-09-10 07:07:13 +02:00
James Cole
b178032985 Merge pull request #9218 from firefly-iii/dependabot/npm_and_yarn/develop/sass-1.78.0 2024-09-10 07:07:05 +02:00
James Cole
561213e95d Merge pull request #9217 from firefly-iii/dependabot/composer/develop/pragmarx/google2fa-8.0.3 2024-09-10 07:06:57 +02:00
James Cole
44fa7c4306 Merge pull request #9215 from firefly-iii/dependabot/composer/develop/laravel/framework-11.22.0 2024-09-10 07:06:43 +02:00
James Cole
e2169563e2 Merge pull request #9214 from firefly-iii/dependabot/composer/develop/phpunit/phpunit-10.5.32 2024-09-10 07:06:35 +02:00
James Cole
845344e003 Merge pull request #9213 from firefly-iii/dependabot/composer/develop/laravel/slack-notification-channel-3.3.2 2024-09-10 07:06:26 +02:00
dependabot[bot]
cdb48453e8 Bump twig/twig in the composer group across 1 directory
Bumps the composer group with 1 update in the / directory: [twig/twig](https://github.com/twigphp/Twig).


Updates `twig/twig` from 3.10.3 to 3.14.0
- [Changelog](https://github.com/twigphp/Twig/blob/3.x/CHANGELOG)
- [Commits](https://github.com/twigphp/Twig/compare/v3.10.3...v3.14.0)

---
updated-dependencies:
- dependency-name: twig/twig
  dependency-type: indirect
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 20:45:08 +00:00
dependabot[bot]
9669cef518 Bump @vue/compiler-sfc from 3.4.38 to 3.5.3
Bumps [@vue/compiler-sfc](https://github.com/vuejs/core/tree/HEAD/packages/compiler-sfc) from 3.4.38 to 3.5.3.
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/commits/v3.5.3/packages/compiler-sfc)

---
updated-dependencies:
- dependency-name: "@vue/compiler-sfc"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 03:45:12 +00:00
dependabot[bot]
f962f71ed7 Bump postcss from 8.4.43 to 8.4.45
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.43 to 8.4.45.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.43...8.4.45)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 03:44:56 +00:00
dependabot[bot]
94ed4021fb Bump sass from 1.77.8 to 1.78.0
Bumps [sass](https://github.com/sass/dart-sass) from 1.77.8 to 1.78.0.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.77.8...1.78.0)

---
updated-dependencies:
- dependency-name: sass
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 03:44:40 +00:00
dependabot[bot]
1765855c57 Bump pragmarx/google2fa from 8.0.1 to 8.0.3
Bumps [pragmarx/google2fa](https://github.com/antonioribeiro/google2fa) from 8.0.1 to 8.0.3.
- [Release notes](https://github.com/antonioribeiro/google2fa/releases)
- [Changelog](https://github.com/antonioribeiro/google2fa/blob/8.x/CHANGELOG.md)
- [Commits](https://github.com/antonioribeiro/google2fa/compare/v8.0.1...v8.0.3)

---
updated-dependencies:
- dependency-name: pragmarx/google2fa
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 03:41:41 +00:00
dependabot[bot]
55cf3e7d44 Bump laravel/framework from 11.21.0 to 11.22.0
Bumps [laravel/framework](https://github.com/laravel/framework) from 11.21.0 to 11.22.0.
- [Release notes](https://github.com/laravel/framework/releases)
- [Changelog](https://github.com/laravel/framework/blob/11.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/framework/compare/v11.21.0...v11.22.0)

---
updated-dependencies:
- dependency-name: laravel/framework
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 03:41:32 +00:00
dependabot[bot]
9f1840dc05 Bump phpunit/phpunit from 10.5.30 to 10.5.32
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 10.5.30 to 10.5.32.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/10.5.32/ChangeLog-10.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/10.5.30...10.5.32)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 03:41:21 +00:00
dependabot[bot]
78dab2e5f9 Bump laravel/slack-notification-channel from 3.3.1 to 3.3.2
Bumps [laravel/slack-notification-channel](https://github.com/laravel/slack-notification-channel) from 3.3.1 to 3.3.2.
- [Release notes](https://github.com/laravel/slack-notification-channel/releases)
- [Changelog](https://github.com/laravel/slack-notification-channel/blob/3.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/slack-notification-channel/compare/v3.3.1...v3.3.2)

---
updated-dependencies:
- dependency-name: laravel/slack-notification-channel
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 03:41:15 +00:00
James Cole
103b9d5005 Update readme.md
Signed-off-by: James Cole <james@firefly-iii.org>
2024-09-04 18:48:17 +02:00
James Cole
1b75b778d8 Merge pull request #9171 from mzhubail/add-about-tests_
Add about test
2024-09-04 10:35:08 +02:00
mzhubail
edf2030251 Add about test 2024-08-25 12:19:23 +03:00
181 changed files with 8921 additions and 6292 deletions

View File

@@ -151,24 +151,24 @@
},
{
"name": "composer/semver",
"version": "3.4.2",
"version": "3.4.3",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6"
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6",
"reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6",
"url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.4",
"symfony/phpunit-bridge": "^4.2 || ^5"
"phpstan/phpstan": "^1.11",
"symfony/phpunit-bridge": "^3 || ^7"
},
"type": "library",
"extra": {
@@ -212,7 +212,7 @@
"support": {
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/semver/issues",
"source": "https://github.com/composer/semver/tree/3.4.2"
"source": "https://github.com/composer/semver/tree/3.4.3"
},
"funding": [
{
@@ -228,7 +228,7 @@
"type": "tidelift"
}
],
"time": "2024-07-12T11:35:52+00:00"
"time": "2024-09-19T14:15:21+00:00"
},
{
"name": "composer/xdebug-handler",
@@ -612,16 +612,16 @@
},
{
"name": "psr/log",
"version": "3.0.1",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "79dff0b268932c640297f5208d6298f71855c03e"
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e",
"reference": "79dff0b268932c640297f5208d6298f71855c03e",
"url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
"shasum": ""
},
"require": {
@@ -656,9 +656,9 @@
"psr-3"
],
"support": {
"source": "https://github.com/php-fig/log/tree/3.0.1"
"source": "https://github.com/php-fig/log/tree/3.0.2"
},
"time": "2024-08-21T13:31:24+00:00"
"time": "2024-09-11T13:17:53+00:00"
},
{
"name": "react/cache",
@@ -1259,16 +1259,16 @@
},
{
"name": "symfony/console",
"version": "v7.1.4",
"version": "v7.1.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111"
"reference": "3284aafcac338b6e86fd955ee4d794cbe434151a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/1eed7af6961d763e7832e874d7f9b21c3ea9c111",
"reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111",
"url": "https://api.github.com/repos/symfony/console/zipball/3284aafcac338b6e86fd955ee4d794cbe434151a",
"reference": "3284aafcac338b6e86fd955ee4d794cbe434151a",
"shasum": ""
},
"require": {
@@ -1332,7 +1332,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.1.4"
"source": "https://github.com/symfony/console/tree/v7.1.7"
},
"funding": [
{
@@ -1348,7 +1348,7 @@
"type": "tidelift"
}
],
"time": "2024-08-15T22:48:53+00:00"
"time": "2024-11-05T15:34:55+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -1419,16 +1419,16 @@
},
{
"name": "symfony/event-dispatcher",
"version": "v7.1.1",
"version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7"
"reference": "87254c78dd50721cfd015b62277a8281c5589702"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7",
"reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87254c78dd50721cfd015b62277a8281c5589702",
"reference": "87254c78dd50721cfd015b62277a8281c5589702",
"shasum": ""
},
"require": {
@@ -1479,7 +1479,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1"
"source": "https://github.com/symfony/event-dispatcher/tree/v7.1.6"
},
"funding": [
{
@@ -1495,7 +1495,7 @@
"type": "tidelift"
}
],
"time": "2024-05-31T14:57:53+00:00"
"time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
@@ -1575,16 +1575,16 @@
},
{
"name": "symfony/filesystem",
"version": "v7.1.2",
"version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "92a91985250c251de9b947a14bb2c9390b1a562c"
"reference": "c835867b3c62bb05c7fe3d637c871c7ae52024d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c",
"reference": "92a91985250c251de9b947a14bb2c9390b1a562c",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/c835867b3c62bb05c7fe3d637c871c7ae52024d4",
"reference": "c835867b3c62bb05c7fe3d637c871c7ae52024d4",
"shasum": ""
},
"require": {
@@ -1621,7 +1621,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v7.1.2"
"source": "https://github.com/symfony/filesystem/tree/v7.1.6"
},
"funding": [
{
@@ -1637,20 +1637,20 @@
"type": "tidelift"
}
],
"time": "2024-06-28T10:03:55+00:00"
"time": "2024-10-25T15:11:02+00:00"
},
{
"name": "symfony/finder",
"version": "v7.1.4",
"version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "d95bbf319f7d052082fb7af147e0f835a695e823"
"reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823",
"reference": "d95bbf319f7d052082fb7af147e0f835a695e823",
"url": "https://api.github.com/repos/symfony/finder/zipball/2cb89664897be33f78c65d3d2845954c8d7a43b8",
"reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8",
"shasum": ""
},
"require": {
@@ -1685,7 +1685,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v7.1.4"
"source": "https://github.com/symfony/finder/tree/v7.1.6"
},
"funding": [
{
@@ -1701,20 +1701,20 @@
"type": "tidelift"
}
],
"time": "2024-08-13T14:28:19+00:00"
"time": "2024-10-01T08:31:23+00:00"
},
{
"name": "symfony/options-resolver",
"version": "v7.1.1",
"version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
"reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55"
"reference": "85e95eeede2d41cd146146e98c9c81d9214cae85"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55",
"reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/85e95eeede2d41cd146146e98c9c81d9214cae85",
"reference": "85e95eeede2d41cd146146e98c9c81d9214cae85",
"shasum": ""
},
"require": {
@@ -1752,7 +1752,7 @@
"options"
],
"support": {
"source": "https://github.com/symfony/options-resolver/tree/v7.1.1"
"source": "https://github.com/symfony/options-resolver/tree/v7.1.6"
},
"funding": [
{
@@ -1768,24 +1768,24 @@
"type": "tidelift"
}
],
"time": "2024-05-31T14:57:53+00:00"
"time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "0424dff1c58f028c451efff2045f5d92410bd540"
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540",
"reference": "0424dff1c58f028c451efff2045f5d92410bd540",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
@@ -1831,7 +1831,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
},
"funding": [
{
@@ -1847,24 +1847,24 @@
"type": "tidelift"
}
],
"time": "2024-05-31T15:07:36+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a"
"reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a",
"reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
"reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"suggest": {
"ext-intl": "For best performance"
@@ -1909,7 +1909,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0"
},
"funding": [
{
@@ -1925,24 +1925,24 @@
"type": "tidelift"
}
],
"time": "2024-05-31T15:07:36+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb"
"reference": "3833d7255cc303546435cb650316bff708a1c75c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb",
"reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
"reference": "3833d7255cc303546435cb650316bff708a1c75c",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"suggest": {
"ext-intl": "For best performance"
@@ -1990,7 +1990,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0"
},
"funding": [
{
@@ -2006,24 +2006,24 @@
"type": "tidelift"
}
],
"time": "2024-05-31T15:07:36+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c"
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
@@ -2070,7 +2070,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
},
"funding": [
{
@@ -2086,24 +2086,24 @@
"type": "tidelift"
}
],
"time": "2024-06-19T12:30:46+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "77fa7995ac1b21ab60769b7323d600a991a90433"
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433",
"reference": "77fa7995ac1b21ab60769b7323d600a991a90433",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"type": "library",
"extra": {
@@ -2150,7 +2150,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0"
},
"funding": [
{
@@ -2166,24 +2166,24 @@
"type": "tidelift"
}
],
"time": "2024-05-31T15:07:36+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-php81",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
"reference": "3fb075789fb91f9ad9af537c4012d523085bd5af"
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af",
"reference": "3fb075789fb91f9ad9af537c4012d523085bd5af",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"type": "library",
"extra": {
@@ -2226,7 +2226,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0"
},
"funding": [
{
@@ -2242,20 +2242,20 @@
"type": "tidelift"
}
],
"time": "2024-06-19T12:30:46+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/process",
"version": "v7.1.3",
"version": "v7.1.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca"
"reference": "9b8a40b7289767aa7117e957573c2a535efe6585"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca",
"reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca",
"url": "https://api.github.com/repos/symfony/process/zipball/9b8a40b7289767aa7117e957573c2a535efe6585",
"reference": "9b8a40b7289767aa7117e957573c2a535efe6585",
"shasum": ""
},
"require": {
@@ -2287,7 +2287,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.1.3"
"source": "https://github.com/symfony/process/tree/v7.1.7"
},
"funding": [
{
@@ -2303,7 +2303,7 @@
"type": "tidelift"
}
],
"time": "2024-07-26T12:44:47+00:00"
"time": "2024-11-06T09:25:12+00:00"
},
{
"name": "symfony/service-contracts",
@@ -2390,16 +2390,16 @@
},
{
"name": "symfony/stopwatch",
"version": "v7.1.1",
"version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
"reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d"
"reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d",
"reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/8b4a434e6e7faf6adedffb48783a5c75409a1a05",
"reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05",
"shasum": ""
},
"require": {
@@ -2432,7 +2432,7 @@
"description": "Provides a way to profile code",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/stopwatch/tree/v7.1.1"
"source": "https://github.com/symfony/stopwatch/tree/v7.1.6"
},
"funding": [
{
@@ -2448,20 +2448,20 @@
"type": "tidelift"
}
],
"time": "2024-05-31T14:57:53+00:00"
"time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/string",
"version": "v7.1.4",
"version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b"
"reference": "61b72d66bf96c360a727ae6232df5ac83c71f626"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b",
"reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b",
"url": "https://api.github.com/repos/symfony/string/zipball/61b72d66bf96c360a727ae6232df5ac83c71f626",
"reference": "61b72d66bf96c360a727ae6232df5ac83c71f626",
"shasum": ""
},
"require": {
@@ -2519,7 +2519,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v7.1.4"
"source": "https://github.com/symfony/string/tree/v7.1.6"
},
"funding": [
{
@@ -2535,16 +2535,16 @@
"type": "tidelift"
}
],
"time": "2024-08-12T09:59:40+00:00"
"time": "2024-09-25T14:20:29+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"platform": {},
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

View File

@@ -176,6 +176,7 @@ MAILGUN_ENDPOINT=api.mailgun.net
# If you use Docker or similar, you can set these variables from a file by appending them with _FILE
MANDRILL_SECRET=
SPARKPOST_SECRET=
MAILERSEND_API_KEY=
# Firefly III can send you the following messages.
SEND_ERROR_MESSAGE=true
@@ -312,6 +313,12 @@ PUSHER_ID=
DEMO_USERNAME=
DEMO_PASSWORD=
#
# Disable or enable the running balance column data
# Please disable this. It's a very experimental feature.
#
USE_RUNNING_BALANCE=false
#
# The v2 layout is very experimental. If it breaks you get to keep both parts.
# Be wary of data loss.

View File

@@ -4,6 +4,7 @@
feature:
issues:
# Post a comment, `{issue-author}` is an optional placeholder
unlabel: feature
comment: |
Hi there!
@@ -32,6 +33,7 @@ epic:
Thank you for your contributions.
enhancement:
unlabel: enhancement
issues:
# Post a comment, `{issue-author}` is an optional placeholder
comment: |

View File

@@ -13,7 +13,7 @@ jobs:
close_duplicates:
runs-on: ubuntu-latest
steps:
- uses: github/command@v1.2.1
- uses: github/command@v1.2.2
id: command
with:
allowed_contexts: "issue"

View File

@@ -35,4 +35,5 @@ jobs:
Thank you for your contributions.
days-before-stale: 14
days-before-close: 7
exempt-issue-labels: 'enhancement,feature,bug,announcement,epic,triage'
exempt-all-milestones: true
exempt-issue-labels: 'triage'

View File

@@ -4,6 +4,8 @@ Over time, many people have contributed to Firefly III. Their efforts are not al
Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution.
## 2024
- Jhon Pedroza
- mzhubail
- tasnim
- withbest
- Steve Wasiura

View File

@@ -52,7 +52,7 @@ class AboutController extends Controller
$data
= [
'version' => config('firefly.version'),
'api_version' => config('firefly.api_version'),
'api_version' => config('firefly.version'),
'php_version' => $phpVersion,
'os' => $phpOs,
'driver' => $currentDriver,

View File

@@ -46,6 +46,8 @@ class DateRequest extends FormRequest
{
$start = $this->getCarbonDate('start');
$end = $this->getCarbonDate('end');
$start->startOfDay();
$end->endOfDay();
if ($start->diffInYears($end, true) > 5) {
throw new FireflyException('Date range out of range.');
}

View File

@@ -79,12 +79,12 @@ class StoreRequest extends FormRequest
'currency_id' => 'numeric|exists:transaction_currencies,id',
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
'date' => 'date|required',
'end_date' => 'date|after:date',
'extension_date' => 'date|after:date',
'end_date' => 'nullable|date|after:date',
'extension_date' => 'nullable|date|after:date',
'repeat_freq' => 'in:weekly,monthly,quarterly,half-year,yearly|required',
'skip' => 'min:0|max:31|numeric',
'active' => [new IsBoolean()],
'notes' => 'min:1|max:32768',
'notes' => 'nullable|min:1|max:32768',
];
}

View File

@@ -138,7 +138,7 @@ class StoreRequest extends FormRequest
// all custom fields:
'internal_reference' => $this->clearString((string)$object['internal_reference']),
'external_id' => $this->clearString((string)$object['external_id']),
'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')),
'original_source' => sprintf('ff3-v%s', config('firefly.version')),
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
'bunq_payment_id' => $this->clearString((string)$object['bunq_payment_id']),
'external_url' => $this->clearString((string)$object['external_url']),

View File

@@ -68,9 +68,9 @@ class AccountController extends Controller
*/
public function accounts(AutocompleteRequest $request): JsonResponse
{
$queryParameters = $request->getParameters();
$result = $this->repository->searchAccount($queryParameters['query'], $queryParameters['account_types'], $queryParameters['size']);
$return = [];
$params = $request->getParameters();
$result = $this->repository->searchAccount($params['query'], $params['account_types'], $params['page'], $params['size']);
$return = [];
/** @var Account $account */
foreach ($result as $account) {
@@ -89,6 +89,7 @@ class AccountController extends Controller
'title' => $account->name,
'meta' => [
'type' => $account->accountType->type,
// TODO is multi currency property.
'currency_id' => null === $currency ? null : (string) $currency->id,
'currency_code' => $currency?->code,
'currency_symbol' => $currency?->symbol,

View File

@@ -84,10 +84,6 @@ class BalanceController extends Controller
$queryParameters = $request->getParameters();
$accounts = $this->getAccountList($queryParameters);
// move date to end of day
$queryParameters['start']->startOfDay();
$queryParameters['end']->endOfDay();
// prepare for currency conversion and data collection:
/** @var TransactionCurrency $default */
$default = app('amount')->getDefaultCurrency();

View File

@@ -23,15 +23,12 @@ declare(strict_types=1);
namespace FireflyIII\Api\V2\Request\Autocomplete;
use FireflyIII\JsonApi\Rules\IsValidFilter;
use FireflyIII\JsonApi\Rules\IsValidPage;
use FireflyIII\Models\AccountType;
use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\Support\Http\Api\ParsesQueryFilters;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
use LaravelJsonApi\Core\Query\QueryParameters;
use LaravelJsonApi\Validation\Rule as JsonApiRule;
/**
* Class AutocompleteRequest
@@ -51,35 +48,46 @@ class AutocompleteRequest extends FormRequest
*/
public function getParameters(): array
{
$queryParameters = QueryParameters::cast($this->all());
return [
'date' => $this->dateOrToday($queryParameters, 'date'),
'query' => $this->arrayOfStrings($queryParameters, 'query'),
'size' => $this->integerFromQueryParams($queryParameters, 'size', 50),
'account_types' => $this->getAccountTypeParameter($this->arrayOfStrings($queryParameters, 'account_types')),
$array = [
'date' => $this->convertDateTime('date'),
'query' => $this->clearString((string) $this->get('query')),
'size' => $this->integerFromValue('size'),
'page' => $this->integerFromValue('page'),
'account_types' => $this->arrayFromValue($this->get('account_types')),
'transaction_types' => $this->arrayFromValue($this->get('transaction_types')),
];
$array['size'] = $array['size'] < 1 || $array['size'] > 100 ? 15 : $array['size'];
$array['page'] = max($array['page'], 1);
if (null === $array['account_types']) {
$array['account_types'] = [];
}
if (null === $array['transaction_types']) {
$array['transaction_types'] = [];
}
// remove 'initial balance' from allowed types. its internal
$array['account_types'] = array_diff($array['account_types'], [AccountType::INITIAL_BALANCE, AccountType::RECONCILIATION, AccountType::CREDITCARD]);
$array['account_types'] = $this->getAccountTypeParameter($array['account_types']);
return $array;
}
public function rules(): array
{
$valid = array_keys($this->types);
return [
'fields' => JsonApiRule::notSupported(),
'filter' => ['nullable', 'array', new IsValidFilter(['query', 'date', 'account_types'])],
'include' => JsonApiRule::notSupported(),
'page' => ['nullable', 'array', new IsValidPage(['size'])],
'sort' => JsonApiRule::notSupported(),
'date' => 'nullable|date|after:1900-01-01|before:2100-01-01',
'query' => 'nullable|string',
'size' => 'nullable|integer|min:1|max:100',
'page' => 'nullable|integer|min:1',
'account_types' => sprintf('nullable|in:%s', implode(',', $valid)),
'transaction_types' => 'nullable|in:todo',
];
}
private function getAccountTypeParameter(mixed $types): array
private function getAccountTypeParameter(array $types): array
{
if (is_string($types) && str_contains($types, ',')) {
$types = explode(',', $types);
}
if (!is_iterable($types)) {
$types = [$types];
}
$return = [];
foreach ($types as $type) {
$return = array_merge($return, $this->mapAccountTypes($type));

View File

@@ -24,8 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V2\Request\Chart;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\JsonApi\Rules\IsValidFilter;
use FireflyIII\Rules\IsFilterValueIn;
use FireflyIII\Support\Http\Api\ParsesQueryFilters;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use FireflyIII\Support\Request\ChecksLogin;
@@ -33,8 +31,6 @@ use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Validator;
use LaravelJsonApi\Core\Query\QueryParameters;
use LaravelJsonApi\Validation\Rule as JsonApiRule;
/**
* Class ChartRequest
@@ -50,50 +46,29 @@ class ChartRequest extends FormRequest
public function getParameters(): array
{
$queryParameters = QueryParameters::cast($this->all());
// $queryParameters = QueryParameters::cast($this->all());
return [
'start' => $this->dateOrToday($queryParameters, 'start'),
'end' => $this->dateOrToday($queryParameters, 'end'),
'preselected' => $this->stringFromQueryParams($queryParameters, 'preselected', 'empty'),
'period' => $this->stringFromQueryParams($queryParameters, 'period', '1M'),
'accounts' => $this->arrayOfStrings($queryParameters, 'accounts'),
// preselected heeft maar een paar toegestane waardes, dat moet ook goed gaan.
// 'query' => $this->arrayOfStrings($queryParameters, 'query'),
// 'size' => $this->integerFromQueryParams($queryParameters,'size', 50),
// 'account_types' => $this->getAccountTypeParameter($this->arrayOfStrings($queryParameters, 'account_types')),
'start' => $this->convertDateTime('start')?->startOfDay(),
'end' => $this->convertDateTime('end')?->endOfDay(),
'preselected' => $this->convertString('preselected', 'empty'),
'period' => $this->convertString('period', '1M'),
'accounts' => $this->arrayFromValue($this->get('accounts')),
];
// collect accounts based on this list?
}
// return [
// 'accounts' => $this->getAccountList(),
// 'preselected' => $this->convertString('preselected'),
// ];
// }
/**
* The rules that the incoming request must be matched against.
*/
public function rules(): array
{
return [
'fields' => JsonApiRule::notSupported(),
'filter' => ['nullable', 'array',
new IsValidFilter(['start', 'end', 'preselected', 'accounts']),
new IsFilterValueIn('preselected', config('firefly.preselected_accounts')),
],
'include' => JsonApiRule::notSupported(),
'page' => JsonApiRule::notSupported(),
'sort' => JsonApiRule::notSupported(),
'start' => 'required|date|after:1900-01-01|before:2099-12-31|before_or_equal:end',
'end' => 'required|date|after:1900-01-01|before:2099-12-31|after_or_equal:start',
'preselected' => sprintf('nullable|in:%s', implode(',', config('firefly.preselected_accounts'))),
'period' => sprintf('nullable|in:%s', implode(',', config('firefly.valid_view_ranges'))),
'accounts.*' => 'exists:accounts,id',
];
// return [
// 'start' => 'required|date|after:1900-01-01|before:2099-12-31',
// 'end' => 'required|date|after_or_equal:start|before:2099-12-31|after:1900-01-01',
// 'preselected' => sprintf('in:%s', implode(',', config('firefly.preselected_accounts'))),
// 'accounts.*' => 'exists:accounts,id',
// ];
}
public function withValidator(Validator $validator): void

View File

@@ -44,7 +44,7 @@ class DateRequest extends FormRequest
public function getAll(): array
{
return [
'start' => $this->getCarbonDate('start'),
'start' => $this->getCarbonDate('start')->startOfDay(),
'end' => $this->getCarbonDate('end')->endOfDay(),
];
}

View File

@@ -147,7 +147,7 @@ class StoreRequest extends FormRequest
// all custom fields:
'internal_reference' => $this->clearString((string)$object['internal_reference']),
'external_id' => $this->clearString((string)$object['external_id']),
'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')),
'original_source' => sprintf('ff3-v%s', config('firefly.version')),
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
'bunq_payment_id' => $this->clearString((string)$object['bunq_payment_id']),
'external_url' => $this->clearString((string)$object['external_url']),

View File

@@ -51,7 +51,11 @@ class FixUnevenAmount extends Command
$this->convertOldStyleTransfers();
$this->fixUnevenAmounts();
$this->matchCurrencies();
AccountBalanceCalculator::forceRecalculateAll();
if (config('firefly.feature_flags.running_balance_column')) {
$this->friendlyInfo('Will recalculate transaction running balance columns. This may take a LONG time. Please be patient.');
AccountBalanceCalculator::recalculateAll(true);
$this->friendlyInfo('Done recalculating transaction running balance columns.');
}
return 0;
}

View File

@@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
/*
* AddTimezonesToDates.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
namespace FireflyIII\Console\Commands\Integrity;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Models\AccountBalance;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Bill;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\InvitedUser;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\PiggyBankRepetition;
use FireflyIII\Models\Recurrence;
use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command;
use Illuminate\Database\QueryException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class AddTimezonesToDates extends Command
{
use ShowsFriendlyMessages;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:add-timezones-to-dates';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Make sure all dates have a timezone.';
/**
* Execute the console command.
*/
public function handle(): void
{
$models = [
AccountBalance::class => ['date'], // done
AvailableBudget::class => ['start_date', 'end_date'], // done
Bill::class => ['date', 'end_date', 'extension_date'], // done
BudgetLimit::class => ['start_date', 'end_date'], // done
CurrencyExchangeRate::class => ['date'], // done
InvitedUser::class => ['expires'],
PiggyBankEvent::class => ['date'],
PiggyBankRepetition::class => ['startdate', 'targetdate'],
PiggyBank::class => ['startdate', 'targetdate'], // done
Recurrence::class => ['first_date', 'repeat_until', 'latest_date'],
Tag::class => ['date'],
TransactionJournal::class => ['date'],
];
foreach ($models as $model => $fields) {
$this->addTimezoneToModel($model, $fields);
}
}
private function addTimezoneToModel(string $model, array $fields): void
{
foreach ($fields as $field) {
$this->addTimezoneToModelField($model, $field);
}
}
private function addTimezoneToModelField(string $model, string $field): void
{
$shortModel = str_replace('FireflyIII\Models\\', '', $model);
$timezoneField = sprintf('%s_tz', $field);
$items = new Collection();
try {
$items = $model::whereNull($timezoneField)->get();
} catch (QueryException $e) {
$this->friendlyError(sprintf('Cannot add timezone information to field "%s" of model "%s". Field does not exist.', $field, $shortModel));
Log::error($e->getMessage());
}
if (0 === $items->count()) {
$this->friendlyPositive(sprintf('Timezone information is present in field "%s" of model "%s".', $field, $shortModel));
return;
}
$this->friendlyInfo(sprintf('Adding timezone information to field "%s" of model "%s".', $field, $shortModel));
foreach ($items as $item) {
$item->{$timezoneField} = config('app.timezone');
$item->saveQuietly();
}
}
}

View File

@@ -47,6 +47,7 @@ class ReportIntegrity extends Command
return 1;
}
$commands = [
'firefly-iii:add-timezones-to-dates',
'firefly-iii:create-group-memberships',
'firefly-iii:report-empty-objects',
'firefly-iii:report-sum',

View File

@@ -46,10 +46,15 @@ class Cron extends Command
protected $signature = 'firefly-iii:cron
{--F|force : Force the cron job(s) to execute.}
{--date= : Set the date in YYYY-MM-DD to make Firefly III think that\'s the current date.}
{--download-cer : Download exchange rates. Other tasks will be skipped unless also requested.}
{--create-recurring : Create recurring transactions. Other tasks will be skipped unless also requested.}
{--create-auto-budgets : Create auto budgets. Other tasks will be skipped unless also requested.}
{--send-bill-warnings : Send bill warnings. Other tasks will be skipped unless also requested.}
';
public function handle(): int
{
$doAll = !$this->option('download-cer') && !$this->option('create-recurring') && !$this->option('create-auto-budgets') && !$this->option('send-bill-warnings');
$date = null;
try {
@@ -60,7 +65,7 @@ class Cron extends Command
$force = (bool)$this->option('force'); // @phpstan-ignore-line
// Fire exchange rates cron job.
if (true === config('cer.download_enabled')) {
if (true === config('cer.download_enabled') && ($doAll || $this->option('download-cer'))) {
try {
$this->exchangeRatesCronJob($force, $date);
} catch (FireflyException $e) {
@@ -71,30 +76,36 @@ class Cron extends Command
}
// Fire recurring transaction cron job.
try {
$this->recurringCronJob($force, $date);
} catch (FireflyException $e) {
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
$this->friendlyError($e->getMessage());
if ($doAll || $this->option('create-recurring')) {
try {
$this->recurringCronJob($force, $date);
} catch (FireflyException $e) {
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
$this->friendlyError($e->getMessage());
}
}
// Fire auto-budget cron job:
try {
$this->autoBudgetCronJob($force, $date);
} catch (FireflyException $e) {
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
$this->friendlyError($e->getMessage());
if ($doAll || $this->option('create-auto-budgets')) {
try {
$this->autoBudgetCronJob($force, $date);
} catch (FireflyException $e) {
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
$this->friendlyError($e->getMessage());
}
}
// Fire bill warning cron job
try {
$this->billWarningCronJob($force, $date);
} catch (FireflyException $e) {
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
$this->friendlyError($e->getMessage());
if ($doAll || $this->option('send-bill-warnings')) {
try {
$this->billWarningCronJob($force, $date);
} catch (FireflyException $e) {
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
$this->friendlyError($e->getMessage());
}
}
$this->friendlyInfo('More feedback on the cron jobs can be found in the log files.');

View File

@@ -33,6 +33,7 @@ use Illuminate\Console\Command;
class CorrectAccountBalance extends Command
{
use ShowsFriendlyMessages;
public const string CONFIG_NAME = '610_correct_balances';
protected $description = 'Recalculate all account balance amounts';
protected $signature = 'firefly-iii:correct-account-balance {--F|force : Force the execution of this command.}';
@@ -44,23 +45,30 @@ class CorrectAccountBalance extends Command
return 0;
}
$this->friendlyWarning('This command has been disabled.');
$this->markAsExecuted();
if (config('firefly.feature_flags.running_balance_column')) {
$this->friendlyInfo('Will recalculate account balances. This may take a LONG time. Please be patient.');
$this->markAsExecuted();
$this->correctBalanceAmounts();
$this->friendlyInfo('Done recalculating account balances.');
return 0;
}
$this->friendlyWarning('This command has been disabled.');
// $this->correctBalanceAmounts();
return 0;
}
private function correctBalanceAmounts(): void
{
AccountBalanceCalculator::recalculateAll();
return;
AccountBalanceCalculator::recalculateAll(true);
}
private function isExecuted(): bool
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
return (bool)$configVar?->data;
return (bool) $configVar?->data;
}
private function markAsExecuted(): void

View File

@@ -54,6 +54,7 @@ class MigrateRuleActions extends Command
}
$this->replaceEqualSign();
$this->replaceObsoleteActions();
$this->markAsExecuted();
return 0;
}
@@ -179,4 +180,9 @@ class MigrateRuleActions extends Command
}
}
}
private function markAsExecuted(): void
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
}

View File

@@ -67,6 +67,7 @@ class UpgradeDatabase extends Command
'firefly-iii:restore-oauth-keys',
'firefly-iii:correct-account-balance',
// also just in case, some integrity commands:
'firefly-iii:add-timezones-to-dates',
'firefly-iii:create-group-memberships',
'firefly-iii:upgrade-group-information',
'firefly-iii:upgrade-currency-preferences',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -58,20 +58,23 @@ class BillFactory
/** @var Bill $bill */
$bill = Bill::create(
[
'name' => $data['name'],
'match' => 'MIGRATED_TO_RULES',
'amount_min' => $data['amount_min'],
'user_id' => $this->user->id,
'user_group_id' => $this->user->user_group_id,
'transaction_currency_id' => $currency->id,
'amount_max' => $data['amount_max'],
'date' => $data['date'],
'end_date' => $data['end_date'] ?? null,
'extension_date' => $data['extension_date'] ?? null,
'repeat_freq' => $data['repeat_freq'],
'skip' => $skip,
'automatch' => true,
'active' => $active,
'name' => $data['name'],
'match' => 'MIGRATED_TO_RULES',
'amount_min' => $data['amount_min'],
'user_id' => $this->user->id,
'user_group_id' => $this->user->user_group_id,
'transaction_currency_id' => $currency->id,
'amount_max' => $data['amount_max'],
'date' => $data['date'],
'date_tz' => $data['date']->format('e'),
'end_date' => $data['end_date'] ?? null,
'end_date_tz' => $data['end_date']?->format('e'),
'extension_date' => $data['extension_date'] ?? null,
'extension_date_tz' => $data['extension_date']?->format('e'),
'repeat_freq' => $data['repeat_freq'],
'skip' => $skip,
'automatch' => true,
'active' => $active,
]
);
} catch (QueryException $e) {
@@ -126,7 +129,7 @@ class BillFactory
public function findByName(string $name): ?Bill
{
return $this->user->bills()->where('name', 'LIKE', sprintf('%%%s%%', $name))->first();
return $this->user->bills()->whereLike('name', sprintf('%%%s%%', $name))->first();
}
public function setUser(User $user): void

View File

@@ -225,7 +225,8 @@ class TransactionJournalFactory
'bill_id' => $billId,
'transaction_currency_id' => $currency->id,
'description' => substr($description, 0, 1000),
'date' => $carbon->format('Y-m-d H:i:s'),
'date' => $carbon,
'date_tz' => $carbon->format('e'),
'order' => $order,
'tag_count' => 0,
'completed' => 0,

View File

@@ -131,12 +131,14 @@ class BudgetLimitHandler
app('log')->debug(sprintf('Will create AB for period %s to %s', $current->format('Y-m-d'), $currentEnd->format('Y-m-d')));
$availableBudget = new AvailableBudget(
[
'user_id' => $budgetLimit->budget->user->id,
'user_group_id' => $budgetLimit->budget->user->user_group_id,
'transaction_currency_id' => $budgetLimit->transaction_currency_id,
'start_date' => $current,
'end_date' => $currentEnd,
'amount' => $amount,
'user_id' => $budgetLimit->budget->user->id,
'user_group_id' => $budgetLimit->budget->user->user_group_id,
'transaction_currency_id' => $budgetLimit->transaction_currency_id,
'start_date' => $current,
'start_date_tz' => $current->format('e'),
'end_date' => $currentEnd,
'end_date_tz' => $currentEnd->format('e'),
'amount' => $amount,
]
);
$availableBudget->save();

View File

@@ -58,6 +58,7 @@ class PiggyBankEventHandler
'piggy_bank_id' => $event->piggyBank->id,
'transaction_journal_id' => $journal?->id,
'date' => $date->format('Y-m-d'),
'date_tz' => $date->format('e'),
'amount' => $event->amount,
]
);

View File

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

View File

@@ -37,7 +37,9 @@ class PiggyBankObserver
$repetition = new PiggyBankRepetition();
$repetition->piggyBank()->associate($piggyBank);
$repetition->startdate = $piggyBank->startdate;
$repetition->startdate_tz = $piggyBank->startdate->format('e');
$repetition->targetdate = $piggyBank->targetdate;
$repetition->targetdate_tz = $piggyBank->targetdate->format('e');
$repetition->currentamount = '0';
$repetition->save();
}

View File

@@ -200,7 +200,7 @@ trait MetaCollection
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s%%', $internalReference));
$this->query->whereNotLike('journal_meta.data', sprintf('%%%s%%', $internalReference));
return $this;
}
@@ -221,7 +221,7 @@ trait MetaCollection
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $externalId));
$this->query->whereLike('journal_meta.data', sprintf('%%%s%%', $externalId));
return $this;
}
@@ -233,7 +233,7 @@ trait MetaCollection
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s%%', $externalId));
$this->query->whereNotLike('journal_meta.data', sprintf('%%%s%%', $externalId));
return $this;
}
@@ -245,7 +245,7 @@ trait MetaCollection
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s"', $externalId));
$this->query->whereNotLike('journal_meta.data', sprintf('%%%s"', $externalId));
return $this;
}
@@ -257,7 +257,7 @@ trait MetaCollection
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId));
$this->query->whereLike('journal_meta.data', sprintf('"%s%%', $externalId));
return $this;
}
@@ -269,7 +269,7 @@ trait MetaCollection
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s"', $externalId));
$this->query->whereLike('journal_meta.data', sprintf('%%%s"', $externalId));
return $this;
}
@@ -281,7 +281,7 @@ trait MetaCollection
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId));
$this->query->whereLike('journal_meta.data', sprintf('"%s%%', $externalId));
return $this;
}
@@ -292,7 +292,7 @@ trait MetaCollection
$url = (string)json_encode($url);
$url = str_replace('\\', '\\\\', trim($url, '"'));
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $url));
$this->query->whereLike('journal_meta.data', sprintf('%%%s%%', $url));
return $this;
}
@@ -303,7 +303,7 @@ trait MetaCollection
$url = (string)json_encode($url);
$url = str_replace('\\', '\\\\', trim($url, '"'));
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s%%', $url));
$this->query->whereNotLike('journal_meta.data', sprintf('%%%s%%', $url));
return $this;
}
@@ -314,7 +314,7 @@ trait MetaCollection
$url = (string)json_encode($url);
$url = str_replace('\\', '\\\\', ltrim($url, '"'));
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s', $url));
$this->query->whereNotLike('journal_meta.data', sprintf('%%%s', $url));
return $this;
}
@@ -327,7 +327,7 @@ trait MetaCollection
// var_dump($url);
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%s%%', $url));
$this->query->whereNotLike('journal_meta.data', sprintf('%s%%', $url));
return $this;
}
@@ -338,7 +338,7 @@ trait MetaCollection
$url = (string)json_encode($url);
$url = str_replace('\\', '\\\\', ltrim($url, '"'));
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s', $url));
$this->query->whereLike('journal_meta.data', sprintf('%%%s', $url));
return $this;
}
@@ -351,7 +351,7 @@ trait MetaCollection
// var_dump($url);
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%s%%', $url));
$this->query->whereLike('journal_meta.data', sprintf('%s%%', $url));
return $this;
}
@@ -404,7 +404,7 @@ trait MetaCollection
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $internalReference));
$this->query->whereLike('journal_meta.data', sprintf('%%%s%%', $internalReference));
return $this;
}
@@ -416,7 +416,7 @@ trait MetaCollection
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s%%', $internalReference));
$this->query->whereNotLike('journal_meta.data', sprintf('%%%s%%', $internalReference));
return $this;
}
@@ -428,7 +428,7 @@ trait MetaCollection
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s"', $internalReference));
$this->query->whereNotLike('journal_meta.data', sprintf('%%%s"', $internalReference));
return $this;
}
@@ -440,7 +440,7 @@ trait MetaCollection
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $internalReference));
$this->query->whereLike('journal_meta.data', sprintf('"%s%%', $internalReference));
return $this;
}
@@ -452,7 +452,7 @@ trait MetaCollection
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s"', $internalReference));
$this->query->whereLike('journal_meta.data', sprintf('%%%s"', $internalReference));
return $this;
}
@@ -464,7 +464,7 @@ trait MetaCollection
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $internalReference));
$this->query->whereLike('journal_meta.data', sprintf('"%s%%', $internalReference));
return $this;
}
@@ -472,7 +472,7 @@ trait MetaCollection
public function notesContain(string $value): GroupCollectorInterface
{
$this->withNotes();
$this->query->where('notes.text', 'LIKE', sprintf('%%%s%%', $value));
$this->query->whereLike('notes.text', sprintf('%%%s%%', $value));
return $this;
}
@@ -502,7 +502,7 @@ trait MetaCollection
$this->withNotes();
$this->query->where(static function (Builder $q) use ($value): void { // @phpstan-ignore-line
$q->whereNull('notes.text');
$q->orWhere('notes.text', 'NOT LIKE', sprintf('%%%s%%', $value));
$q->orWhereNotLike('notes.text', sprintf('%%%s%%', $value));
});
return $this;
@@ -513,7 +513,7 @@ trait MetaCollection
$this->withNotes();
$this->query->where(static function (Builder $q) use ($value): void { // @phpstan-ignore-line
$q->whereNull('notes.text');
$q->orWhere('notes.text', 'NOT LIKE', sprintf('%%%s', $value));
$q->orWhereNotLike('notes.text', sprintf('%%%s', $value));
});
return $this;
@@ -524,7 +524,7 @@ trait MetaCollection
$this->withNotes();
$this->query->where(static function (Builder $q) use ($value): void { // @phpstan-ignore-line
$q->whereNull('notes.text');
$q->orWhere('notes.text', 'NOT LIKE', sprintf('%s%%', $value));
$q->orWhereNotLike('notes.text', sprintf('%s%%', $value));
});
return $this;
@@ -533,7 +533,7 @@ trait MetaCollection
public function notesEndWith(string $value): GroupCollectorInterface
{
$this->withNotes();
$this->query->where('notes.text', 'LIKE', sprintf('%%%s', $value));
$this->query->whereLike('notes.text', sprintf('%%%s', $value));
return $this;
}
@@ -560,7 +560,7 @@ trait MetaCollection
public function notesStartWith(string $value): GroupCollectorInterface
{
$this->withNotes();
$this->query->where('notes.text', 'LIKE', sprintf('%s%%', $value));
$this->query->whereLike('notes.text', sprintf('%s%%', $value));
return $this;
}
@@ -717,7 +717,7 @@ trait MetaCollection
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', '=', $internalReference);
$this->query->where('journal_meta.data', '=', sprintf('%s', json_encode($internalReference)));
return $this;
}

View File

@@ -117,6 +117,7 @@ class GroupCollector implements GroupCollectorInterface
'transaction_journals.transaction_type_id',
'transaction_journals.description',
'transaction_journals.date',
'transaction_journals.date_tz',
'transaction_journals.order',
// types
@@ -156,7 +157,7 @@ class GroupCollector implements GroupCollectorInterface
static function (EloquentBuilder $q1) use ($array): void {
foreach ($array as $word) {
$keyword = sprintf('%%%s', $word);
$q1->where('transaction_journals.description', 'NOT LIKE', $keyword);
$q1->whereNotLike('transaction_journals.description', $keyword);
}
}
);
@@ -164,7 +165,7 @@ class GroupCollector implements GroupCollectorInterface
static function (EloquentBuilder $q2) use ($array): void {
foreach ($array as $word) {
$keyword = sprintf('%%%s', $word);
$q2->where('transaction_groups.title', 'NOT LIKE', $keyword);
$q2->whereNotLike('transaction_groups.title', $keyword);
$q2->orWhereNull('transaction_groups.title');
}
}
@@ -183,7 +184,7 @@ class GroupCollector implements GroupCollectorInterface
static function (EloquentBuilder $q1) use ($array): void {
foreach ($array as $word) {
$keyword = sprintf('%s%%', $word);
$q1->where('transaction_journals.description', 'NOT LIKE', $keyword);
$q1->whereNotLike('transaction_journals.description', $keyword);
}
}
);
@@ -191,7 +192,7 @@ class GroupCollector implements GroupCollectorInterface
static function (EloquentBuilder $q2) use ($array): void {
foreach ($array as $word) {
$keyword = sprintf('%s%%', $word);
$q2->where('transaction_groups.title', 'NOT LIKE', $keyword);
$q2->whereNotLike('transaction_groups.title', $keyword);
$q2->orWhereNull('transaction_groups.title');
}
}
@@ -210,7 +211,7 @@ class GroupCollector implements GroupCollectorInterface
static function (EloquentBuilder $q1) use ($array): void {
foreach ($array as $word) {
$keyword = sprintf('%%%s', $word);
$q1->where('transaction_journals.description', 'LIKE', $keyword);
$q1->whereLike('transaction_journals.description', $keyword);
}
}
);
@@ -218,7 +219,7 @@ class GroupCollector implements GroupCollectorInterface
static function (EloquentBuilder $q2) use ($array): void {
foreach ($array as $word) {
$keyword = sprintf('%%%s', $word);
$q2->where('transaction_groups.title', 'LIKE', $keyword);
$q2->whereLike('transaction_groups.title', $keyword);
}
}
);
@@ -265,7 +266,7 @@ class GroupCollector implements GroupCollectorInterface
static function (EloquentBuilder $q1) use ($array): void {
foreach ($array as $word) {
$keyword = sprintf('%s%%', $word);
$q1->where('transaction_journals.description', 'LIKE', $keyword);
$q1->whereLike('transaction_journals.description', $keyword);
}
}
);
@@ -273,7 +274,7 @@ class GroupCollector implements GroupCollectorInterface
static function (EloquentBuilder $q2) use ($array): void {
foreach ($array as $word) {
$keyword = sprintf('%s%%', $word);
$q2->where('transaction_groups.title', 'LIKE', $keyword);
$q2->whereLike('transaction_groups.title', $keyword);
}
}
);
@@ -379,7 +380,7 @@ class GroupCollector implements GroupCollectorInterface
static function (EloquentBuilder $q1) use ($array): void {
foreach ($array as $word) {
$keyword = sprintf('%%%s%%', $word);
$q1->where('transaction_journals.description', 'NOT LIKE', $keyword);
$q1->whereNotLike('transaction_journals.description', $keyword);
}
}
);
@@ -387,7 +388,7 @@ class GroupCollector implements GroupCollectorInterface
static function (EloquentBuilder $q2) use ($array): void {
foreach ($array as $word) {
$keyword = sprintf('%%%s%%', $word);
$q2->where('transaction_groups.title', 'NOT LIKE', $keyword);
$q2->whereNotLike('transaction_groups.title', $keyword);
$q2->orWhereNull('transaction_groups.title');
}
}
@@ -944,7 +945,7 @@ class GroupCollector implements GroupCollectorInterface
static function (EloquentBuilder $q1) use ($array): void {
foreach ($array as $word) {
$keyword = sprintf('%%%s%%', $word);
$q1->where('transaction_journals.description', 'LIKE', $keyword);
$q1->whereLike('transaction_journals.description', $keyword);
}
}
);
@@ -952,7 +953,7 @@ class GroupCollector implements GroupCollectorInterface
static function (EloquentBuilder $q2) use ($array): void {
foreach ($array as $word) {
$keyword = sprintf('%%%s%%', $word);
$q2->where('transaction_groups.title', 'LIKE', $keyword);
$q2->whereLike('transaction_groups.title', $keyword);
}
}
);

View File

@@ -103,7 +103,8 @@ class PopupReport implements PopupReportInterface
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($attributes['accounts'])
$collector
->setAccounts($attributes['accounts'])
->withAccountInformation()
->withBudgetInformation()
->withCategoryInformation()
@@ -113,11 +114,10 @@ class PopupReport implements PopupReportInterface
if (null !== $currency) {
$collector->setCurrency($currency);
}
if (null === $budget->id) {
if (null === $budget->id || 0 === $budget->id) {
$collector->setTypes([TransactionType::WITHDRAWAL])->withoutBudget();
}
if (null !== $budget->id) {
if (null !== $budget->id && 0 !== $budget->id) {
$collector->setBudget($budget);
}

View File

@@ -77,12 +77,16 @@ class LoginController extends Controller
*/
public function login(Request $request): JsonResponse|RedirectResponse
{
Log::channel('audit')->info(sprintf('User is trying to login using "%s"', $request->get($this->username())));
$username = $request->get($this->username());
Log::channel('audit')->info(sprintf('User is trying to login using "%s"', $username));
app('log')->debug('User is trying to login.');
try {
$this->validateLogin($request);
} catch (ValidationException $e) {
// basic validation exception.
// report the failed login to the user if the count is 2 or 5.
// TODO here be warning.
return redirect(route('login'))
->withErrors(
[

View File

@@ -23,6 +23,10 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Auth;
use FireflyIII\Events\Security\MFABackupFewLeft;
use FireflyIII\Events\Security\MFABackupNoLeft;
use FireflyIII\Events\Security\MFAManyFailedAttempts;
use FireflyIII\Events\Security\MFAUsedBackupCode;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
@@ -30,6 +34,7 @@ use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\Log;
use PragmaRX\Google2FALaravel\Support\Authenticator;
/**
@@ -47,7 +52,7 @@ class TwoFactorController extends Controller
/** @var User $user */
$user = auth()->user();
$siteOwner = config('firefly.site_owner');
$title = (string)trans('firefly.two_factor_forgot_title');
$title = (string) trans('firefly.two_factor_forgot_title');
return view('auth.lost-two-factor', compact('user', 'siteOwner', 'title'));
}
@@ -59,7 +64,7 @@ class TwoFactorController extends Controller
{
/** @var array $mfaHistory */
$mfaHistory = app('preferences')->get('mfa_history', [])->data;
$mfaCode = (string)$request->get('one_time_password');
$mfaCode = (string) $request->get('one_time_password');
// is in history? then refuse to use it.
if ($this->inMFAHistory($mfaCode, $mfaHistory)) {
@@ -72,10 +77,26 @@ class TwoFactorController extends Controller
/** @var Authenticator $authenticator */
$authenticator = app(Authenticator::class)->boot($request);
// if not OK, save error.
if (!$authenticator->isAuthenticated()) {
$user = auth()->user();
$this->addToMFAFailureCounter();
$counter = $this->getMFAFailureCounter();
if (3 === $counter || 10 === $counter) {
// do not reset MFA failure counter, but DO send a warning to the user.
Log::channel('audit')->info(sprintf('User "%s" has had %d failed MFA attempts.', $user->email, $counter));
event(new MFAManyFailedAttempts($user, $counter));
}
unset($user);
}
if ($authenticator->isAuthenticated()) {
// save MFA in preferences
$this->addToMFAHistory($mfaCode);
// reset failure count
$this->resetMFAFailureCounter();
// otp auth success!
return redirect(route('home'));
}
@@ -85,7 +106,14 @@ class TwoFactorController extends Controller
$this->removeFromBackupCodes($mfaCode);
$authenticator->login();
// reset failure count
$this->resetMFAFailureCounter();
session()->flash('info', trans('firefly.mfa_backup_code'));
// send user notification.
$user = auth()->user();
Log::channel('audit')->info(sprintf('User "%s" has used a backup code.', $user->email));
event(new MFAUsedBackupCode($user));
return redirect(route('home'));
}
@@ -175,6 +203,42 @@ class TwoFactorController extends Controller
$list = [];
}
$newList = array_values(array_diff($list, [$mfaCode]));
// if the list is 3 or less, send a notification.
if (count($newList) <= 3 && count($newList) > 0) {
$user = auth()->user();
Log::channel('audit')->info(sprintf('User "%s" has used a backup code. They have %d backup codes left.', $user->email, count($newList)));
event(new MFABackupFewLeft($user, count($newList)));
}
// if the list is empty, send notification
if (0 === count($newList)) {
$user = auth()->user();
Log::channel('audit')->info(sprintf('User "%s" has used their last backup code.', $user->email));
event(new MFABackupNoLeft($user));
}
app('preferences')->set('mfa_recovery', $newList);
}
private function addToMFAFailureCounter(): void
{
$preference = (int) app('preferences')->get('mfa_failure_count', 0)->data;
++$preference;
Log::channel('audit')->info(sprintf('MFA failure count is set to %d.', $preference));
app('preferences')->set('mfa_failure_count', $preference);
}
private function getMFAFailureCounter(): int
{
$value = (int) app('preferences')->get('mfa_failure_count', 0)->data;
Log::channel('audit')->info(sprintf('MFA failure count is %d.', $value));
return $value;
}
private function resetMFAFailureCounter(): void
{
app('preferences')->set('mfa_failure_count', 0);
Log::channel('audit')->info('MFA failure count is set to zero.');
}
}

View File

@@ -151,6 +151,7 @@ class CategoryController extends Controller
*/
private function reportPeriodChart(Collection $accounts, Carbon $start, Carbon $end, ?Category $category): array
{
$income = [];
$expenses = [];
$categoryId = 0;
@@ -169,8 +170,8 @@ class CategoryController extends Controller
$categoryId = $category->id;
// this gives us all currencies
$collection = new Collection([$category]);
$expenses = $opsRepository->listExpenses($start, $end, null, $collection);
$income = $opsRepository->listIncome($start, $end, null, $collection);
$expenses = $opsRepository->listExpenses($start, $end, $accounts, $collection);
$income = $opsRepository->listIncome($start, $end, $accounts, $collection);
}
$currencies = array_unique(array_merge(array_keys($income), array_keys($expenses)));
$periods = app('navigation')->listOfPeriods($start, $end);

View File

@@ -122,6 +122,7 @@ class CategoryReportController extends Controller
public function categoryIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): JsonResponse
{
$result = [];
$earned = $this->opsRepository->listIncome($start, $end, $accounts, $categories);

View File

@@ -95,7 +95,7 @@ class DebugController extends Controller
// also do some recalculations.
Artisan::call('firefly-iii:trigger-credit-recalculation');
AccountBalanceCalculator::forceRecalculateAll();
AccountBalanceCalculator::recalculateAll(true);
try {
Artisan::call('twig:clean');

View File

@@ -107,7 +107,6 @@ class JavascriptController extends Controller
$lang = $pref->data;
$dateRange = $this->getDateRangeConfig();
$uid = substr(hash('sha256', sprintf('%s-%s-%s', (string)config('app.key'), auth()->user()->id, auth()->user()->email)), 0, 12);
$data = [
'currencyCode' => $currency->code,
'currencySymbol' => $currency->symbol,

View File

@@ -65,6 +65,16 @@ class FrontpageController extends Controller
$info[] = $entry;
}
}
// sort by current percentage (lowest at the top)
uasort(
$info,
static function (array $a, array $b) {
return $a['percentage'] <=> $b['percentage'];
}
);
$html = '';
if (0 !== count($info)) {
try {

View File

@@ -0,0 +1,342 @@
<?php
/*
* MfaController.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Profile;
use FireflyIII\Events\Security\DisabledMFA;
use FireflyIII\Events\Security\EnabledMFA;
use FireflyIII\Events\Security\MFANewBackupCodes;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Http\Requests\ExistingTokenFormRequest;
use FireflyIII\Http\Requests\TokenFormRequest;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use PragmaRX\Recovery\Recovery;
/**
* Class MfaController
*
* Enable MFA Flow:
*
* Page 1 (GET): Show QR code and the manual code. Secret keeps rotating.
* POST: store secret, store response, validate password.
* ---
* Page 3 (GET): Confirm 2FA status and show recovery codes.
* Same page as page 1, but when secret is present.
*/
class MfaController extends Controller
{
protected bool $internalAuth;
/**
* ProfileController constructor.
*/
public function __construct()
{
parent::__construct();
$this->middleware(
static function ($request, $next) {
app('view')->share('title', (string) trans('firefly.profile'));
app('view')->share('mainTitleIcon', 'fa-user');
return $next($request);
}
);
$authGuard = config('firefly.authentication_guard');
$this->internalAuth = 'web' === $authGuard;
app('log')->debug(sprintf('ProfileController::__construct(). Authentication guard is "%s"', $authGuard));
$this->middleware(IsDemoUser::class)->except(['index']);
}
public function index(): Factory|RedirectResponse|View
{
if (!$this->internalAuth) {
request()->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
return redirect(route('profile.index'));
}
$subTitle = (string)trans('firefly.mfa_index_title');
$subTitleIcon = 'fa-calculator';
$enabledMFA = null !== auth()->user()->mfa_secret;
return view('profile.mfa.index')->with(compact('subTitle', 'subTitleIcon', 'enabledMFA'));
}
public function disableMFA(Request $request): Factory|RedirectResponse|View
{
if (!$this->internalAuth) {
request()->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
return redirect(route('profile.index'));
}
$enabledMFA = null !== auth()->user()->mfa_secret;
if (false === $enabledMFA) {
request()->session()->flash('info', trans('firefly.mfa_already_disabled'));
return redirect(route('profile.index'));
}
$subTitle = (string)trans('firefly.mfa_index_title');
$subTitleIcon = 'fa-calculator';
return view('profile.mfa.disable-mfa')->with(compact('subTitle', 'subTitleIcon', 'enabledMFA'));
}
/**
* Delete 2FA routine.
*/
public function disableMFAPost(ExistingTokenFormRequest $request): Redirector|RedirectResponse
{
if (!$this->internalAuth) {
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
return redirect(route('profile.index'));
}
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
/** @var User $user */
$user = auth()->user();
app('preferences')->delete('temp-mfa-secret');
app('preferences')->delete('temp-mfa-codes');
$repository->setMFACode($user, null);
app('preferences')->mark();
session()->flash('success', (string) trans('firefly.pref_two_factor_auth_disabled'));
session()->flash('info', (string) trans('firefly.pref_two_factor_auth_remove_it'));
// also logout current 2FA tokens.
$cookieName = config('google2fa.cookie_name', 'google2fa_token');
\Cookie::forget($cookieName);
// send user notification.
Log::channel('audit')->info(sprintf('User "%s" has disabled MFA', $user->email));
event(new DisabledMFA($user));
return redirect(route('profile.index'));
}
/**
* Enable 2FA screen.
*/
public function enableMFA(Request $request): Redirector|RedirectResponse|View
{
if (!$this->internalAuth) {
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
return redirect(route('profile.index'));
}
/** @var User $user */
$user = auth()->user();
$enabledMFA = null !== $user->mfa_secret;
// If FF3 already has a secret, just set the two-factor auth enabled to 1,
// and let the user continue with the existing secret.
if ($enabledMFA) {
session()->flash('info', (string) trans('firefly.2fa_already_enabled'));
return redirect(route('profile.index'));
}
$domain = $this->getDomain();
$secret = \Google2FA::generateSecretKey();
$image = \Google2FA::getQRCodeInline($domain, auth()->user()->email, (string) $secret);
app('preferences')->set('temp-mfa-secret', $secret);
return view('profile.mfa.enable-mfa', compact('image', 'secret'));
}
public function backupCodesPost(ExistingTokenFormRequest $request): Redirector|RedirectResponse|View
{
if (!$this->internalAuth) {
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
return redirect(route('profile.index'));
}
$enabledMFA = null !== auth()->user()->mfa_secret;
if (false === $enabledMFA) {
request()->session()->flash('info', trans('firefly.mfa_not_enabled'));
return redirect(route('profile.index'));
}
// generate recovery codes:
$recovery = app(Recovery::class);
$recoveryCodes = $recovery->lowercase()
->setCount(8) // Generate 8 codes
->setBlocks(2) // Every code must have 2 blocks
->setChars(6) // Each block must have 6 chars
->toArray()
;
$codes = implode("\r\n", $recoveryCodes);
app('preferences')->set('mfa_recovery', $recoveryCodes);
app('preferences')->mark();
// send user notification.
$user = auth()->user();
Log::channel('audit')->info(sprintf('User "%s" has generated new backup codes.', $user->email));
event(new MFANewBackupCodes($user));
return view('profile.mfa.backup-codes-post')->with(compact('codes'));
}
/**
* @throws FireflyException
*/
public function backupCodes(Request $request): Factory|RedirectResponse|View
{
if (!$this->internalAuth) {
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
return redirect(route('profile.index'));
}
$enabledMFA = null !== auth()->user()->mfa_secret;
if (false === $enabledMFA) {
request()->session()->flash('info', trans('firefly.mfa_not_enabled'));
return redirect(route('profile.index'));
}
return view('profile.mfa.backup-codes-intro');
}
/**
* Submit 2FA for the first time.
*
* @return Redirector|RedirectResponse
*
* @throws FireflyException
*/
public function enableMFAPost(TokenFormRequest $request)
{
if (!$this->internalAuth) {
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
return redirect(route('profile.index'));
}
/** @var User $user */
$user = auth()->user();
// verify password.
$password = $request->get('password');
if (!auth()->validate(['email' => $user->email, 'password' => $password])) {
session()->flash('error', 'Bad user pw, no MFA for you!');
return redirect(route('profile.mfa.index'));
}
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
$secret = app('preferences')->get('temp-mfa-secret')?->data;
if (is_array($secret)) {
$secret = null;
}
$secret = (string) $secret;
$repository->setMFACode($user, $secret);
app('preferences')->delete('temp-mfa-secret');
session()->flash('success', (string) trans('firefly.saved_preferences'));
app('preferences')->mark();
// also save the code so replay attack is prevented.
$mfaCode = $request->get('code');
$this->addToMFAHistory($mfaCode);
// make sure MFA is logged out.
if ('testing' !== config('app.env')) {
\Google2FA::logout();
}
// drop all info from session:
session()->forget(['temp-mfa-secret', 'two-factor-secret', 'two-factor-codes']);
// send user notification.
Log::channel('audit')->info(sprintf('User "%s" has enabled MFA', $user->email));
event(new EnabledMFA($user));
return redirect(route('profile.mfa.backup-codes'));
}
/**
* TODO duplicate code.
*
* @throws FireflyException
*/
private function addToMFAHistory(string $mfaCode): void
{
/** @var array $mfaHistory */
$mfaHistory = app('preferences')->get('mfa_history', [])->data;
$entry = [
'time' => time(),
'code' => $mfaCode,
];
$mfaHistory[] = $entry;
app('preferences')->set('mfa_history', $mfaHistory);
$this->filterMFAHistory();
}
/**
* Remove old entries from the preferences array.
*/
private function filterMFAHistory(): void
{
/** @var array $mfaHistory */
$mfaHistory = app('preferences')->get('mfa_history', [])->data;
$newHistory = [];
$now = time();
foreach ($mfaHistory as $entry) {
$time = $entry['time'];
$code = $entry['code'];
if ($now - $time <= 300) {
$newHistory[] = [
'time' => $time,
'code' => $code,
];
}
}
app('preferences')->set('mfa_history', $newHistory);
}
}

View File

@@ -30,7 +30,6 @@ use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Http\Requests\DeleteAccountFormRequest;
use FireflyIII\Http\Requests\EmailFormRequest;
use FireflyIII\Http\Requests\ProfileFormRequest;
use FireflyIII\Http\Requests\TokenFormRequest;
use FireflyIII\Models\Preference;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Http\Controllers\CreateStuff;
@@ -45,10 +44,6 @@ use Illuminate\Routing\Redirector;
use Illuminate\Support\Collection;
use Illuminate\View\View;
use Laravel\Passport\ClientRepository;
use PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException;
use PragmaRX\Google2FA\Exceptions\InvalidCharactersException;
use PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException;
use PragmaRX\Recovery\Recovery;
/**
* Class ProfileController.
@@ -83,65 +78,6 @@ class ProfileController extends Controller
$this->middleware(IsDemoUser::class)->except(['index']);
}
/**
* View that generates a 2FA code for the user.
*
* @throws IncompatibleWithGoogleAuthenticatorException
* @throws InvalidCharactersException
* @throws SecretKeyTooShortException
*/
public function code(Request $request): Factory|RedirectResponse|View
{
if (!$this->internalAuth) {
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
return redirect(route('profile.index'));
}
$domain = $this->getDomain();
$secretPreference = app('preferences')->get('temp-mfa-secret');
$codesPreference = app('preferences')->get('temp-mfa-codes');
// generate secret if not in session
if (null === $secretPreference) {
// generate secret + store + flash
$secret = \Google2FA::generateSecretKey();
app('preferences')->set('temp-mfa-secret', $secret);
}
// re-use secret if in session
if (null !== $secretPreference) {
// get secret from session and flash
$secret = $secretPreference->data;
}
if (is_array($secret)) {
$secret = '';
}
// generate recovery codes if not in session:
$recoveryCodes = '';
if (null === $codesPreference) {
// generate codes + store + flash:
$recovery = app(Recovery::class);
$recoveryCodes = $recovery->lowercase()->setCount(8)->setBlocks(2)->setChars(6)->toArray();
app('preferences')->set('temp-mfa-codes', $recoveryCodes);
}
// get codes from session if present already:
if (null !== $codesPreference) {
$recoveryCodes = $codesPreference->data;
}
if (!is_array($recoveryCodes)) {
$recoveryCodes = [];
}
$codes = implode("\r\n", $recoveryCodes);
$image = \Google2FA::getQRCodeInline($domain, auth()->user()->email, (string)$secret);
return view('profile.code', compact('image', 'secret', 'codes'));
}
/**
* Screen to confirm email change.
*
@@ -193,61 +129,6 @@ class ProfileController extends Controller
return view('profile.delete-account', compact('title', 'subTitle', 'subTitleIcon'));
}
/**
* Delete 2FA routine.
*/
public function deleteCode(Request $request): Redirector|RedirectResponse
{
if (!$this->internalAuth) {
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
return redirect(route('profile.index'));
}
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
/** @var User $user */
$user = auth()->user();
app('preferences')->delete('temp-mfa-secret');
app('preferences')->delete('temp-mfa-codes');
$repository->setMFACode($user, null);
app('preferences')->mark();
session()->flash('success', (string)trans('firefly.pref_two_factor_auth_disabled'));
session()->flash('info', (string)trans('firefly.pref_two_factor_auth_remove_it'));
return redirect(route('profile.index'));
}
/**
* Enable 2FA screen.
*/
public function enable2FA(Request $request): Redirector|RedirectResponse
{
if (!$this->internalAuth) {
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
return redirect(route('profile.index'));
}
/** @var User $user */
$user = auth()->user();
$enabledMFA = null !== $user->mfa_secret;
// if we don't have a valid secret yet, redirect to the code page to get one.
if (!$enabledMFA) {
return redirect(route('profile.code'));
}
// If FF3 already has a secret, just set the two factor auth enabled to 1,
// and let the user continue with the existing secret.
session()->flash('info', (string)trans('firefly.2fa_already_enabled'));
return redirect(route('profile.index'));
}
/**
* Index for profile.
*
@@ -298,33 +179,6 @@ class ProfileController extends Controller
return view('profile.logout-other-sessions');
}
/**
* @throws FireflyException
*/
public function newBackupCodes(Request $request): Factory|RedirectResponse|View
{
if (!$this->internalAuth) {
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
return redirect(route('profile.index'));
}
// generate recovery codes:
$recovery = app(Recovery::class);
$recoveryCodes = $recovery->lowercase()
->setCount(8) // Generate 8 codes
->setBlocks(2) // Every code must have 7 blocks
->setChars(6) // Each block must have 16 chars
->toArray()
;
$codes = implode("\r\n", $recoveryCodes);
app('preferences')->set('mfa_recovery', $recoveryCodes);
app('preferences')->mark();
return view('profile.new-backup-codes', compact('codes'));
}
/**
* Submit the change email form.
*/
@@ -442,99 +296,6 @@ class ProfileController extends Controller
return view('profile.change-password', compact('title', 'subTitle', 'subTitleIcon'));
}
/**
* Submit 2FA for the first time.
*
* @return Redirector|RedirectResponse
*
* @throws FireflyException
*/
public function postCode(TokenFormRequest $request)
{
if (!$this->internalAuth) {
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
return redirect(route('profile.index'));
}
/** @var User $user */
$user = auth()->user();
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
$secret = app('preferences')->get('temp-mfa-secret')?->data;
if (is_array($secret)) {
$secret = null;
}
$secret = (string)$secret;
$repository->setMFACode($user, $secret);
app('preferences')->delete('temp-mfa-secret');
app('preferences')->delete('temp-mfa-codes');
session()->flash('success', (string)trans('firefly.saved_preferences'));
app('preferences')->mark();
// also save the code so replay attack is prevented.
$mfaCode = $request->get('code');
$this->addToMFAHistory($mfaCode);
// save backup codes in preferences:
app('preferences')->set('mfa_recovery', session()->get('temp-mfa-codes'));
// make sure MFA is logged out.
if ('testing' !== config('app.env')) {
\Google2FA::logout();
}
// drop all info from session:
session()->forget(['temp-mfa-secret', 'two-factor-secret', 'temp-mfa-codes', 'two-factor-codes']);
return redirect(route('profile.index'));
}
/**
* TODO duplicate code.
*
* @throws FireflyException
*/
private function addToMFAHistory(string $mfaCode): void
{
/** @var array $mfaHistory */
$mfaHistory = app('preferences')->get('mfa_history', [])->data;
$entry = [
'time' => time(),
'code' => $mfaCode,
];
$mfaHistory[] = $entry;
app('preferences')->set('mfa_history', $mfaHistory);
$this->filterMFAHistory();
}
/**
* Remove old entries from the preferences array.
*/
private function filterMFAHistory(): void
{
/** @var array $mfaHistory */
$mfaHistory = app('preferences')->get('mfa_history', [])->data;
$newHistory = [];
$now = time();
foreach ($mfaHistory as $entry) {
$time = $entry['time'];
$code = $entry['code'];
if ($now - $time <= 300) {
$newHistory[] = [
'time' => $time,
'code' => $code,
];
}
}
app('preferences')->set('mfa_history', $newHistory);
}
/**
* Submit delete account.
*
@@ -664,7 +425,7 @@ class ProfileController extends Controller
$repository->changeEmail($user, $match);
$repository->unblockUser($user);
// return to login.
// return to login page.
session()->flash('success', (string)trans('firefly.login_with_old_email'));
return redirect(route('login'));

View File

@@ -77,17 +77,18 @@ class ShowController extends Controller
*/
public function show(Recurrence $recurrence)
{
$repos = app(AttachmentRepositoryInterface::class);
$repos = app(AttachmentRepositoryInterface::class);
/** @var RecurrenceTransformer $transformer */
$transformer = app(RecurrenceTransformer::class);
$transformer = app(RecurrenceTransformer::class);
$transformer->setParameters(new ParameterBag());
$array = $transformer->transform($recurrence);
$array = $transformer->transform($recurrence);
$groups = $this->recurring->getTransactions($recurrence);
$today = today(config('app.timezone'));
$array['repeat_until'] = null !== $array['repeat_until'] ? new Carbon($array['repeat_until']) : null;
$groups = $this->recurring->getTransactions($recurrence);
$today = today(config('app.timezone'));
$array['repeat_until'] = null !== $array['repeat_until'] ? new Carbon($array['repeat_until']) : null;
$array['journal_count'] = $this->recurring->getJournalCount($recurrence);
// transform dates back to Carbon objects and expand information
foreach ($array['repetitions'] as $index => $repetition) {
@@ -103,9 +104,9 @@ class ShowController extends Controller
}
// add attachments to the recurrence object.
$attachments = $recurrence->attachments()->get();
$array['attachments'] = [];
$attachmentTransformer = app(AttachmentTransformer::class);
$attachments = $recurrence->attachments()->get();
$array['attachments'] = [];
$attachmentTransformer = app(AttachmentTransformer::class);
/** @var Attachment $attachment */
foreach ($attachments as $attachment) {
@@ -114,7 +115,16 @@ class ShowController extends Controller
$array['attachments'][] = $item;
}
$subTitle = (string)trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]);
if (null !== $array['nr_of_repetitions']) {
$left = $array['nr_of_repetitions'] - $array['journal_count'];
$left = max(0, $left);
// limit each repetition to X occurrences:
foreach ($array['repetitions'] as $index => $repetition) {
$array['repetitions'][$index]['occurrences'] = array_slice($repetition['occurrences'], 0, $left);
}
}
$subTitle = (string)trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]);
return view('recurring.show', compact('recurrence', 'subTitle', 'array', 'groups', 'today'));
}

View File

@@ -40,24 +40,24 @@ class TriggerController extends Controller
{
public function trigger(Recurrence $recurrence, TriggerRecurrenceRequest $request): RedirectResponse
{
$all = $request->getAll();
$date = $all['date'];
$all = $request->getAll();
$date = $all['date'];
// grab the date from the last time the recurrence fired:
$backupDate = $recurrence->latest_date;
$backupDate = $recurrence->latest_date;
// fire the recurring cron job on the given date, then post-date the created transaction.
app('log')->info(sprintf('Trigger: will now fire recurring cron job task for date "%s".', $date->format('Y-m-d H:i:s')));
/** @var CreateRecurringTransactions $job */
$job = app(CreateRecurringTransactions::class);
$job = app(CreateRecurringTransactions::class);
$job->setRecurrences(new Collection([$recurrence]));
$job->setDate($date);
$job->setForce(false);
$job->handle();
app('log')->debug('Done with recurrence.');
$groups = $job->getGroups();
$groups = $job->getGroups();
/** @var TransactionGroup $group */
foreach ($groups as $group) {
@@ -68,7 +68,8 @@ class TriggerController extends Controller
$journal->save();
}
}
$recurrence->latest_date = $backupDate;
$recurrence->latest_date = $backupDate;
$recurrence->latest_date_tz = $backupDate?->format('e');
$recurrence->save();
app('preferences')->mark();

View File

@@ -0,0 +1,56 @@
<?php
/**
* TokenFormRequest.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\Requests;
use FireflyIII\Support\Request\ChecksLogin;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Validator;
/**
* Class ExistingTokenFormRequest.
*/
class ExistingTokenFormRequest extends FormRequest
{
use ChecksLogin;
/**
* Rules for this request.
*/
public function rules(): array
{
// fixed
return [
'password' => 'required|currentPassword',
'code' => 'required|existingMfaCode',
];
}
public function withValidator(Validator $validator): void
{
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}
}

View File

@@ -42,7 +42,8 @@ class TokenFormRequest extends FormRequest
{
// fixed
return [
'code' => 'required|2faCode',
'password' => 'required|currentPassword',
'code' => 'required|2faCode',
];
}

View File

@@ -177,10 +177,11 @@ class CreateRecurringTransactions implements ShouldQueue
// has repeated X times.
$journalCount = $this->repository->getJournalCount($recurrence);
if (0 !== $recurrence->repetitions && $journalCount >= $recurrence->repetitions && false === $this->force) {
app('log')->info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $recurrence->repetitions));
app('log')->info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $journalCount));
return false;
}
app('log')->debug(sprintf('Recurrence #%d has run %d times, max is %d times.', $recurrence->id, $journalCount, $recurrence->repetitions));
// is no longer running
if ($this->repeatUntilHasPassed($recurrence)) {
@@ -202,8 +203,8 @@ class CreateRecurringTransactions implements ShouldQueue
sprintf(
'Recurrence #%d is set to run on %s, and today\'s date is %s. Skipped.',
$recurrence->id,
$recurrence->first_date->format('Y-m-d'),
$this->date->format('Y-m-d')
$recurrence->first_date->format('Y-m-d H:i:s'),
$this->date->format('Y-m-d H:i:s')
)
);
@@ -244,9 +245,10 @@ class CreateRecurringTransactions implements ShouldQueue
private function hasNotStartedYet(Recurrence $recurrence): bool
{
$startDate = $this->getStartDate($recurrence);
app('log')->debug(sprintf('Start date is %s', $startDate->format('Y-m-d')));
app('log')->debug(sprintf('Start date is %s', $startDate->format('Y-m-d H:i:s')));
app('log')->debug(sprintf('Ask date is %s', $this->date->format('Y-m-d H:i:s')));
return $startDate->gt($this->date);
return $startDate->gte($this->date);
}
/**
@@ -341,7 +343,7 @@ class CreateRecurringTransactions implements ShouldQueue
app('log')->debug(sprintf('%s IS today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d')));
// count created journals on THIS day.
$journalCount = $this->repository->getJournalCount($recurrence, $date, $date);
$journalCount = $this->repository->getJournalCount($recurrence, $date, $date);
if ($journalCount > 0 && false === $this->force) {
app('log')->info(sprintf('Already created %d journal(s) for date %s', $journalCount, $date->format('Y-m-d')));
@@ -359,14 +361,12 @@ class CreateRecurringTransactions implements ShouldQueue
}
// create transaction array and send to factory.
$groupTitle = null;
$count = $recurrence->recurrenceTransactions->count();
$groupTitle = null;
$count = $recurrence->recurrenceTransactions->count();
// #8844, if there is one recurrence transaction, use the first title as the title.
if (1 === $count) {
/** @var RecurrenceTransaction $first */
$first = $recurrence->recurrenceTransactions()->first();
$groupTitle = $first->description;
}
// #9305, if there is one recurrence transaction, group title must be NULL.
$groupTitle = null;
// #8844, if there are more, use the recurrence transaction itself.
if ($count > 1) {
$groupTitle = $recurrence->title;
@@ -378,14 +378,14 @@ class CreateRecurringTransactions implements ShouldQueue
return null;
}
$array = [
$array = [
'user' => $recurrence->user_id,
'group_title' => $groupTitle,
'transactions' => $this->getTransactionData($recurrence, $repetition, $date),
];
/** @var TransactionGroup $group */
$group = $this->groupRepository->store($array);
$group = $this->groupRepository->store($array);
++$this->created;
app('log')->info(sprintf('Created new transaction group #%d', $group->id));
@@ -394,7 +394,8 @@ class CreateRecurringTransactions implements ShouldQueue
$this->groups->push($group);
// update recurring thing:
$recurrence->latest_date = $date;
$recurrence->latest_date = $date;
$recurrence->latest_date_tz = $date?->format('e');
$recurrence->save();
return $group;

View File

@@ -28,8 +28,6 @@ class AccountSchema extends Schema
*/
public function fields(): array
{
Log::debug(__METHOD__);
return [
ID::make(),
Attribute::make('created_at'),

View File

@@ -14,7 +14,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
class AccountBalance extends Model
{
use HasFactory;
protected $fillable = ['account_id', 'title', 'transaction_currency_id', 'balance'];
protected $fillable = ['account_id', 'title', 'transaction_currency_id', 'balance', 'date', 'date_tz'];
public function account(): BelongsTo
{

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -34,19 +35,46 @@ class AccountType extends Model
{
use ReturnsIntegerIdTrait;
/** @deprecated */
public const string ASSET = 'Asset account';
/** @deprecated */
public const string BENEFICIARY = 'Beneficiary account';
/** @deprecated */
public const string CASH = 'Cash account';
/** @deprecated */
public const string CREDITCARD = 'Credit card';
/** @deprecated */
public const string DEBT = 'Debt';
/** @deprecated */
public const string DEFAULT = 'Default account';
/** @deprecated */
public const string EXPENSE = 'Expense account';
/** @deprecated */
public const string IMPORT = 'Import account';
/** @deprecated */
public const string INITIAL_BALANCE = 'Initial balance account';
/** @deprecated */
public const string LIABILITY_CREDIT = 'Liability credit account';
/** @deprecated */
public const string LOAN = 'Loan';
/** @deprecated */
public const string MORTGAGE = 'Mortgage';
/** @deprecated */
public const string RECONCILIATION = 'Reconciliation account';
/** @deprecated */
public const string REVENUE = 'Revenue account';
protected $casts
@@ -61,4 +89,11 @@ class AccountType extends Model
{
return $this->hasMany(Account::class);
}
protected function casts(): array
{
return [
// 'type' => AccountTypeEnum::class,
];
}
}

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Enums\AutoBudgetType;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
@@ -53,6 +54,13 @@ class AutoBudget extends Model
return $this->belongsTo(TransactionCurrency::class);
}
protected function casts(): array
{
return [
// 'auto_budget_type' => AutoBudgetType::class,
];
}
protected function amount(): Attribute
{
return Attribute::make(

View File

@@ -51,7 +51,7 @@ class AvailableBudget extends Model
'transaction_currency_id' => 'int',
];
protected $fillable = ['user_id', 'user_group_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date'];
protected $fillable = ['user_id', 'user_group_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -68,6 +68,7 @@ class Bill extends Model
'user_group_id',
'amount_max',
'date',
'date_tz',
'repeat_freq',
'skip',
'automatch',
@@ -75,6 +76,8 @@ class Bill extends Model
'transaction_currency_id',
'end_date',
'extension_date',
'end_date_tz',
'extension_date_tz',
];
protected $hidden = ['amount_min_encrypted', 'amount_max_encrypted', 'name_encrypted', 'match_encrypted'];

View File

@@ -54,7 +54,7 @@ class BudgetLimit extends Model
'deleted' => Deleted::class,
];
protected $fillable = ['budget_id', 'start_date', 'end_date', 'amount', 'transaction_currency_id'];
protected $fillable = ['budget_id', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz', 'amount', 'transaction_currency_id'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -49,7 +49,7 @@ class CurrencyExchangeRate extends Model
'to_currency_id' => 'int',
'date' => 'datetime',
];
protected $fillable = ['user_id', 'from_currency_id', 'to_currency_id', 'date', 'rate'];
protected $fillable = ['user_id', 'from_currency_id', 'to_currency_id', 'date', 'date_tz', 'rate'];
public function fromCurrency(): BelongsTo
{

View File

@@ -44,7 +44,7 @@ class InvitedUser extends Model
'expires' => 'datetime',
'redeemed' => 'boolean',
];
protected $fillable = ['user_id', 'email', 'invite_code', 'expires', 'redeemed'];
protected $fillable = ['user_id', 'email', 'invite_code', 'expires', 'expires_tz', 'redeemed'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -53,7 +53,7 @@ class PiggyBank extends Model
'encrypted' => 'boolean',
];
protected $fillable = ['name', 'account_id', 'order', 'targetamount', 'startdate', 'targetdate', 'active'];
protected $fillable = ['name', 'account_id', 'order', 'targetamount', 'startdate', 'startdate_tz', 'targetdate', 'targetdate_tz', 'active'];
protected $hidden = ['targetamount_encrypted', 'encrypted'];

View File

@@ -42,7 +42,7 @@ class PiggyBankEvent extends Model
'date' => 'date',
];
protected $fillable = ['piggy_bank_id', 'transaction_journal_id', 'date', 'amount'];
protected $fillable = ['piggy_bank_id', 'transaction_journal_id', 'date', 'date_tz', 'amount'];
protected $hidden = ['amount_encrypted'];

View File

@@ -45,7 +45,7 @@ class PiggyBankRepetition extends Model
'targetdate' => 'date',
];
protected $fillable = ['piggy_bank_id', 'startdate', 'targetdate', 'currentamount'];
protected $fillable = ['piggy_bank_id', 'startdate', 'startdate_tz', 'targetdate', 'targetdate_tz', 'currentamount'];
public function piggyBank(): BelongsTo
{

View File

@@ -60,7 +60,7 @@ class Recurrence extends Model
];
protected $fillable
= ['user_id', 'transaction_type_id', 'title', 'description', 'first_date', 'repeat_until', 'latest_date', 'repetitions', 'apply_rules', 'active'];
= ['user_id', 'transaction_type_id', 'title', 'description', 'first_date', 'first_date_tz', 'repeat_until', 'repeat_until_tz', 'latest_date', 'latest_date_tz', 'repetitions', 'apply_rules', 'active'];
/** @var string The table to store the data in */
protected $table = 'recurrences';

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Enums\RecurrenceRepetitionWeekend;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
@@ -58,6 +59,13 @@ class RecurrenceRepetition extends Model
/** @var string The table to store the data in */
protected $table = 'recurrences_repetitions';
protected function casts(): array
{
return [
// 'weekend' => RecurrenceRepetitionWeekend::class,
];
}
public function recurrence(): BelongsTo
{
return $this->belongsTo(Recurrence::class);

View File

@@ -53,7 +53,7 @@ class Tag extends Model
'longitude' => 'float',
];
protected $fillable = ['user_id', 'user_group_id', 'tag', 'date', 'description', 'tagMode'];
protected $fillable = ['user_id', 'user_group_id', 'tag', 'date', 'date_tz', 'description', 'tagMode'];
protected $hidden = ['zoomLevel', 'latitude', 'longitude'];

View File

@@ -37,6 +37,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
@@ -76,6 +77,7 @@ class TransactionJournal extends Model
'completed',
'order',
'date',
'date_tz',
];
protected $hidden = ['encrypted'];
@@ -88,7 +90,7 @@ class TransactionJournal extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$journalId = (int)$value;
$journalId = (int) $value;
/** @var User $user */
$user = auth()->user();
@@ -167,12 +169,16 @@ class TransactionJournal extends Model
public function scopeAfter(EloquentBuilder $query, Carbon $date): EloquentBuilder
{
return $query->where('transaction_journals.date', '>=', $date->format('Y-m-d 00:00:00'));
Log::debug(sprintf('scopeAfter("%s")', $date->format('Y-m-d H:i:s')));
return $query->where('transaction_journals.date', '>=', $date->format('Y-m-d H:i:s'));
}
public function scopeBefore(EloquentBuilder $query, Carbon $date): EloquentBuilder
{
return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00'));
Log::debug(sprintf('scopeBefore("%s")', $date->format('Y-m-d H:i:s')));
return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'));
}
public function scopeTransactionTypes(EloquentBuilder $query, array $types): void
@@ -238,14 +244,14 @@ class TransactionJournal extends Model
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
get: static fn ($value) => (int) $value,
);
}
protected function transactionTypeId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
get: static fn ($value) => (int) $value,
);
}
}

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -53,6 +54,13 @@ class TransactionType extends Model
];
protected $fillable = ['type'];
protected function casts(): array
{
return [
// 'type' => TransactionTypeEnum::class,
];
}
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).
*

View File

@@ -53,6 +53,15 @@ class Webhook extends Model
];
protected $fillable = ['active', 'trigger', 'response', 'delivery', 'user_id', 'user_group_id', 'url', 'title', 'secret'];
protected function casts(): array
{
return [
// 'delivery' => WebhookDelivery::class,
// 'response' => WebhookResponse::class,
// 'trigger' => WebhookTrigger::class,
];
}
public static function getDeliveries(): array
{
$array = [];
@@ -130,7 +139,7 @@ class Webhook extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$webhookId = (int)$value;
$webhookId = (int) $value;
/** @var User $user */
$user = auth()->user();

View File

@@ -0,0 +1,117 @@
<?php
/*
* EnabledMFANotification.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Notifications\Security;
use FireflyIII\Support\Notifications\UrlValidator;
use FireflyIII\User;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class DisabledMFANotification extends Notification
{
use Queueable;
private User $user;
/**
* Create a new notification instance.
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*
* @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toArray($notifiable)
{
return [
];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*
* @return MailMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toMail($notifiable)
{
$subject = (string)trans('email.disabled_mfa_subject');
return (new MailMessage())->markdown('emails.security.disabled-mfa', ['user' => $this->user])->subject($subject);
}
/**
* Get the Slack representation of the notification.
*
* @param mixed $notifiable
*
* @return SlackMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toSlack($notifiable)
{
$message = (string)trans('email.disabled_mfa_slack', ['email' => $this->user->email]);
return (new SlackMessage())->content($message);
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
*
* @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function via($notifiable)
{
/** @var null|User $user */
$user = auth()->user();
$slackUrl = null === $user ? '' : app('preferences')->getForUser(auth()->user(), 'slack_webhook_url', '')->data;
if (is_array($slackUrl)) {
$slackUrl = '';
}
if (UrlValidator::isValidWebhookURL((string)$slackUrl)) {
return ['mail', 'slack'];
}
return ['mail'];
}
}

View File

@@ -0,0 +1,117 @@
<?php
/*
* EnabledMFANotification.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Notifications\Security;
use FireflyIII\Support\Notifications\UrlValidator;
use FireflyIII\User;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class EnabledMFANotification extends Notification
{
use Queueable;
private User $user;
/**
* Create a new notification instance.
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*
* @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toArray($notifiable)
{
return [
];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*
* @return MailMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toMail($notifiable)
{
$subject = (string)trans('email.enabled_mfa_subject');
return (new MailMessage())->markdown('emails.security.enabled-mfa', ['user' => $this->user])->subject($subject);
}
/**
* Get the Slack representation of the notification.
*
* @param mixed $notifiable
*
* @return SlackMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toSlack($notifiable)
{
$message = (string)trans('email.enabled_mfa_slack', ['email' => $this->user->email]);
return (new SlackMessage())->content($message);
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
*
* @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function via($notifiable)
{
/** @var null|User $user */
$user = auth()->user();
$slackUrl = null === $user ? '' : app('preferences')->getForUser(auth()->user(), 'slack_webhook_url', '')->data;
if (is_array($slackUrl)) {
$slackUrl = '';
}
if (UrlValidator::isValidWebhookURL((string)$slackUrl)) {
return ['mail', 'slack'];
}
return ['mail'];
}
}

View File

@@ -0,0 +1,119 @@
<?php
/*
* EnabledMFANotification.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Notifications\Security;
use FireflyIII\Support\Notifications\UrlValidator;
use FireflyIII\User;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class MFABackupFewLeftNotification extends Notification
{
use Queueable;
private User $user;
private int $count;
/**
* Create a new notification instance.
*/
public function __construct(User $user, int $count)
{
$this->user = $user;
$this->count = $count;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*
* @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toArray($notifiable)
{
return [
];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*
* @return MailMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toMail($notifiable)
{
$subject = (string)trans('email.mfa_few_backups_left_subject', ['count' => $this->count]);
return (new MailMessage())->markdown('emails.security.few-backup-codes', ['user' => $this->user, 'count' => $this->count])->subject($subject);
}
/**
* Get the Slack representation of the notification.
*
* @param mixed $notifiable
*
* @return SlackMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toSlack($notifiable)
{
$message = (string)trans('email.mfa_few_backups_left_slack', ['email' => $this->user->email, 'count' => $this->count]);
return (new SlackMessage())->content($message);
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
*
* @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function via($notifiable)
{
/** @var null|User $user */
$user = auth()->user();
$slackUrl = null === $user ? '' : app('preferences')->getForUser(auth()->user(), 'slack_webhook_url', '')->data;
if (is_array($slackUrl)) {
$slackUrl = '';
}
if (UrlValidator::isValidWebhookURL((string)$slackUrl)) {
return ['mail', 'slack'];
}
return ['mail'];
}
}

View File

@@ -0,0 +1,117 @@
<?php
/*
* EnabledMFANotification.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Notifications\Security;
use FireflyIII\Support\Notifications\UrlValidator;
use FireflyIII\User;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class MFABackupNoLeftNotification extends Notification
{
use Queueable;
private User $user;
/**
* Create a new notification instance.
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*
* @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toArray($notifiable)
{
return [
];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*
* @return MailMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toMail($notifiable)
{
$subject = (string)trans('email.mfa_no_backups_left_subject');
return (new MailMessage())->markdown('emails.security.no-backup-codes', ['user' => $this->user])->subject($subject);
}
/**
* Get the Slack representation of the notification.
*
* @param mixed $notifiable
*
* @return SlackMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toSlack($notifiable)
{
$message = (string)trans('email.mfa_no_backups_left_slack', ['email' => $this->user->email]);
return (new SlackMessage())->content($message);
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
*
* @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function via($notifiable)
{
/** @var null|User $user */
$user = auth()->user();
$slackUrl = null === $user ? '' : app('preferences')->getForUser(auth()->user(), 'slack_webhook_url', '')->data;
if (is_array($slackUrl)) {
$slackUrl = '';
}
if (UrlValidator::isValidWebhookURL((string)$slackUrl)) {
return ['mail', 'slack'];
}
return ['mail'];
}
}

View File

@@ -0,0 +1,119 @@
<?php
/*
* EnabledMFANotification.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Notifications\Security;
use FireflyIII\Support\Notifications\UrlValidator;
use FireflyIII\User;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class MFAManyFailedAttemptsNotification extends Notification
{
use Queueable;
private User $user;
private int $count;
/**
* Create a new notification instance.
*/
public function __construct(User $user, int $count)
{
$this->user = $user;
$this->count = $count;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*
* @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toArray($notifiable)
{
return [
];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*
* @return MailMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toMail($notifiable)
{
$subject = (string)trans('email.mfa_many_failed_subject', ['count' => $this->count]);
return (new MailMessage())->markdown('emails.security.many-failed-attempts', ['user' => $this->user, 'count' => $this->count])->subject($subject);
}
/**
* Get the Slack representation of the notification.
*
* @param mixed $notifiable
*
* @return SlackMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toSlack($notifiable)
{
$message = (string)trans('email.mfa_many_failed_slack', ['email' => $this->user->email, 'count' => $this->count]);
return (new SlackMessage())->content($message);
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
*
* @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function via($notifiable)
{
/** @var null|User $user */
$user = auth()->user();
$slackUrl = null === $user ? '' : app('preferences')->getForUser(auth()->user(), 'slack_webhook_url', '')->data;
if (is_array($slackUrl)) {
$slackUrl = '';
}
if (UrlValidator::isValidWebhookURL((string)$slackUrl)) {
return ['mail', 'slack'];
}
return ['mail'];
}
}

View File

@@ -0,0 +1,117 @@
<?php
/*
* EnabledMFANotification.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Notifications\Security;
use FireflyIII\Support\Notifications\UrlValidator;
use FireflyIII\User;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class MFAUsedBackupCodeNotification extends Notification
{
use Queueable;
private User $user;
/**
* Create a new notification instance.
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*
* @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toArray($notifiable)
{
return [
];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*
* @return MailMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toMail($notifiable)
{
$subject = (string)trans('email.used_backup_code_subject');
return (new MailMessage())->markdown('emails.security.used-backup-code', ['user' => $this->user])->subject($subject);
}
/**
* Get the Slack representation of the notification.
*
* @param mixed $notifiable
*
* @return SlackMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toSlack($notifiable)
{
$message = (string)trans('email.used_backup_code_slack', ['email' => $this->user->email]);
return (new SlackMessage())->content($message);
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
*
* @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function via($notifiable)
{
/** @var null|User $user */
$user = auth()->user();
$slackUrl = null === $user ? '' : app('preferences')->getForUser(auth()->user(), 'slack_webhook_url', '')->data;
if (is_array($slackUrl)) {
$slackUrl = '';
}
if (UrlValidator::isValidWebhookURL((string)$slackUrl)) {
return ['mail', 'slack'];
}
return ['mail'];
}
}

View File

@@ -0,0 +1,117 @@
<?php
/*
* EnabledMFANotification.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Notifications\Security;
use FireflyIII\Support\Notifications\UrlValidator;
use FireflyIII\User;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class NewBackupCodesNotification extends Notification
{
use Queueable;
private User $user;
/**
* Create a new notification instance.
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*
* @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toArray($notifiable)
{
return [
];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*
* @return MailMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toMail($notifiable)
{
$subject = (string)trans('email.new_backup_codes_subject');
return (new MailMessage())->markdown('emails.security.new-backup-codes', ['user' => $this->user])->subject($subject);
}
/**
* Get the Slack representation of the notification.
*
* @param mixed $notifiable
*
* @return SlackMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toSlack($notifiable)
{
$message = (string)trans('email.new_backup_codes_slack', ['email' => $this->user->email]);
return (new SlackMessage())->content($message);
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
*
* @return array
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function via($notifiable)
{
/** @var null|User $user */
$user = auth()->user();
$slackUrl = null === $user ? '' : app('preferences')->getForUser(auth()->user(), 'slack_webhook_url', '')->data;
if (is_array($slackUrl)) {
$slackUrl = '';
}
if (UrlValidator::isValidWebhookURL((string)$slackUrl)) {
return ['mail', 'slack'];
}
return ['mail'];
}
}

View File

@@ -40,6 +40,13 @@ use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Events\RequestedReportOnJournals;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\RequestedVersionCheckStatus;
use FireflyIII\Events\Security\DisabledMFA;
use FireflyIII\Events\Security\EnabledMFA;
use FireflyIII\Events\Security\MFABackupFewLeft;
use FireflyIII\Events\Security\MFABackupNoLeft;
use FireflyIII\Events\Security\MFAManyFailedAttempts;
use FireflyIII\Events\Security\MFANewBackupCodes;
use FireflyIII\Events\Security\MFAUsedBackupCode;
use FireflyIII\Events\StoredAccount;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Events\TriggeredAuditLog;
@@ -93,7 +100,7 @@ class EventServiceProvider extends ServiceProvider
protected $listen
= [
// is a User related event.
RegisteredUser::class => [
RegisteredUser::class => [
'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationMail',
'FireflyIII\Handlers\Events\UserEventHandler@sendAdminRegistrationNotification',
'FireflyIII\Handlers\Events\UserEventHandler@attachUserRole',
@@ -101,110 +108,133 @@ class EventServiceProvider extends ServiceProvider
'FireflyIII\Handlers\Events\UserEventHandler@createExchangeRates',
],
// is a User related event.
Login::class => [
Login::class => [
'FireflyIII\Handlers\Events\UserEventHandler@checkSingleUserIsAdmin',
'FireflyIII\Handlers\Events\UserEventHandler@demoUserBackToEnglish',
],
ActuallyLoggedIn::class => [
ActuallyLoggedIn::class => [
'FireflyIII\Handlers\Events\UserEventHandler@storeUserIPAddress',
],
DetectedNewIPAddress::class => [
DetectedNewIPAddress::class => [
'FireflyIII\Handlers\Events\UserEventHandler@notifyNewIPAddress',
],
RequestedVersionCheckStatus::class => [
RequestedVersionCheckStatus::class => [
'FireflyIII\Handlers\Events\VersionCheckEventHandler@checkForUpdates',
],
RequestedReportOnJournals::class => [
RequestedReportOnJournals::class => [
'FireflyIII\Handlers\Events\AutomationHandler@reportJournals',
],
// is a User related event.
RequestedNewPassword::class => [
RequestedNewPassword::class => [
'FireflyIII\Handlers\Events\UserEventHandler@sendNewPassword',
],
// is a User related event.
UserChangedEmail::class => [
UserChangedEmail::class => [
'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeConfirmMail',
'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeUndoMail',
],
// admin related
AdminRequestedTestMessage::class => [
AdminRequestedTestMessage::class => [
'FireflyIII\Handlers\Events\AdminEventHandler@sendTestMessage',
],
NewVersionAvailable::class => [
NewVersionAvailable::class => [
'FireflyIII\Handlers\Events\AdminEventHandler@sendNewVersion',
],
InvitationCreated::class => [
InvitationCreated::class => [
'FireflyIII\Handlers\Events\AdminEventHandler@sendInvitationNotification',
'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationInvite',
],
// is a Transaction Journal related event.
StoredTransactionGroup::class => [
StoredTransactionGroup::class => [
'FireflyIII\Handlers\Events\StoredGroupEventHandler@processRules',
'FireflyIII\Handlers\Events\StoredGroupEventHandler@recalculateCredit',
'FireflyIII\Handlers\Events\StoredGroupEventHandler@triggerWebhooks',
],
// is a Transaction Journal related event.
UpdatedTransactionGroup::class => [
UpdatedTransactionGroup::class => [
'FireflyIII\Handlers\Events\UpdatedGroupEventHandler@unifyAccounts',
'FireflyIII\Handlers\Events\UpdatedGroupEventHandler@processRules',
'FireflyIII\Handlers\Events\UpdatedGroupEventHandler@recalculateCredit',
'FireflyIII\Handlers\Events\UpdatedGroupEventHandler@triggerWebhooks',
],
DestroyedTransactionGroup::class => [
DestroyedTransactionGroup::class => [
'FireflyIII\Handlers\Events\DestroyedGroupEventHandler@triggerWebhooks',
],
// API related events:
AccessTokenCreated::class => [
AccessTokenCreated::class => [
'FireflyIII\Handlers\Events\APIEventHandler@accessTokenCreated',
],
// Webhook related event:
RequestedSendWebhookMessages::class => [
RequestedSendWebhookMessages::class => [
'FireflyIII\Handlers\Events\WebhookEventHandler@sendWebhookMessages',
],
// account related events:
StoredAccount::class => [
StoredAccount::class => [
'FireflyIII\Handlers\Events\StoredAccountEventHandler@recalculateCredit',
],
UpdatedAccount::class => [
UpdatedAccount::class => [
'FireflyIII\Handlers\Events\UpdatedAccountEventHandler@recalculateCredit',
],
// bill related events:
WarnUserAboutBill::class => [
WarnUserAboutBill::class => [
'FireflyIII\Handlers\Events\BillEventHandler@warnAboutBill',
],
// audit log events:
TriggeredAuditLog::class => [
TriggeredAuditLog::class => [
'FireflyIII\Handlers\Events\AuditEventHandler@storeAuditEvent',
],
// piggy bank related events:
ChangedAmount::class => [
ChangedAmount::class => [
'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changePiggyAmount',
],
// budget related events: CRUD budget limit
Created::class => [
Created::class => [
'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@created',
],
Updated::class => [
Updated::class => [
'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@updated',
],
Deleted::class => [
Deleted::class => [
'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@deleted',
],
// rule actions
RuleActionFailedOnArray::class => [
RuleActionFailedOnArray::class => [
'FireflyIII\Handlers\Events\Model\RuleHandler@ruleActionFailedOnArray',
],
RuleActionFailedOnObject::class => [
RuleActionFailedOnObject::class => [
'FireflyIII\Handlers\Events\Model\RuleHandler@ruleActionFailedOnObject',
],
// security related
EnabledMFA::class => [
'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFAEnabledMail',
],
DisabledMFA::class => [
'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFADisabledMail',
],
MFANewBackupCodes::class => [
'FireflyIII\Handlers\Events\Security\MFAHandler@sendNewMFABackupCodesMail',
],
MFAUsedBackupCode::class => [
'FireflyIII\Handlers\Events\Security\MFAHandler@sendUsedBackupCodeMail',
],
MFABackupFewLeft::class => [
'FireflyIII\Handlers\Events\Security\MFAHandler@sendBackupFewLeftMail',
],
MFABackupNoLeft::class => [
'FireflyIII\Handlers\Events\Security\MFAHandler@sendBackupNoLeftMail',
],
MFAManyFailedAttempts::class => [
'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFAFailedAttemptsMail',
],
];
/**

View File

@@ -576,7 +576,7 @@ class AccountRepository implements AccountRepositoryInterface
$parts = explode(' ', $query);
foreach ($parts as $part) {
$search = sprintf('%%%s%%', $part);
$dbQuery->where('name', 'LIKE', $search);
$dbQuery->whereLike('name', $search);
}
}
if (0 !== count($types)) {
@@ -604,11 +604,11 @@ class AccountRepository implements AccountRepositoryInterface
$search = sprintf('%%%s%%', $part);
$dbQuery->where(
static function (EloquentBuilder $q1) use ($search): void { // @phpstan-ignore-line
$q1->where('accounts.iban', 'LIKE', $search);
$q1->whereLike('accounts.iban', $search);
$q1->orWhere(
static function (EloquentBuilder $q2) use ($search): void {
$q2->where('account_meta.name', '=', 'account_number');
$q2->where('account_meta.data', 'LIKE', $search);
$q2->whereLike('account_meta.data', $search);
}
);
}

View File

@@ -57,7 +57,7 @@ class BillRepository implements BillRepositoryInterface
{
$search = $this->user->bills();
if ('' !== $query) {
$search->where('name', 'LIKE', sprintf('%%%s', $query));
$search->whereLike('name', sprintf('%%%s', $query));
}
$search->orderBy('name', 'ASC')
->where('active', true)
@@ -70,7 +70,7 @@ class BillRepository implements BillRepositoryInterface
{
$search = $this->user->bills();
if ('' !== $query) {
$search->where('name', 'LIKE', sprintf('%s%%', $query));
$search->whereLike('name', sprintf('%s%%', $query));
}
$search->orderBy('name', 'ASC')
->where('active', true)
@@ -306,6 +306,8 @@ class BillRepository implements BillRepositoryInterface
{
// app('log')->debug('Now in getPaidDatesInRange()');
Log::debug(sprintf('Search for linked journals between %s and %s', $start->toW3cString(), $end->toW3cString()));
return $bill->transactionJournals()
->before($end)->after($start)->get(
[
@@ -435,15 +437,17 @@ class BillRepository implements BillRepositoryInterface
}
// find the most recent date for this bill NOT in the future. Cache this date:
$start = clone $bill->date;
$start->startOfDay();
app('log')->debug('nextExpectedMatch: Start is '.$start->format('Y-m-d'));
while ($start < $date) {
app('log')->debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d'), $date->format('Y-m-d')));
app('log')->debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d H:i:s'), $date->format('Y-m-d H:i:s')));
$start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
app('log')->debug('Start is now '.$start->format('Y-m-d'));
app('log')->debug('Start is now '.$start->format('Y-m-d H:i:s'));
}
$end = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
$end->endOfDay();
// see if the bill was paid in this period.
$journalCount = $bill->transactionJournals()->before($end)->after($start)->count();
@@ -485,7 +489,7 @@ class BillRepository implements BillRepositoryInterface
{
$query = sprintf('%%%s%%', $query);
return $this->user->bills()->where('name', 'LIKE', $query)->take($limit)->get();
return $this->user->bills()->whereLike('name', $query)->take($limit)->get();
}
public function setObjectGroup(Bill $bill, string $objectGroupTitle): Bill

View File

@@ -198,11 +198,13 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
->where('end_date', $end->format('Y-m-d'))->first()
;
if (null === $availableBudget) {
$availableBudget = new AvailableBudget();
$availableBudget = new AvailableBudget();
$availableBudget->user()->associate($this->user);
$availableBudget->transactionCurrency()->associate($currency);
$availableBudget->start_date = $start->startOfDay()->format('Y-m-d'); // @phpstan-ignore-line
$availableBudget->end_date = $end->endOfDay()->format('Y-m-d'); // @phpstan-ignore-line
$availableBudget->start_date = $start->startOfDay()->format('Y-m-d'); // @phpstan-ignore-line
$availableBudget->start_date_tz = $start->format('e');
$availableBudget->end_date = $end->endOfDay()->format('Y-m-d'); // @phpstan-ignore-line
$availableBudget->end_date_tz = $end->format('e');
}
$availableBudget->amount = $amount;
$availableBudget->save();
@@ -235,7 +237,9 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
'transaction_currency_id' => $data['currency_id'],
'amount' => $data['amount'],
'start_date' => $start->format('Y-m-d'),
'start_date_tz' => $start->format('e'),
'end_date' => $end->format('Y-m-d'),
'end_date_tz' => $end->format('e'),
]
);
}
@@ -255,8 +259,9 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
if (array_key_exists('start', $data)) {
$start = $data['start'];
if ($start instanceof Carbon) {
$start = $data['start']->startOfDay();
$availableBudget->start_date = $start->format('Y-m-d');
$start = $data['start']->startOfDay();
$availableBudget->start_date = $start->format('Y-m-d');
$availableBudget->start_date_tz = $start->format('e');
$availableBudget->save();
}
}
@@ -264,8 +269,9 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
if (array_key_exists('end', $data)) {
$end = $data['end'];
if ($end instanceof Carbon) {
$end = $data['end']->endOfDay();
$availableBudget->end_date = $end->format('Y-m-d');
$end = $data['end']->endOfDay();
$availableBudget->end_date = $end->format('Y-m-d');
$availableBudget->end_date_tz = $end->format('e');
$availableBudget->save();
}
}

View File

@@ -277,7 +277,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$currency->save();
// find the budget:
$budget = $this->user->budgets()->find((int)$data['budget_id']);
$budget = $this->user->budgets()->find((int) $data['budget_id']);
if (null === $budget) {
throw new FireflyException('200004: Budget does not exist.');
}
@@ -323,8 +323,15 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
{
$budgetLimit->amount = array_key_exists('amount', $data) ? $data['amount'] : $budgetLimit->amount;
$budgetLimit->budget_id = array_key_exists('budget_id', $data) ? $data['budget_id'] : $budgetLimit->budget_id;
$budgetLimit->start_date = array_key_exists('start', $data) ? $data['start']->format('Y-m-d 00:00:00') : $budgetLimit->start_date;
$budgetLimit->end_date = array_key_exists('end', $data) ? $data['end']->format('Y-m-d 23:59:59') : $budgetLimit->end_date;
if (array_key_exists('start', $data)) {
$budgetLimit->start_date = $data['start']->startOfDay();
$budgetLimit->start_date_tz = $data['start']->format('e');
}
if (array_key_exists('end', $data)) {
$budgetLimit->end_date = $data['end']->endOfDay();
$budgetLimit->end_date_tz = $data['end']->format('e');
}
// if no currency has been provided, use the user's default currency:
$currency = null;
@@ -351,7 +358,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $amount): ?BudgetLimit
{
// count the limits:
$limits = $budget->budgetlimits()
$limits = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->count('budget_limits.*')
@@ -360,7 +367,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
// there might be a budget limit for these dates:
/** @var null|BudgetLimit $limit */
$limit = $budget->budgetlimits()
$limit = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->first(['budget_limits.*'])
@@ -395,11 +402,13 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
}
app('log')->debug('No existing budget limit, create a new one');
// or create one and return it.
$limit = new BudgetLimit();
$limit = new BudgetLimit();
$limit->budget()->associate($budget);
$limit->start_date = $start->startOfDay();
$limit->end_date = $end->startOfDay();
$limit->amount = $amount;
$limit->start_date = $start->startOfDay();
$limit->start_date_tz = $start->format('e');
$limit->end_date = $end->startOfDay();
$limit->end_date_tz = $end->format('e');
$limit->amount = $amount;
$limit->save();
app('log')->debug(sprintf('Created new budget limit with ID #%d and amount %s', $limit->id, $amount));

View File

@@ -57,7 +57,7 @@ class BudgetRepository implements BudgetRepositoryInterface
{
$search = $this->user->budgets();
if ('' !== $query) {
$search->where('name', 'LIKE', sprintf('%%%s', $query));
$search->whereLike('name', sprintf('%%%s', $query));
}
$search->orderBy('order', 'ASC')
->orderBy('name', 'ASC')->where('active', true)
@@ -70,7 +70,7 @@ class BudgetRepository implements BudgetRepositoryInterface
{
$search = $this->user->budgets();
if ('' !== $query) {
$search->where('name', 'LIKE', sprintf('%s%%', $query));
$search->whereLike('name', sprintf('%s%%', $query));
}
$search->orderBy('order', 'ASC')
->orderBy('name', 'ASC')->where('active', true)
@@ -512,7 +512,7 @@ class BudgetRepository implements BudgetRepositoryInterface
}
$query = sprintf('%%%s%%', $name);
return $this->user->budgets()->where('name', 'LIKE', $query)->first();
return $this->user->budgets()->whereLike('name', $query)->first();
}
/**
@@ -577,7 +577,7 @@ class BudgetRepository implements BudgetRepositoryInterface
{
$search = $this->user->budgets();
if ('' !== $query) {
$search->where('name', 'LIKE', sprintf('%%%s%%', $query));
$search->whereLike('name', sprintf('%%%s%%', $query));
}
$search->orderBy('order', 'ASC')
->orderBy('name', 'ASC')->where('active', true)

View File

@@ -49,7 +49,7 @@ class CategoryRepository implements CategoryRepositoryInterface
{
$search = $this->user->categories();
if ('' !== $query) {
$search->where('name', 'LIKE', sprintf('%%%s', $query));
$search->whereLike('name', sprintf('%%%s', $query));
}
return $search->take($limit)->get();
@@ -59,7 +59,7 @@ class CategoryRepository implements CategoryRepositoryInterface
{
$search = $this->user->categories();
if ('' !== $query) {
$search->where('name', 'LIKE', sprintf('%s%%', $query));
$search->whereLike('name', sprintf('%s%%', $query));
}
return $search->take($limit)->get();
@@ -344,7 +344,7 @@ class CategoryRepository implements CategoryRepositoryInterface
{
$search = $this->user->categories();
if ('' !== $query) {
$search->where('name', 'LIKE', sprintf('%%%s%%', $query));
$search->whereLike('name', sprintf('%%%s%%', $query));
}
return $search->take($limit)->get();

View File

@@ -89,11 +89,12 @@ class CurrencyRepository implements CurrencyRepositoryInterface
{
return CurrencyExchangeRate::create(
[
'user_id' => $this->user->id,
'from_currency_id' => $fromCurrency->id,
'to_currency_id' => $toCurrency->id,
'date' => $date,
'rate' => $rate,
'user_id' => $this->user->id,
'from_currency_id' => $fromCurrency->id,
'to_currency_id' => $toCurrency->id,
'date' => $date,
'date_tz' => $date->format('e'),
'rate' => $rate,
]
);
}

View File

@@ -197,7 +197,7 @@ class JournalRepository implements JournalRepositoryInterface
->orderBy('date', 'DESC')
;
if ('' !== $search) {
$query->where('description', 'LIKE', sprintf('%%%s%%', $search));
$query->whereLike('description', sprintf('%%%s%%', $search));
}
return $query->take($limit)->get();

View File

@@ -120,7 +120,7 @@ class ObjectGroupRepository implements ObjectGroupRepositoryInterface
$parts = explode(' ', $query);
foreach ($parts as $part) {
$search = sprintf('%%%s%%', $part);
$dbQuery->where('title', 'LIKE', $search);
$dbQuery->whereLike('title', $search);
}
}

View File

@@ -178,12 +178,12 @@ trait ModifiesPiggyBanks
*/
public function store(array $data): PiggyBank
{
$order = $this->getMaxOrder() + 1;
$order = $this->getMaxOrder() + 1;
if (array_key_exists('order', $data)) {
$order = $data['order'];
}
$data['order'] = 31337; // very high when creating.
$piggyData = $data;
$data['order'] = 31337; // very high when creating.
$piggyData = $data;
// unset fields just in case.
unset($piggyData['object_group_title'], $piggyData['object_group_id'], $piggyData['notes'], $piggyData['current_amount']);
@@ -192,6 +192,9 @@ trait ModifiesPiggyBanks
$piggyData['targetamount'] = '0';
}
$piggyData['startdate_tz'] = $piggyData['startdate']?->format('e');
$piggyData['targetdate_tz'] = $piggyData['targetdate']?->format('e');
try {
/** @var PiggyBank $piggyBank */
$piggyBank = PiggyBank::create($piggyData);
@@ -208,13 +211,13 @@ trait ModifiesPiggyBanks
$this->updateNote($piggyBank, $data['notes']);
// repetition is auto created.
$repetition = $this->getRepetition($piggyBank);
$repetition = $this->getRepetition($piggyBank);
if (null !== $repetition && array_key_exists('current_amount', $data) && '' !== $data['current_amount']) {
$repetition->currentamount = $data['current_amount'];
$repetition->save();
}
$objectGroupTitle = $data['object_group_title'] ?? '';
$objectGroupTitle = $data['object_group_title'] ?? '';
if ('' !== $objectGroupTitle) {
$objectGroup = $this->findOrCreateObjectGroup($objectGroupTitle);
if (null !== $objectGroup) {
@@ -223,7 +226,7 @@ trait ModifiesPiggyBanks
}
}
// try also with ID
$objectGroupId = (int)($data['object_group_id'] ?? 0);
$objectGroupId = (int)($data['object_group_id'] ?? 0);
if (0 !== $objectGroupId) {
$objectGroup = $this->findObjectGroupById($objectGroupId);
if (null !== $objectGroup) {
@@ -373,10 +376,12 @@ trait ModifiesPiggyBanks
$piggyBank->targetamount = '0';
}
if (array_key_exists('targetdate', $data) && '' !== $data['targetdate']) {
$piggyBank->targetdate = $data['targetdate'];
$piggyBank->targetdate = $data['targetdate'];
$piggyBank->targetdate_tz = $data['targetdate']->format('e');
}
if (array_key_exists('startdate', $data)) {
$piggyBank->startdate = $data['startdate'];
$piggyBank->startdate = $data['startdate'];
$piggyBank->startdate_tz = $data['targetdate']->format('e');
}
$piggyBank->save();

View File

@@ -343,7 +343,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
{
$search = $this->user->piggyBanks();
if ('' !== $query) {
$search->where('piggy_banks.name', 'LIKE', sprintf('%%%s%%', $query));
$search->whereLike('piggy_banks.name', sprintf('%%%s%%', $query));
}
$search->orderBy('piggy_banks.order', 'ASC')
->orderBy('piggy_banks.name', 'ASC')

View File

@@ -67,13 +67,13 @@ class RecurringRepository implements RecurringRepositoryInterface
$set
= TransactionJournalMeta::where(static function (Builder $q1) use ($recurrence): void {
$q1->where('name', 'recurrence_id');
$q1->where('data', json_encode((string)$recurrence->id));
$q1->where('data', json_encode((string) $recurrence->id));
})->get(['journal_meta.transaction_journal_id']);
// there are X journals made for this recurrence. Any of them meant for today?
foreach ($set as $journalMeta) {
$count = TransactionJournalMeta::where(static function (Builder $q2) use ($date): void {
$string = (string)$date;
$string = (string) $date;
app('log')->debug(sprintf('Search for date: %s', json_encode($string)));
$q2->where('name', 'recurrence_date');
$q2->where('data', json_encode($string));
@@ -141,7 +141,7 @@ class RecurringRepository implements RecurringRepositoryInterface
/** @var RecurrenceTransactionMeta $meta */
foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
if ('bill_id' === $meta->name) {
$return = (int)$meta->value;
$return = (int) $meta->value;
}
}
@@ -158,7 +158,7 @@ class RecurringRepository implements RecurringRepositoryInterface
/** @var RecurrenceTransactionMeta $meta */
foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
if ('budget_id' === $meta->name) {
$return = (int)$meta->value;
$return = (int) $meta->value;
}
}
@@ -175,7 +175,7 @@ class RecurringRepository implements RecurringRepositoryInterface
/** @var RecurrenceTransactionMeta $meta */
foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
if ('category_id' === $meta->name) {
$return = (int)$meta->value;
$return = (int) $meta->value;
}
}
@@ -192,7 +192,7 @@ class RecurringRepository implements RecurringRepositoryInterface
/** @var RecurrenceTransactionMeta $meta */
foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
if ('category_name' === $meta->name) {
$return = (string)$meta->value;
$return = (string) $meta->value;
}
}
@@ -204,6 +204,7 @@ class RecurringRepository implements RecurringRepositoryInterface
*/
public function getJournalCount(Recurrence $recurrence, ?Carbon $start = null, ?Carbon $end = null): int
{
Log::debug(sprintf('Now in getJournalCount(#%d, "%s", "%s")', $recurrence->id, $start?->format('Y-m-d H:i:s'), $end?->format('Y-m-d H:i:s')));
$query = TransactionJournal::leftJoin('journal_meta', 'journal_meta.transaction_journal_id', '=', 'transaction_journals.id')
->where('transaction_journals.user_id', $recurrence->user_id)
->whereNull('transaction_journals.deleted_at')
@@ -216,8 +217,10 @@ class RecurringRepository implements RecurringRepositoryInterface
if (null !== $end) {
$query->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00'));
}
$count = $query->count('transaction_journals.id');
Log::debug(sprintf('Count is %d', $count));
return $query->count('transaction_journals.id');
return $count;
}
/**
@@ -228,7 +231,7 @@ class RecurringRepository implements RecurringRepositoryInterface
return TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
->where('transaction_journals.user_id', $this->user->id)
->where('journal_meta.name', '=', 'recurrence_id')
->where('journal_meta.data', '=', json_encode((string)$recurrence->id))
->where('journal_meta.data', '=', json_encode((string) $recurrence->id))
->get(['journal_meta.transaction_journal_id'])->pluck('transaction_journal_id')->toArray()
;
}
@@ -241,7 +244,7 @@ class RecurringRepository implements RecurringRepositoryInterface
/** @var null|Note $note */
$note = $recurrence->notes()->first();
return (string)$note?->text;
return (string) $note?->text;
}
public function getPiggyBank(RecurrenceTransaction $transaction): ?int
@@ -251,7 +254,7 @@ class RecurringRepository implements RecurringRepositoryInterface
/** @var RecurrenceTransactionMeta $metaEntry */
foreach ($meta as $metaEntry) {
if ('piggy_bank_id' === $metaEntry->name) {
return (int)$metaEntry->value;
return (int) $metaEntry->value;
}
}
@@ -281,12 +284,12 @@ class RecurringRepository implements RecurringRepositoryInterface
->whereNull('transaction_journals.deleted_at')
->where('transaction_journals.user_id', $this->user->id)
->where('name', 'recurrence_id')
->where('data', json_encode((string)$recurrence->id))
->where('data', json_encode((string) $recurrence->id))
->get()->pluck('transaction_journal_id')->toArray()
;
$search = [];
foreach ($journalMeta as $journalId) {
$search[] = (int)$journalId;
$search[] = (int) $journalId;
}
/** @var GroupCollectorInterface $collector */
@@ -314,13 +317,13 @@ class RecurringRepository implements RecurringRepositoryInterface
->whereNull('transaction_journals.deleted_at')
->where('transaction_journals.user_id', $this->user->id)
->where('name', 'recurrence_id')
->where('data', json_encode((string)$recurrence->id))
->where('data', json_encode((string) $recurrence->id))
->get()->pluck('transaction_journal_id')->toArray()
;
$search = [];
foreach ($journalMeta as $journalId) {
$search[] = (int)$journalId;
$search[] = (int) $journalId;
}
if (0 === count($search)) {
return new Collection();
@@ -442,28 +445,28 @@ class RecurringRepository implements RecurringRepositoryInterface
if (is_array($language)) {
$language = 'en_US';
}
$language = (string)$language;
$language = (string) $language;
if ('daily' === $repetition->repetition_type) {
return (string)trans('firefly.recurring_daily', [], $language);
return (string) trans('firefly.recurring_daily', [], $language);
}
if ('weekly' === $repetition->repetition_type) {
$dayOfWeek = trans(sprintf('config.dow_%s', $repetition->repetition_moment), [], $language);
if ($repetition->repetition_skip > 0) {
return (string)trans('firefly.recurring_weekly_skip', ['weekday' => $dayOfWeek, 'skip' => $repetition->repetition_skip + 1], $language);
return (string) trans('firefly.recurring_weekly_skip', ['weekday' => $dayOfWeek, 'skip' => $repetition->repetition_skip + 1], $language);
}
return (string)trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek], $language);
return (string) trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek], $language);
}
if ('monthly' === $repetition->repetition_type) {
if ($repetition->repetition_skip > 0) {
return (string)trans(
return (string) trans(
'firefly.recurring_monthly_skip',
['dayOfMonth' => $repetition->repetition_moment, 'skip' => $repetition->repetition_skip + 1],
$language
);
}
return (string)trans(
return (string) trans(
'firefly.recurring_monthly',
['dayOfMonth' => $repetition->repetition_moment, 'skip' => $repetition->repetition_skip - 1],
$language
@@ -474,7 +477,7 @@ class RecurringRepository implements RecurringRepositoryInterface
// first part is number of week, second is weekday.
$dayOfWeek = trans(sprintf('config.dow_%s', $parts[1]), [], $language);
return (string)trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $language);
return (string) trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $language);
}
if ('yearly' === $repetition->repetition_type) {
$today = today(config('app.timezone'))->endOfYear();
@@ -482,11 +485,11 @@ class RecurringRepository implements RecurringRepositoryInterface
if (null === $repDate) {
$repDate = clone $today;
}
$diffInYears = (int)$today->diffInYears($repDate, true);
$diffInYears = (int) $today->diffInYears($repDate, true);
$repDate->addYears($diffInYears); // technically not necessary.
$string = $repDate->isoFormat((string)trans('config.month_and_day_no_year_js'));
$string = $repDate->isoFormat((string) trans('config.month_and_day_no_year_js'));
return (string)trans('firefly.recurring_yearly', ['date' => $string], $language);
return (string) trans('firefly.recurring_yearly', ['date' => $string], $language);
}
return '';
@@ -496,7 +499,7 @@ class RecurringRepository implements RecurringRepositoryInterface
{
$search = $this->user->recurrences();
if ('' !== $query) {
$search->where('recurrences.title', 'LIKE', sprintf('%%%s%%', $query));
$search->whereLike('recurrences.title', sprintf('%%%s%%', $query));
}
$search
->orderBy('recurrences.title', 'ASC')
@@ -520,16 +523,16 @@ class RecurringRepository implements RecurringRepositoryInterface
public function totalTransactions(Recurrence $recurrence, RecurrenceRepetition $repetition): int
{
// if repeat = null just return 0.
if (null === $recurrence->repeat_until && 0 === (int)$recurrence->repetitions) {
if (null === $recurrence->repeat_until && 0 === (int) $recurrence->repetitions) {
return 0;
}
// expect X transactions then stop. Return that number
if (null === $recurrence->repeat_until && 0 !== (int)$recurrence->repetitions) {
return (int)$recurrence->repetitions;
if (null === $recurrence->repeat_until && 0 !== (int) $recurrence->repetitions) {
return (int) $recurrence->repetitions;
}
// need to calculate, this depends on the repetition:
if (null !== $recurrence->repeat_until && 0 === (int)$recurrence->repetitions) {
if (null !== $recurrence->repeat_until && 0 === (int) $recurrence->repetitions) {
$occurrences = $this->getOccurrencesInRange($repetition, $recurrence->first_date ?? today(), $recurrence->repeat_until);
return count($occurrences);

View File

@@ -213,7 +213,7 @@ class RuleRepository implements RuleRepositoryInterface
{
$search = $this->user->rules();
if ('' !== $query) {
$search->where('rules.title', 'LIKE', sprintf('%%%s%%', $query));
$search->whereLike('rules.title', sprintf('%%%s%%', $query));
}
$search->orderBy('rules.order', 'ASC')
->orderBy('rules.title', 'ASC')

View File

@@ -371,7 +371,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
{
$search = $this->user->ruleGroups();
if ('' !== $query) {
$search->where('rule_groups.title', 'LIKE', sprintf('%%%s%%', $query));
$search->whereLike('rule_groups.title', sprintf('%%%s%%', $query));
}
$search->orderBy('rule_groups.order', 'ASC')
->orderBy('rule_groups.title', 'ASC')

View File

@@ -205,7 +205,7 @@ class TagRepository implements TagRepositoryInterface
{
$search = sprintf('%%%s%%', $query);
return $this->user->tags()->where('tag', 'LIKE', $search)->get(['tags.*']);
return $this->user->tags()->whereLike('tag', $search)->get(['tags.*']);
}
/**
@@ -217,7 +217,7 @@ class TagRepository implements TagRepositoryInterface
$tags = $this->user->tags()->orderBy('tag', 'ASC');
if ('' !== $query) {
$search = sprintf('%%%s%%', $query);
$tags->where('tag', 'LIKE', $search);
$tags->whereLike('tag', $search);
}
return $tags->take($limit)->get('tags.*');
@@ -309,14 +309,14 @@ class TagRepository implements TagRepositoryInterface
{
$search = sprintf('%%%s', $query);
return $this->user->tags()->where('tag', 'LIKE', $search)->get(['tags.*']);
return $this->user->tags()->whereLike('tag', $search)->get(['tags.*']);
}
public function tagStartsWith(string $query): Collection
{
$search = sprintf('%s%%', $query);
return $this->user->tags()->where('tag', 'LIKE', $search)->get(['tags.*']);
return $this->user->tags()->whereLike('tag', $search)->get(['tags.*']);
}
public function transferredInPeriod(Tag $tag, Carbon $start, Carbon $end): array

View File

@@ -62,6 +62,6 @@ class TransactionTypeRepository implements TransactionTypeRepositoryInterface
return TransactionType::get();
}
return TransactionType::where('type', 'LIKE', sprintf('%%%s%%', $query))->take($limit)->get();
return TransactionType::whereLike('type', sprintf('%%%s%%', $query))->take($limit)->get();
}
}

View File

@@ -276,6 +276,7 @@ class UserRepository implements UserRepositoryInterface
$invitee->email = $email;
$invitee->redeemed = false;
$invitee->expires = $now;
$invitee->expires_tz = $now->format('e');
$invitee->save();
return $invitee;

View File

@@ -270,7 +270,7 @@ class AccountRepository implements AccountRepositoryInterface
$query->where('accounts.active', $value);
}
if ('name' === $column) {
$query->where('accounts.name', 'LIKE', sprintf('%%%s%%', $value));
$query->whereLike('accounts.name', sprintf('%%%s%%', $value));
}
}
@@ -298,34 +298,38 @@ class AccountRepository implements AccountRepositoryInterface
return $query->get(['accounts.*']);
}
public function searchAccount(array $query, array $types, int $limit): Collection
public function searchAccount(string $query, array $types, int $page, int $limit): Collection
{
// search by group, not by user
$dbQuery = $this->userGroup->accounts()
->where('active', true)
->orderBy('accounts.updated_at', 'ASC')
->orderBy('accounts.order', 'ASC')
->orderBy('accounts.account_type_id', 'ASC')
->orderBy('accounts.name', 'ASC')
->with(['accountType'])
;
if (count($query) > 0) {
// split query on spaces just in case:
// split query on spaces just in case:
if ('' !== trim($query)) {
$dbQuery->where(function (EloquentBuilder $q) use ($query): void {
foreach ($query as $line) {
$parts = explode(' ', $line);
foreach ($parts as $part) {
$search = sprintf('%%%s%%', $part);
$q->orWhere('name', 'LIKE', $search);
}
$parts = explode(' ', $query);
foreach ($parts as $part) {
$search = sprintf('%%%s%%', $part);
$q->orWhereLike('name', $search);
}
});
}
if (0 !== count($types)) {
$dbQuery->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
$dbQuery->whereIn('account_types.type', $types);
}
return $dbQuery->take($limit)->get(['accounts.*']);
$dbQuery->skip(($page - 1) * $limit)->take($limit);
return $dbQuery->get(['accounts.*']);
}
#[\Override]

View File

@@ -80,7 +80,7 @@ interface AccountRepositoryInterface
*/
public function resetAccountOrder(): void;
public function searchAccount(array $query, array $types, int $limit): Collection;
public function searchAccount(string $query, array $types, int $page, int $limit): Collection;
public function setUser(User $user): void;

View File

@@ -41,7 +41,7 @@ class CategoryRepository implements CategoryRepositoryInterface
$parts = explode(' ', $line);
foreach ($parts as $part) {
$search = sprintf('%%%s%%', $part);
$q->orWhere('name', 'LIKE', $search);
$q->orWhereLike('name', $search);
}
}
});

View File

@@ -320,7 +320,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
{
$query = TransactionCurrency::where('enabled', true);
if ('' !== $search) {
$query->where('name', 'LIKE', sprintf('%%%s%%', $search));
$query->whereLike('name', sprintf('%%%s%%', $search));
}
return $query->take($limit)->get();

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