Compare commits

...

218 Commits

Author SHA1 Message Date
github-actions
17b0b1f43f Auto commit for release 'v6.1.24' on 2024-11-23 2024-11-23 21:07:01 +01:00
James Cole
b61df5ec19 Update changelog 2024-11-23 21:01:58 +01:00
James Cole
1ac7275f83 Fix https://github.com/firefly-iii/firefly-iii/issues/9491 2024-11-23 20:59:08 +01:00
github-actions
cd10d04907 Auto commit for release 'v6.1.23' on 2024-11-23 2024-11-23 19:19:46 +01:00
James Cole
f9b76fcb8b Update changelog for new release. 2024-11-23 19:14:15 +01:00
James Cole
093fa067e6 Merge branches 'develop' and 'develop' of github.com:firefly-iii/firefly-iii into develop 2024-11-23 16:15:00 +01:00
James Cole
fa655f065b Part of new API, cleanup code. 2024-11-23 16:14:47 +01:00
James Cole
c8f2244912 Merge pull request #9483 from antoniomrfranco/fix/report-sum-foreign-amount
fix: include foreign_amount in transaction sum calculation
2024-11-23 09:45:53 +01:00
James Cole
f3a20e14a6 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2024-11-23 09:06:36 +01:00
James Cole
33ad47b115 Add import 2024-11-23 09:06:29 +01:00
James Cole
775424d3b7 Merge pull request #9488 from antoniomrfranco/fix/transfer-dest-foreign-info
fix: set dest foreign_amount and foreign_currency_id for foreign transfers
2024-11-23 09:06:07 +01:00
Antônio Franco
c9c86bbd1d fix: set dest foreign_amount and foreign_currency_id for foreign transfers 2024-11-22 13:52:33 -03:00
Antônio Franco
f76a6ad85c fix: include foreign_amount in transaction sum calculation 2024-11-22 09:52:35 -03:00
James Cole
2138b14d89 Add to update as well. 2024-11-22 06:04:32 +01:00
James Cole
1bf61f57f5 Add UTC support. 2024-11-22 06:03:29 +01:00
James Cole
07b55bd71f Fix https://github.com/firefly-iii/firefly-iii/issues/9477 2024-11-19 06:31:49 +01:00
github-actions
8d2d3d4002 Auto commit for release 'develop' on 2024-11-18 2024-11-18 04:18:51 +01:00
James Cole
d182b4b4a6 Fix tables. 2024-11-17 07:12:54 +01:00
James Cole
60f6a91fe4 Cast fields to string and drop unused table. 2024-11-15 19:13:37 +01:00
James Cole
ec89a2f956 Add missing commas. 2024-11-15 18:59:57 +01:00
James Cole
87113d7181 Merge pull request #9468 from yparitcher/main 2024-11-15 06:17:17 +01:00
yparitcher
59fae290e5 Transaction Model: explicitly cast decimal to string
Laravel defers to PDO & the underlying database as to what type decimals are cast as.
When using sqlite text that match a float will be returned as a float, while mySql always returns a string for a decimal.
This leads to crashes with sqlite while trying to import tranansactions. (It does not happen every transaction, only when the balance{before,after} coming from the database is floatable)

`FireflyIII\Support\Models\AccountBalanceCalculator::getLatesBalance(): Return value must be of type string, float returned.`

Fixes: #9458

Signed-off-by: yparitcher <y@paritcher.com>
2024-11-15 00:03:21 -05:00
James Cole
1a8ba2ce53 Merge branch 'main' into develop 2024-11-14 06:37:23 +01:00
James Cole
dddaa25d86 Merge pull request #9465 from firefly-iii/dependabot/composer/composer-2bb02343b8 2024-11-13 18:35:54 +01:00
James Cole
f28341587a Merge branch 'main' into develop 2024-11-13 18:14:07 +01:00
James Cole
5593bf3e08 Fix https://github.com/firefly-iii/firefly-iii/issues/9466 2024-11-13 18:12:40 +01:00
dependabot[bot]
92574a7a9d 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.7 to 7.1.8
- [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.7...v7.1.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-13 16:42:52 +00:00
James Cole
e049266f5d Merge pull request #9463 from firefly-iii/dependabot/composer/composer-a1e7ad0bd3 2024-11-13 08:46:50 +01:00
dependabot[bot]
5b3e6fcb07 Bump laravel/framework in the composer group across 1 directory
Bumps the composer group with 1 update in the / directory: [laravel/framework](https://github.com/laravel/framework).


Updates `laravel/framework` from 11.30.0 to 11.31.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.30.0...v11.31.0)

---
updated-dependencies:
- dependency-name: laravel/framework
  dependency-type: direct:production
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 22:48:57 +00:00
James Cole
b0bfb556db Fix https://github.com/firefly-iii/firefly-iii/issues/9458 2024-11-11 19:40:00 +01:00
James Cole
484acbcb45 Merge branch 'main' into develop 2024-11-11 05:59:45 +01:00
James Cole
cdc802cfb8 Fix broken links 2024-11-11 05:59:35 +01:00
github-actions
582671ca84 Auto commit for release 'develop' on 2024-11-11 2024-11-11 04:10:58 +01:00
James Cole
22498b5804 Expand access rights 2024-11-10 15:30:02 +01:00
James Cole
87f277a482 Small change in time(zone) representation. 2024-11-10 07:30:10 +01:00
James Cole
ae0d74f57a Fix https://github.com/firefly-iii/firefly-iii/issues/9451 2024-11-10 07:10:30 +01:00
James Cole
0ae5593dde Playing around with local date time parsing. 2024-11-09 20:38:30 +01:00
James Cole
0d11769590 Add better timezone support. 2024-11-09 12:19:01 +01:00
github-actions
b7d8daf013 Auto commit for release 'v6.1.22' on 2024-11-09 2024-11-09 06:35:28 +01:00
James Cole
a9c0126b05 Clean up changelog. 2024-11-09 06:30:39 +01:00
James Cole
6bc5a57d10 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2024-11-09 06:23:37 +01:00
James Cole
2714ee96f1 Fix https://github.com/firefly-iii/firefly-iii/issues/9447 2024-11-09 06:23:28 +01:00
github-actions
524d382b7a Auto commit for release 'develop' on 2024-11-08 2024-11-08 21:20:33 +01:00
James Cole
2723e05d2a Fix https://github.com/firefly-iii/firefly-iii/issues/9444 2024-11-08 21:13:41 +01:00
James Cole
6dd9bda6b4 Merge branch 'main' into develop 2024-11-08 21:08:52 +01:00
James Cole
44449bc716 Add changelog 2024-11-08 21:08:28 +01:00
James Cole
b17d8edb50 Add changelog line 2024-11-08 21:04:08 +01:00
James Cole
578072238a Fix https://github.com/firefly-iii/firefly-iii/issues/9443 2024-11-08 21:03:33 +01:00
James Cole
b4edd3dcc4 Catch potential nullpointers 2024-11-08 21:02:36 +01:00
James Cole
068094caac Update BillUpdateService.php
Catch nullpointers

Signed-off-by: James Cole <james@firefly-iii.org>
2024-11-08 09:30:24 +01:00
James Cole
deb58e617d Update PiggyBankObserver.php
Catch nullpointer.

Signed-off-by: James Cole <james@firefly-iii.org>
2024-11-08 09:29:35 +01:00
James Cole
baca0c1120 Update stale.yml
Signed-off-by: James Cole <james@firefly-iii.org>
2024-11-07 10:40:38 +01:00
github-actions
02543438a4 Auto commit for release 'develop' on 2024-11-07 2024-11-07 03:36:46 +01:00
James Cole
d507e59038 Catch nullpointer. 2024-11-07 03:31:55 +01:00
James Cole
9d0fd7ef1b Deprecate constants 2024-11-07 03:29:44 +01:00
James Cole
dbef5e2143 Merge branch 'main' into develop 2024-11-07 03:26:38 +01:00
James Cole
04eca755d2 Smarter (faster) way of updating timezone info. 2024-11-07 03:26:07 +01:00
James Cole
7883692196 Merge pull request #9438 from firefly-iii/dependabot/composer/composer-159f11b3b5 2024-11-07 03:01:55 +01:00
dependabot[bot]
8f64977cb9 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.14.0 to 3.14.1
- [Changelog](https://github.com/twigphp/Twig/blob/v3.14.1/CHANGELOG)
- [Commits](https://github.com/twigphp/Twig/compare/v3.14.0...v3.14.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-06 19:53:31 +00:00
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
github-actions
7e665dbdfc Auto commit for release 'develop' on 2024-09-02 2024-09-02 05:06:53 +02:00
James Cole
b6897ec3a9 Merge pull request #9178 from tasnim0tantawi/bill-test
Add  test cases for Api\V1\Controllers\Autocomplete\BillController & BudgetController
2024-08-31 15:30:37 +02:00
James Cole
660260174a Merge pull request #9179 from tasnim0tantawi/fix-navigation-mtd
fix Navigation.php MTD logic to make tests pass.
2024-08-31 15:30:11 +02:00
tasnim
78d32865b5 Autocomplete\Budget tests 2024-08-27 11:38:49 +03:00
tasnim
edfa92c1aa Navigation.php back and change tests 2024-08-27 09:44:47 +03:00
tasnim
63012f269c fix Navigation.php MTD logic to make tests pass. 2024-08-26 16:06:58 +03:00
tasnim
7d0e7f779f Add unit test cases for Api\V1\BillController 2024-08-26 14:40:11 +03:00
James Cole
b8e18f80f4 Merge pull request #9160 from tasnim0tantawi/category-test
add test cases for api/v1/autocomplete/CategoryController
2024-08-26 07:13:21 +02:00
github-actions
481b01e4f7 Auto commit for release 'develop' on 2024-08-26 2024-08-26 05:07:55 +02:00
mzhubail
edf2030251 Add about test 2024-08-25 12:19:23 +03:00
tasnim
bd1cfffb61 add my name 2024-08-25 09:07:27 +03:00
James Cole
629f70d27d small npm updates 2024-08-25 07:41:19 +02:00
James Cole
57f5ebc0f9 Merge branch 'main' into develop
# Conflicts:
#	package-lock.json
#	resources/assets/v1/package.json
2024-08-25 07:37:50 +02:00
James Cole
b4f51e7b47 Package updates. 2024-08-25 07:36:35 +02:00
James Cole
d78d254e86 Merge pull request #9163 from firefly-iii/dependabot/npm_and_yarn/npm_and_yarn-ba93e5c870
Bump the npm_and_yarn group across 1 directory with 2 updates
2024-08-21 15:42:39 +02:00
dependabot[bot]
eb0c113699 Bump the npm_and_yarn group across 1 directory with 2 updates
Bumps the npm_and_yarn group with 2 updates in the / directory: [axios](https://github.com/axios/axios) and [postcss](https://github.com/postcss/postcss).


Updates `axios` from 1.7.2 to 1.7.4
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.2...v1.7.4)

Updates `postcss` from 8.4.40 to 8.4.41
- [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.40...8.4.41)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-21 13:36:39 +00:00
tasnim
23045ebd59 remove useless comments 2024-08-20 16:02:43 +03:00
tasnim
2e5931f304 add test cases for api/v1/autocomplete/CategoryController 2024-08-20 15:00:52 +03:00
James Cole
a620b07c00 Fix button in edit mode. 2024-08-19 11:40:00 +02:00
203 changed files with 11156 additions and 6126 deletions

View File

@@ -72,27 +72,27 @@
},
{
"name": "composer/pcre",
"version": "3.2.0",
"version": "3.3.2",
"source": {
"type": "git",
"url": "https://github.com/composer/pcre.git",
"reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90"
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/ea4ab6f9580a4fd221e0418f2c357cdd39102a90",
"reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90",
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0"
},
"conflict": {
"phpstan/phpstan": "<1.11.8"
"phpstan/phpstan": "<1.11.10"
},
"require-dev": {
"phpstan/phpstan": "^1.11.8",
"phpstan/phpstan-strict-rules": "^1.1",
"phpstan/phpstan": "^1.12 || ^2",
"phpstan/phpstan-strict-rules": "^1 || ^2",
"phpunit/phpunit": "^8 || ^9"
},
"type": "library",
@@ -131,7 +131,7 @@
],
"support": {
"issues": "https://github.com/composer/pcre/issues",
"source": "https://github.com/composer/pcre/tree/3.2.0"
"source": "https://github.com/composer/pcre/tree/3.3.2"
},
"funding": [
{
@@ -147,28 +147,28 @@
"type": "tidelift"
}
],
"time": "2024-07-25T09:36:02+00:00"
"time": "2024-11-12T16:29:46+00:00"
},
{
"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",
@@ -345,16 +345,16 @@
},
{
"name": "fidry/cpu-core-counter",
"version": "1.1.0",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/theofidry/cpu-core-counter.git",
"reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42"
"reference": "8520451a140d3f46ac33042715115e290cf5785f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42",
"reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42",
"url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f",
"reference": "8520451a140d3f46ac33042715115e290cf5785f",
"shasum": ""
},
"require": {
@@ -394,7 +394,7 @@
],
"support": {
"issues": "https://github.com/theofidry/cpu-core-counter/issues",
"source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0"
"source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0"
},
"funding": [
{
@@ -402,20 +402,20 @@
"type": "github"
}
],
"time": "2024-02-07T09:43:46+00:00"
"time": "2024-08-06T10:04:20+00:00"
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v3.62.0",
"version": "v3.64.0",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "627692f794d35c43483f34b01d94740df2a73507"
"reference": "58dd9c931c785a79739310aef5178928305ffa67"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/627692f794d35c43483f34b01d94740df2a73507",
"reference": "627692f794d35c43483f34b01d94740df2a73507",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/58dd9c931c785a79739310aef5178928305ffa67",
"reference": "58dd9c931c785a79739310aef5178928305ffa67",
"shasum": ""
},
"require": {
@@ -497,7 +497,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.62.0"
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.64.0"
},
"funding": [
{
@@ -505,7 +505,7 @@
"type": "github"
}
],
"time": "2024-08-07T17:03:09+00:00"
"time": "2024-08-30T23:09:38+00:00"
},
{
"name": "psr/container",
@@ -612,16 +612,16 @@
},
{
"name": "psr/log",
"version": "3.0.0",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
"reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
"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.0"
"source": "https://github.com/php-fig/log/tree/3.0.2"
},
"time": "2021-07-14T16:46:02+00:00"
"time": "2024-09-11T13:17:53+00:00"
},
{
"name": "react/cache",
@@ -1259,16 +1259,16 @@
},
{
"name": "symfony/console",
"version": "v7.1.3",
"version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9"
"reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9",
"reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9",
"url": "https://api.github.com/repos/symfony/console/zipball/ff04e5b5ba043d2badfb308197b9e6b42883fcd5",
"reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5",
"shasum": ""
},
"require": {
@@ -1332,7 +1332,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.1.3"
"source": "https://github.com/symfony/console/tree/v7.1.8"
},
"funding": [
{
@@ -1348,7 +1348,7 @@
"type": "tidelift"
}
],
"time": "2024-07-26T12:41:01+00:00"
"time": "2024-11-06T14:23:19+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.3",
"version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "717c6329886f32dc65e27461f80f2a465412fdca"
"reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/717c6329886f32dc65e27461f80f2a465412fdca",
"reference": "717c6329886f32dc65e27461f80f2a465412fdca",
"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.3"
"source": "https://github.com/symfony/finder/tree/v7.1.6"
},
"funding": [
{
@@ -1701,20 +1701,20 @@
"type": "tidelift"
}
],
"time": "2024-07-24T07:08:44+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.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca"
"reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca",
"reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca",
"url": "https://api.github.com/repos/symfony/process/zipball/42783370fda6e538771f7c7a36e9fa2ee3a84892",
"reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892",
"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.8"
},
"funding": [
{
@@ -2303,7 +2303,7 @@
"type": "tidelift"
}
],
"time": "2024-07-26T12:44:47+00:00"
"time": "2024-11-06T14:23:19+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.3",
"version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "ea272a882be7f20cad58d5d78c215001617b7f07"
"reference": "591ebd41565f356fcd8b090fe64dbb5878f50281"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07",
"reference": "ea272a882be7f20cad58d5d78c215001617b7f07",
"url": "https://api.github.com/repos/symfony/string/zipball/591ebd41565f356fcd8b090fe64dbb5878f50281",
"reference": "591ebd41565f356fcd8b090fe64dbb5878f50281",
"shasum": ""
},
"require": {
@@ -2519,7 +2519,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v7.1.3"
"source": "https://github.com/symfony/string/tree/v7.1.8"
},
"funding": [
{
@@ -2535,16 +2535,16 @@
"type": "tidelift"
}
],
"time": "2024-07-22T10:25:37+00:00"
"time": "2024-11-13T13:31:21+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

@@ -12,6 +12,7 @@ jobs:
permissions:
issues: write # for actions/stale to close stale issues
pull-requests: write # for actions/stale to close stale PRs
actions: write
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
@@ -35,4 +36,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,11 @@ 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
- Antônio Franco
- yparitcher
- Jhon Pedroza
- mzhubail
- tasnim
- withbest
- Steve Wasiura
- imlonghao

View File

@@ -112,7 +112,12 @@ class StoreController extends Controller
return response()->json([], 422);
}
$helper->saveAttachmentFromApi($attachment, $body);
$result = $helper->saveAttachmentFromApi($attachment, $body);
if (false === $result) {
app('log')->error('Could not save attachment from API.');
return response()->json([], 422);
}
return response()->json([], 204);
}

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

@@ -48,8 +48,19 @@ class StoreRequest extends FormRequest
public function rules(): array
{
$roles = [];
foreach (UserRoleEnum::cases() as $role) {
$roles[] = $role->value;
}
$string = implode(',', $roles);
return [
'title' => 'unique:user_groups,title|required|min:1|max:255',
'title' => 'unique:user_groups,title|required|min:1|max:255',
'members' => 'required|min:1',
'members.*.user_email' => 'email|missing_with:members.*.user_id',
'members.*.user_id' => 'integer|exists:users,id|missing_with:members.*.user_email',
'members.*.roles' => 'required|array|min:1',
'members.*.roles.*' => sprintf('required|in:%s', $string),
];
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Casts;
use Carbon\Carbon;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
/**
* Class SeparateTimezoneCaster
*
* Checks if the object has a separate _tz value. If it does, it will use that timezone to parse the date.
* If it is NULL, it will use the system's timezone.
*
* At some point a user's database consists entirely of UTC dates, and we won't need this anymore. However,
* the completeness of this migration is not yet guaranteed.
*/
class SeparateTimezoneCaster implements CastsAttributes
{
/**
* @param array<string, mixed> $attributes
*/
public function get(Model $model, string $key, mixed $value, array $attributes): ?Carbon
{
if ('' === $value || null === $value) {
return null;
}
$timeZone = $attributes[sprintf('%s_tz', $key)] ?? config('app.timezone');
return Carbon::parse($value, $timeZone)->setTimezone(config('app.timezone'));
}
/**
* Prepare the given value for storage.
*
* @param array<string, mixed> $attributes
*/
public function set(Model $model, string $key, mixed $value, array $attributes): mixed
{
return $value;
}
}

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 FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Console\Command;
use Illuminate\Database\QueryException;
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.';
public static array $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'],
];
/**
* Execute the console command.
*/
public function handle(): void
{
foreach (self::$models as $model => $fields) {
$this->addTimezoneToModel($model, $fields);
}
// not yet in UTC mode
FireflyConfig::set('utc', false);
}
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);
$count = 0;
try {
$count = $model::whereNull($timezoneField)->count();
} 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 === $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));
$model::whereNull($timezoneField)->update([$timezoneField => config('app.timezone')]);
}
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
/*
* ConvertDatesToUTC.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 Carbon\Carbon;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Console\Command;
use Illuminate\Database\QueryException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class ConvertDatesToUTC extends Command
{
use ShowsFriendlyMessages;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:migrate-to-utc';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Convert stored dates to UTC.';
/**
* Execute the console command.
*/
public function handle(): int
{
/**
* @var string $model
* @var array $fields
*/
foreach (AddTimezonesToDates::$models as $model => $fields) {
$this->ConvertModeltoUTC($model, $fields);
}
// tell the system we are now in UTC mode.
FireflyConfig::set('utc', true);
return Command::SUCCESS;
}
private function ConvertModeltoUTC(string $model, array $fields): void
{
/** @var string $field */
foreach ($fields as $field) {
$this->convertFieldtoUTC($model, $field);
}
}
private function convertFieldtoUTC(string $model, string $field): void
{
$this->info(sprintf('Converting %s.%s to UTC', $model, $field));
$shortModel = str_replace('FireflyIII\Models\\', '', $model);
$timezoneField = sprintf('%s_tz', $field);
$items = new Collection();
$timeZone = config('app.timezone');
try {
$items = $model::where($timezoneField, $timeZone)->get();
} catch (QueryException $e) {
$this->friendlyError(sprintf('Cannot find 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('All timezone information is UTC in field "%s" of model "%s".', $field, $shortModel));
return;
}
$this->friendlyInfo(sprintf('Converting field "%s" of model "%s" to UTC.', $field, $shortModel));
$items->each(
function ($item) use ($field, $timezoneField): void {
/** @var Carbon $date */
$date = Carbon::parse($item->{$field}, $item->{$timezoneField});
$date->setTimezone('UTC');
$item->{$field} = $date->format('Y-m-d H:i:s');
$item->{$timezoneField} = 'UTC';
$item->save();
}
);
}
}

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

@@ -58,7 +58,7 @@ class ReportSum extends Command
/** @var User $user */
foreach ($userRepository->all() as $user) {
$sum = (string)$user->transactions()->sum('amount');
$sum = (string)$user->transactions()->selectRaw('SUM(amount) + SUM(foreign_amount) as total')->value('total');
if (!is_numeric($sum)) {
$message = sprintf('Error: Transactions for user #%d (%s) have an invalid sum ("%s").', $user->id, $user->email, $sum);
$this->friendlyError($message);

View File

@@ -80,7 +80,7 @@ class ForceMigration extends Command
sleep(2);
Schema::dropIfExists('migrations');
$this->friendlyLine('Re-run all migrations...');
Artisan::call('migrate', ['--seed' => true]);
Artisan::call('migrate', ['--seed' => true, '--force' => true]);
sleep(2);
$this->friendlyLine('');
$this->friendlyWarning('There is a good chance you just saw a lot of error messages.');

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

@@ -47,7 +47,7 @@ class FixPostgresSequences extends Command
return 0;
}
$this->friendlyLine('Going to verify PostgreSQL table sequences.');
$tablesToCheck = ['2fa_tokens', 'account_meta', 'account_types', 'accounts', 'attachments', 'auto_budgets', 'available_budgets', 'bills', 'budget_limits', 'budget_transaction', 'budget_transaction_journal', 'budgets', 'categories', 'category_transaction', 'category_transaction_journal', 'configuration', 'currency_exchange_rates', 'failed_jobs', 'group_journals', 'jobs', 'journal_links', 'journal_meta', 'limit_repetitions', 'link_types', 'locations', 'migrations', 'notes', 'oauth_clients', 'oauth_personal_access_clients', 'object_groups', 'permissions', 'piggy_bank_events', 'piggy_bank_repetitions', 'piggy_banks', 'preferences', 'recurrences', 'recurrences_meta', 'recurrences_repetitions', 'recurrences_transactions', 'roles', 'rt_meta', 'rule_actions', 'rule_groups', 'rule_triggers', 'rules', 'tag_transaction_journal', 'tags', 'transaction_currencies', 'transaction_groups', 'transaction_journals', 'transaction_types', 'transactions', 'users', 'webhook_attempts', 'webhook_messages', 'webhooks'];
$tablesToCheck = ['2fa_tokens', 'account_meta', 'account_types', 'accounts', 'attachments', 'auto_budgets', 'available_budgets', 'bills', 'budget_limits', 'budget_transaction', 'budget_transaction_journal', 'budgets', 'categories', 'category_transaction', 'category_transaction_journal', 'configuration', 'currency_exchange_rates', 'failed_jobs', 'group_journals', 'jobs', 'journal_links', 'journal_meta', 'link_types', 'locations', 'migrations', 'notes', 'oauth_clients', 'oauth_personal_access_clients', 'object_groups', 'permissions', 'piggy_bank_events', 'piggy_bank_repetitions', 'piggy_banks', 'preferences', 'recurrences', 'recurrences_meta', 'recurrences_repetitions', 'recurrences_transactions', 'roles', 'rt_meta', 'rule_actions', 'rule_groups', 'rule_triggers', 'rules', 'tag_transaction_journal', 'tags', 'transaction_currencies', 'transaction_groups', 'transaction_journals', 'transaction_types', 'transactions', 'users', 'webhook_attempts', 'webhook_messages', 'webhooks'];
foreach ($tablesToCheck as $tableToCheck) {
$this->friendlyLine(sprintf('Checking the next id sequence for table "%s".', $tableToCheck));

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

@@ -154,7 +154,7 @@ class TransactionIdentifier extends Command
app('log')->error($e->getMessage());
$this->friendlyError('Firefly III could not find the "identifier" field in the "transactions" table.');
$this->friendlyError(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version')));
$this->friendlyError('Please run "php artisan migrate" to add this field to the table.');
$this->friendlyError('Please run "php artisan migrate --force" to add this field to the table.');
$this->friendlyError('Then, run "php artisan firefly:upgrade-database" to try again.');
return null;

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

@@ -30,6 +30,7 @@ namespace FireflyIII\Enums;
enum UserRoleEnum: string
{
// most basic rights, cannot see other members, can see everything else.
// includes reading of metadata
case READ_ONLY = 'ro';
// required to even USE the group properly (in this order)
@@ -38,6 +39,15 @@ enum UserRoleEnum: string
// required to edit, add or change categories/tags/object-groups
case MANAGE_META = 'mng_meta';
// read other objects and things.
case READ_BUDGETS = 'read_budgets';
case READ_PIGGY_BANKS = 'read_piggies';
case READ_SUBSCRIPTIONS = 'read_subscriptions';
case READ_RULES = 'read_rules';
case READ_RECURRING = 'read_recurring';
case READ_WEBHOOKS = 'read_webhooks';
case READ_CURRENCIES = 'read_currencies';
// manage other financial objects:
case MANAGE_BUDGETS = 'mng_budgets';
case MANAGE_PIGGY_BANKS = 'mng_piggies';

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

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Factory;
use Carbon\Carbon;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
@@ -43,6 +44,7 @@ use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
use FireflyIII\Services\Internal\Destroy\JournalDestroyService;
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\NullArrayObject;
use FireflyIII\User;
use FireflyIII\Validation\AccountValidator;
@@ -157,7 +159,7 @@ class TransactionJournalFactory
$this->errorIfDuplicate($row['import_hash_v2']);
/** Some basic fields */
// Some basic fields
$type = $this->typeRepository->findTransactionType(null, $row['type']);
$carbon = $row['date'] ?? today(config('app.timezone'));
$order = $row['order'] ?? 0;
@@ -170,6 +172,13 @@ class TransactionJournalFactory
// Manipulate basic fields
$carbon->setTimezone(config('app.timezone'));
// 2024-11-19, overrule timezone with UTC and store it as UTC.
if (FireflyConfig::get('utc', false)->data) {
$carbon->setTimezone('UTC');
}
// $carbon->setTimezone('UTC');
try {
// validate source and destination using a new Validator.
$this->validateAccounts($row);
@@ -205,7 +214,7 @@ class TransactionJournalFactory
app('log')->debug('Done with getAccount(2x)');
// this is the moment for a reconciliation sanity check (again).
if (TransactionType::RECONCILIATION === $type->type) {
if (TransactionTypeEnum::RECONCILIATION->value === $type->type) {
[$sourceAccount, $destinationAccount] = $this->reconciliationSanityCheck($sourceAccount, $destinationAccount);
}
@@ -225,7 +234,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

@@ -93,7 +93,7 @@ trait AttachmentCollection
->where(
static function (EloquentBuilder $q1): void { // @phpstan-ignore-line
$q1->where('attachments.attachable_type', TransactionJournal::class);
$q1->where('attachments.uploaded', true);
// $q1->where('attachments.uploaded', true);
$q1->whereNull('attachments.deleted_at');
$q1->orWhereNull('attachments.attachable_type');
}

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

@@ -40,6 +40,7 @@ use FireflyIII\Support\Http\Controllers\ChartGeneration;
use FireflyIII\Support\Http\Controllers\DateCalculation;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class AccountController.
@@ -300,13 +301,13 @@ class AccountController extends Controller
$start = clone session('start', today(config('app.timezone'))->startOfMonth());
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
$defaultSet = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray();
app('log')->debug('Default set is ', $defaultSet);
Log::debug('Default set is ', $defaultSet);
$frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
$frontpageArray = !is_array($frontpage->data) ? [] : $frontpage->data;
app('log')->debug('Frontpage preference set is ', $frontpageArray);
Log::debug('Frontpage preference set is ', $frontpageArray);
if (0 === count($frontpageArray)) {
app('preferences')->set('frontpageAccounts', $defaultSet);
app('log')->debug('frontpage set is empty!');
Log::debug('frontpage set is empty!');
}
$accounts = $repository->getAccountsById($frontpageArray);
@@ -414,7 +415,7 @@ class AccountController extends Controller
*/
private function periodByCurrency(Carbon $start, Carbon $end, Account $account, TransactionCurrency $currency): array
{
app('log')->debug(sprintf('Now in periodByCurrency("%s", "%s", %s, "%s")', $start->format('Y-m-d'), $end->format('Y-m-d'), $account->id, $currency->code));
Log::debug(sprintf('Now in periodByCurrency("%s", "%s", %s, "%s")', $start->format('Y-m-d'), $end->format('Y-m-d'), $account->id, $currency->code));
$locale = app('steam')->getLocale();
$step = $this->calculateStep($start, $end);
$result = [
@@ -424,13 +425,13 @@ class AccountController extends Controller
];
$entries = [];
$current = clone $start;
app('log')->debug(sprintf('Step is %s', $step));
Log::debug(sprintf('Step is %s', $step));
// fix for issue https://github.com/firefly-iii/firefly-iii/issues/8041
// have to make sure this chart is always based on the balance at the END of the period.
// This period depends on the size of the chart
$current = app('navigation')->endOfX($current, $step, null);
app('log')->debug(sprintf('$current date is %s', $current->format('Y-m-d')));
Log::debug(sprintf('$current date is %s', $current->format('Y-m-d')));
if ('1D' === $step) {
// per day the entire period, balance for every day.
$format = (string)trans('config.month_and_day_js', [], $locale);
@@ -447,7 +448,7 @@ class AccountController extends Controller
}
if ('1W' === $step || '1M' === $step || '1Y' === $step) {
while ($end >= $current) {
app('log')->debug(sprintf('Current is: %s', $current->format('Y-m-d')));
Log::debug(sprintf('Current is: %s', $current->format('Y-m-d')));
$balance = (float)app('steam')->balance($account, $current, $currency);
$label = app('navigation')->periodShow($current, $step);
$entries[$label] = $balance;

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

@@ -24,18 +24,19 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Report\NetWorthInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Http\Controllers\BasicDataSupport;
use FireflyIII\Support\Http\Controllers\ChartGeneration;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class ReportController.
@@ -88,7 +89,7 @@ class ReportController extends Controller
$includeNetWorth = $accountRepository->getMetaValue($account, 'include_net_worth');
$result = null === $includeNetWorth ? true : '1' === $includeNetWorth;
if (false === $result) {
app('log')->debug(sprintf('Will not include "%s" in net worth charts.', $account->name));
Log::debug(sprintf('Will not include "%s" in net worth charts.', $account->name));
}
return $result;
@@ -136,6 +137,7 @@ class ReportController extends Controller
*/
public function operations(Collection $accounts, Carbon $start, Carbon $end): JsonResponse
{
$end->endOfDay();
// chart properties for cache:
$cache = new CacheProperties();
$cache->addProperty('chart.report.operations');
@@ -146,7 +148,8 @@ class ReportController extends Controller
// return response()->json($cache->get());
}
app('log')->debug('Going to do operations for accounts ', $accounts->pluck('id')->toArray());
Log::debug('Going to do operations for accounts ', $accounts->pluck('id')->toArray());
Log::debug(sprintf('Period: %s to %s', $start->toW3cString(), $end->toW3cString()));
$format = app('navigation')->preferredCarbonFormat($start, $end);
$titleFormat = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
$preferredRange = app('navigation')->preferredRangeFormat($start, $end);
@@ -158,7 +161,14 @@ class ReportController extends Controller
$collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->withAccountInformation();
$collector->setXorAccounts($accounts);
$collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::RECONCILIATION, TransactionType::TRANSFER]);
$collector->setTypes(
[
TransactionTypeEnum::WITHDRAWAL,
TransactionTypeEnum::DEPOSIT,
TransactionTypeEnum::RECONCILIATION,
TransactionTypeEnum::TRANSFER,
]
);
$journals = $collector->getExtractedJournals();
// loop. group by currency and by period.
@@ -184,15 +194,25 @@ class ReportController extends Controller
// deposit = incoming
// transfer or reconcile or opening balance, and these accounts are the destination.
if (TransactionType::DEPOSIT === $journal['transaction_type_type'] || ((TransactionType::TRANSFER === $journal['transaction_type_type'] || TransactionType::RECONCILIATION === $journal['transaction_type_type'] || TransactionType::OPENING_BALANCE === $journal['transaction_type_type']) && in_array($journal['destination_account_id'], $ids, true))) {
if (
TransactionTypeEnum::DEPOSIT->value === $journal['transaction_type_type']
|| ((
TransactionTypeEnum::TRANSFER->value === $journal['transaction_type_type']
|| TransactionTypeEnum::RECONCILIATION->value === $journal['transaction_type_type']
|| TransactionTypeEnum::OPENING_BALANCE->value === $journal['transaction_type_type']
)
&& in_array($journal['destination_account_id'], $ids, true))) {
$key = 'earned';
}
$data[$currencyId][$period][$key] = bcadd($data[$currencyId][$period][$key], $amount);
}
// loop this data, make chart bars for each currency:
Log::debug('Looping data');
/** @var array $currency */
foreach ($data as $currency) {
Log::debug(sprintf('Now processing currency "%s"', $currency['currency_name']));
$income = [
'label' => (string)trans('firefly.box_earned_in_currency', ['currency' => $currency['currency_name']]),
'type' => 'bar',
@@ -214,12 +234,15 @@ class ReportController extends Controller
// loop all possible periods between $start and $end
$currentStart = clone $start;
$currentEnd = clone $end;
Log::debug(sprintf('START current start and end: %s and %s', $currentStart->toW3cString(), $currentEnd->toW3cString()));
// #8374. Sloppy fix for yearly charts. Not really interested in a better fix with v2 layout and all.
if ('1Y' === $preferredRange) {
$currentEnd = app('navigation')->endOfPeriod($currentEnd, $preferredRange);
}
Log::debug('Start of sub-loop');
while ($currentStart <= $currentEnd) {
Log::debug(sprintf('Current start: %s', $currentStart->toW3cString()));
$key = $currentStart->format($format);
$title = $currentStart->isoFormat($titleFormat);
// #8663 make sure the period exists in the data previously collected.
@@ -227,12 +250,20 @@ class ReportController extends Controller
$income['entries'][$title] = app('steam')->bcround($currency[$key]['earned'] ?? '0', $currency['currency_decimal_places']);
$expense['entries'][$title] = app('steam')->bcround($currency[$key]['spent'] ?? '0', $currency['currency_decimal_places']);
}
// #9477 if the period is not in the data, add it with zero values.
if (!array_key_exists($key, $currency)) {
$income['entries'][$title] = '0';
$expense['entries'][$title] = '0';
}
$currentStart = app('navigation')->addPeriod($currentStart, $preferredRange, 0);
}
Log::debug('End of sub-loop');
$chartData[] = $income;
$chartData[] = $expense;
}
Log::debug('End of loop');
$data = $this->generator->multiSet($chartData);
$cache->store($data);

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

@@ -158,6 +158,11 @@ class Installer
// version compare thing.
$configVersion = (string)config('firefly.version');
$dbVersion = (string)app('fireflyconfig')->getFresh('ff3_version', '1.0')->data;
if (str_starts_with($configVersion, 'develop')) {
Log::debug('Skipping version check for develop version.');
return false;
}
if (1 === version_compare($configVersion, $dbVersion)) {
app('log')->warning(
sprintf(

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

@@ -42,6 +42,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class CreateRecurringTransactions.
@@ -88,7 +89,7 @@ class CreateRecurringTransactions implements ShouldQueue
$this->recurrences = new Collection();
$this->groups = new Collection();
app('log')->debug(sprintf('Created new CreateRecurringTransactions("%s")', $this->date->format('Y-m-d')));
Log::debug(sprintf('Created new CreateRecurringTransactions("%s")', $this->date->format('Y-m-d')));
}
public function getGroups(): Collection
@@ -101,25 +102,25 @@ class CreateRecurringTransactions implements ShouldQueue
*/
public function handle(): void
{
app('log')->debug(sprintf('Now at start of CreateRecurringTransactions() job for %s.', $this->date->format('D d M Y')));
Log::debug(sprintf('Now at start of CreateRecurringTransactions() job for %s.', $this->date->format('D d M Y')));
// only use recurrences from database if there is no collection submitted.
if (0 !== count($this->recurrences)) {
app('log')->debug('Using predetermined set of recurrences.');
Log::debug('Using predetermined set of recurrences.');
}
if (0 === count($this->recurrences)) {
app('log')->debug('Grab all recurrences from the database.');
Log::debug('Grab all recurrences from the database.');
$this->recurrences = $this->repository->getAll();
}
$result = [];
$count = $this->recurrences->count();
$this->submitted = $count;
app('log')->debug(sprintf('Count of collection is %d', $count));
Log::debug(sprintf('Count of collection is %d', $count));
// filter recurrences:
$filtered = $this->filterRecurrences($this->recurrences);
app('log')->debug(sprintf('Left after filtering is %d', $filtered->count()));
Log::debug(sprintf('Left after filtering is %d', $filtered->count()));
/** @var Recurrence $recurrence */
foreach ($filtered as $recurrence) {
@@ -133,20 +134,20 @@ class CreateRecurringTransactions implements ShouldQueue
// clear cache for user
app('preferences')->setForUser($recurrence->user, 'lastActivity', microtime());
app('log')->debug(sprintf('Now at recurrence #%d of user #%d', $recurrence->id, $recurrence->user_id));
Log::debug(sprintf('Now at recurrence #%d of user #%d', $recurrence->id, $recurrence->user_id));
$createdReps = $this->handleRepetitions($recurrence);
app('log')->debug(sprintf('Done with recurrence #%d', $recurrence->id));
Log::debug(sprintf('Done with recurrence #%d', $recurrence->id));
$result[$recurrence->user_id] = $result[$recurrence->user_id]->merge($createdReps);
++$this->executed;
}
app('log')->debug('Now running report thing.');
Log::debug('Now running report thing.');
// will now send email to users.
foreach ($result as $userId => $journals) {
event(new RequestedReportOnJournals($userId, $journals));
}
app('log')->debug('Done with handle()');
Log::debug('Done with handle()');
// clear cache:
app('preferences')->mark();
@@ -166,10 +167,10 @@ class CreateRecurringTransactions implements ShouldQueue
*/
private function validRecurrence(Recurrence $recurrence): bool
{
app('log')->debug(sprintf('Now filtering recurrence #%d, owned by user #%d', $recurrence->id, $recurrence->user_id));
Log::debug(sprintf('Now filtering recurrence #%d, owned by user #%d', $recurrence->id, $recurrence->user_id));
// is not active.
if (!$this->active($recurrence)) {
app('log')->info(sprintf('Recurrence #%d is not active. Skipped.', $recurrence->id));
Log::info(sprintf('Recurrence #%d is not active. Skipped.', $recurrence->id));
return false;
}
@@ -177,14 +178,15 @@ 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));
Log::info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $journalCount));
return false;
}
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)) {
app('log')->info(
Log::info(
sprintf(
'Recurrence #%d was set to run until %s, and today\'s date is %s. Skipped.',
$recurrence->id,
@@ -198,12 +200,12 @@ class CreateRecurringTransactions implements ShouldQueue
// first_date is in the future
if ($this->hasNotStartedYet($recurrence)) {
app('log')->info(
Log::info(
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')
)
);
@@ -212,11 +214,11 @@ class CreateRecurringTransactions implements ShouldQueue
// already fired today (with success):
if (false === $this->force && $this->hasFiredToday($recurrence)) {
app('log')->info(sprintf('Recurrence #%d has already fired today. Skipped.', $recurrence->id));
Log::info(sprintf('Recurrence #%d has already fired today. Skipped.', $recurrence->id));
return false;
}
app('log')->debug('Will be included.');
Log::debug('Will be included.');
return true;
}
@@ -244,7 +246,8 @@ 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')));
Log::debug(sprintf('Start date is %s', $startDate->toW3cString()));
Log::debug(sprintf('Ask date is %s', $this->date->toW3cString()));
return $startDate->gt($this->date);
}
@@ -283,7 +286,7 @@ class CreateRecurringTransactions implements ShouldQueue
/** @var RecurrenceRepetition $repetition */
foreach ($recurrence->recurrenceRepetitions as $repetition) {
app('log')->debug(
Log::debug(
sprintf(
'Now repeating %s with value "%s", skips every %d time(s)',
$repetition->repetition_type,
@@ -338,63 +341,62 @@ class CreateRecurringTransactions implements ShouldQueue
if ($date->ne($this->date)) {
return null;
}
app('log')->debug(sprintf('%s IS today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d')));
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')));
Log::info(sprintf('Already created %d journal(s) for date %s', $journalCount, $date->format('Y-m-d')));
return null;
}
if ($this->repository->createdPreviously($recurrence, $date) && false === $this->force) {
app('log')->info('There is a transaction already made for this date, so will not be created now');
Log::info('There is a transaction already made for this date, so will not be created now');
return null;
}
if ($journalCount > 0 && true === $this->force) {
app('log')->warning(sprintf('Already created %d groups for date %s but FORCED to continue.', $journalCount, $date->format('Y-m-d')));
Log::warning(sprintf('Already created %d groups for date %s but FORCED to continue.', $journalCount, $date->format('Y-m-d')));
}
// 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;
}
if (0 === $count) {
app('log')->error('No transactions to be created in this recurrence. Cannot continue.');
Log::error('No transactions to be created in this recurrence. Cannot continue.');
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));
Log::info(sprintf('Created new transaction group #%d', $group->id));
// trigger event:
event(new StoredTransactionGroup($group, $recurrence->apply_rules, true));
$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;
@@ -459,6 +461,7 @@ class CreateRecurringTransactions implements ShouldQueue
{
$newDate = clone $date;
$newDate->startOfDay();
Log::debug(sprintf('Overruled date to "%s', $newDate->format('Y-m-d H:i:s')));
$this->date = $newDate;
}

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

@@ -51,12 +51,13 @@ class Account extends Model
protected $casts
= [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'user_id' => 'integer',
'deleted_at' => 'datetime',
'active' => 'boolean',
'encrypted' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'user_id' => 'integer',
'deleted_at' => 'datetime',
'active' => 'boolean',
'encrypted' => 'boolean',
'virtual_balance' => 'string',
];
protected $fillable = ['user_id', 'user_group_id', 'account_type_id', 'name', 'active', 'virtual_balance', 'iban'];

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -14,7 +15,15 @@ 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'];
protected function casts(): array
{
return [
'date' => SeparateTimezoneCaster::class,
'balance' => 'string',
];
}
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;
@@ -38,11 +39,20 @@ class AutoBudget extends Model
use ReturnsIntegerIdTrait;
use SoftDeletes;
/** @deprecated */
public const int AUTO_BUDGET_ADJUSTED = 3;
/** @deprecated */
public const int AUTO_BUDGET_RESET = 1;
/** @deprecated */
public const int AUTO_BUDGET_ROLLOVER = 2;
protected $fillable = ['budget_id', 'amount', 'period'];
protected $casts = [
'amount' => 'string',
];
public function budget(): BelongsTo
{
return $this->belongsTo(Budget::class);
@@ -53,6 +63,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

@@ -49,9 +49,10 @@ class AvailableBudget extends Model
'start_date' => 'date',
'end_date' => 'date',
'transaction_currency_id' => 'int',
'amount' => 'string',
];
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

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
@@ -49,14 +50,16 @@ class Bill extends Model
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'date' => 'date',
'end_date' => 'date',
'extension_date' => 'date',
'date' => SeparateTimezoneCaster::class,
'end_date' => SeparateTimezoneCaster::class,
'extension_date' => SeparateTimezoneCaster::class,
'skip' => 'int',
'automatch' => 'boolean',
'active' => 'boolean',
'name_encrypted' => 'boolean',
'match_encrypted' => 'boolean',
'amount_min' => 'string',
'amount_max' => 'string',
];
protected $fillable
@@ -68,6 +71,7 @@ class Bill extends Model
'user_group_id',
'amount_max',
'date',
'date_tz',
'repeat_freq',
'skip',
'automatch',
@@ -75,6 +79,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

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Events\Model\BudgetLimit\Created;
use FireflyIII\Events\Model\BudgetLimit\Deleted;
use FireflyIII\Events\Model\BudgetLimit\Updated;
@@ -43,9 +44,10 @@ class BudgetLimit extends Model
= [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'start_date' => 'date',
'end_date' => 'date',
'start_date' => SeparateTimezoneCaster::class,
'end_date' => SeparateTimezoneCaster::class,
'auto_budget' => 'boolean',
'amount' => 'string',
];
protected $dispatchesEvents
= [
@@ -54,7 +56,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

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
@@ -47,9 +48,11 @@ class CurrencyExchangeRate extends Model
'user_id' => 'int',
'from_currency_id' => 'int',
'to_currency_id' => 'int',
'date' => 'datetime',
'date' => SeparateTimezoneCaster::class,
'rate' => 'string',
'user_rate' => 'string',
];
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

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
@@ -41,10 +42,10 @@ class InvitedUser extends Model
protected $casts
= [
'expires' => 'datetime',
'expires' => SeparateTimezoneCaster::class,
'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

@@ -43,17 +43,18 @@ class PiggyBank extends Model
protected $casts
= [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'startdate' => 'date',
'targetdate' => 'date',
'order' => 'int',
'active' => 'boolean',
'encrypted' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'startdate' => 'date',
'targetdate' => 'date',
'order' => 'int',
'active' => 'boolean',
'encrypted' => 'boolean',
'targetamount' => 'string',
];
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

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
@@ -39,10 +40,11 @@ class PiggyBankEvent extends Model
= [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'date' => 'date',
'date' => SeparateTimezoneCaster::class,
'amount' => 'string',
];
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

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Carbon\Carbon;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Casts\Attribute;
@@ -39,13 +40,14 @@ class PiggyBankRepetition extends Model
protected $casts
= [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'startdate' => 'date',
'targetdate' => 'date',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'startdate' => SeparateTimezoneCaster::class,
'targetdate' => SeparateTimezoneCaster::class,
'virtual_balance' => 'string',
];
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

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
@@ -51,16 +52,16 @@ class Recurrence extends Model
'title' => 'string',
'id' => 'int',
'description' => 'string',
'first_date' => 'date',
'repeat_until' => 'date',
'latest_date' => 'date',
'first_date' => SeparateTimezoneCaster::class,
'repeat_until' => SeparateTimezoneCaster::class,
'latest_date' => SeparateTimezoneCaster::class,
'repetitions' => 'int',
'active' => 'bool',
'apply_rules' => 'bool',
];
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;
@@ -37,9 +38,16 @@ class RecurrenceRepetition extends Model
use ReturnsIntegerIdTrait;
use SoftDeletes;
/** @deprecated */
public const int WEEKEND_DO_NOTHING = 1;
/** @deprecated */
public const int WEEKEND_SKIP_CREATION = 2;
/** @deprecated */
public const int WEEKEND_TO_FRIDAY = 3;
/** @deprecated */
public const int WEEKEND_TO_MONDAY = 4;
protected $casts
@@ -58,6 +66,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

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
@@ -47,13 +48,13 @@ class Tag extends Model
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'date' => 'date',
'date' => SeparateTimezoneCaster::class,
'zoomLevel' => 'int',
'latitude' => 'float',
'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

@@ -54,7 +54,11 @@ class Transaction extends Model
'bill_name_encrypted' => 'boolean',
'reconciled' => 'boolean',
'balance_dirty' => 'boolean',
'balance_before' => 'string',
'balance_after' => 'string',
'date' => 'datetime',
'amount' => 'string',
'foreign_amount' => 'string',
];
protected $fillable

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Carbon\Carbon;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
@@ -37,6 +38,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;
/**
@@ -54,7 +56,7 @@ class TransactionJournal extends Model
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'date' => 'datetime',
'date' => SeparateTimezoneCaster::class,
'interest_date' => 'date',
'book_date' => 'date',
'process_date' => 'date',
@@ -76,6 +78,7 @@ class TransactionJournal extends Model
'completed',
'order',
'date',
'date_tz',
];
protected $hidden = ['encrypted'];
@@ -88,7 +91,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 +170,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 +245,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;
@@ -37,12 +38,25 @@ class TransactionType extends Model
use ReturnsIntegerIdTrait;
use SoftDeletes;
/** @deprecated */
public const string DEPOSIT = 'Deposit';
/** @deprecated */
public const string INVALID = 'Invalid';
/** @deprecated */
public const string LIABILITY_CREDIT = 'Liability credit';
/** @deprecated */
public const string OPENING_BALANCE = 'Opening balance';
/** @deprecated */
public const string RECONCILIATION = 'Reconciliation';
/** @deprecated */
public const string TRANSFER = 'Transfer';
/** @deprecated */
public const string WITHDRAWAL = 'Withdrawal';
protected $casts
@@ -53,6 +67,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();

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