Compare commits

..

204 Commits

Author SHA1 Message Date
James Cole
ab38bfe4d2 Merge branch 'release/5.4.2' into main 2020-09-24 06:24:29 +02:00
James Cole
dcb3f23078 Update meta data for new release. 2020-09-24 06:22:08 +02:00
James Cole
c0f363cf86 Consistent dates for #3847 2020-09-24 06:09:26 +02:00
James Cole
46ebf3c07c Consistent dates for #3847 2020-09-24 06:06:18 +02:00
James Cole
ed7977a105 Fix count for #3841 2020-09-24 06:00:45 +02:00
James Cole
f093dbb30b Fix #3844 2020-09-23 20:37:31 +02:00
James Cole
620b69f234 Fix #3841 2020-09-23 20:30:42 +02:00
James Cole
a346b6ee29 Bad redirect when editing a not found transaction. 2020-09-23 20:28:20 +02:00
James Cole
ccb511936e Better fix for #3842, thanks @niklas2810 2020-09-23 20:18:30 +02:00
James Cole
9f98289952 Fix #3843 2020-09-23 20:17:21 +02:00
James Cole
e549b74c97 Fix #3842 2020-09-23 20:13:08 +02:00
James Cole
f73af50eb0 Fix #3840 2020-09-23 19:32:46 +02:00
James Cole
5f5346ed71 Partial fix for #3840 2020-09-23 19:29:10 +02:00
James Cole
fc157b2944 Fix #3839 2020-09-23 19:14:50 +02:00
James Cole
cc1a537ecb Meta data for 5.4.2 2020-09-23 19:14:39 +02:00
James Cole
1e5df66c62 Expand readme. 2020-09-23 16:44:39 +02:00
James Cole
e5ea82663e Create .mergify.yml 2020-09-23 07:41:56 +00:00
James Cole
2d6e6f3ec9 Merge pull request #3837 from okaufmann/fix-account-detail
Fixes account notes parsing (Reopen)
2020-09-23 07:40:58 +00:00
James Cole
a822c9f833 Merge branch 'develop' into fix-account-detail 2020-09-23 07:40:46 +00:00
James Cole
0b8b9cb280 Update version. 2020-09-23 06:18:59 +02:00
James Cole
29b8cf936e Add mergify config 2020-09-23 06:18:49 +02:00
James Cole
b288d6b0eb Fix #3828 2020-09-23 06:18:43 +02:00
James Cole
8a2d5b12c3 Instructions for one-time donations. 2020-09-23 05:56:49 +02:00
James Cole
a7dc9e201f Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2020-09-23 05:50:51 +02:00
James Cole
c686f16a93 Merge pull request #3831 from okaufmann/main
Fixes `Unknown column 'notes' in 'where clause'` when using Rules
2020-09-23 03:49:50 +00:00
Oliver Kaufmann
58245d85b3 fix account notes 2020-09-23 02:18:53 +02:00
Oliver Kaufmann
f564ef5195 fix notes filter query 2020-09-22 23:48:36 +02:00
James Cole
18c24a7251 Fix #3827 2020-09-22 18:27:21 +02:00
James Cole
d97fd73ce5 Make sure notes always have a value, even when null #3823 2020-09-22 16:19:51 +02:00
James Cole
1624abd9ed Merge pull request #3822 from achembarpu/patch-1
Fix web app manifest
2020-09-22 05:15:50 +00:00
Arvind Chembarpu
14f22009ed Update manifest link 2020-09-21 21:25:57 +02:00
Arvind Chembarpu
86cd3c0c38 Do not force portrait mode
For easier use on larger devices like tablets
2020-09-21 21:24:32 +02:00
Arvind Chembarpu
855fe1235a Rename site.webmanifest to manifest.webmanifest
As per Web App Manifest spec - https://www.w3.org/TR/appmanifest/
2020-09-21 21:24:03 +02:00
Arvind Chembarpu
198a0f7011 Delete manifest.json 2020-09-21 21:21:32 +02:00
James Cole
2b16d73e65 Remove LDAP limit from 2FA 2020-09-21 20:40:47 +02:00
James Cole
03f68426e5 Merge tag '5.4.1' into develop
5.4.1
2020-09-21 19:52:27 +02:00
James Cole
229479b7ed Merge branch 'release/5.4.1' into main 2020-09-21 19:52:26 +02:00
James Cole
0f742aa040 Update changelog and meta files for 5.4.1 2020-09-21 18:28:34 +02:00
James Cole
e5ac6a3a1d Add some debug info. 2020-09-21 16:08:41 +02:00
James Cole
d05fb4472c Fix count reference. 2020-09-21 15:56:50 +02:00
James Cole
5161971373 Different call to count() 2020-09-21 15:45:51 +02:00
James Cole
2a5841c631 Temp debug information. 2020-09-21 15:39:14 +02:00
James Cole
766abd3336 Extra info for debug screen. 2020-09-21 15:39:03 +02:00
James Cole
b58cde5431 Add some debug info. 2020-09-21 15:30:54 +02:00
James Cole
9964cf11a4 Fix #3809 2020-09-21 15:20:14 +02:00
James Cole
7f9c8fa133 Merge tag '5.4.0' into develop
5.4.0
2020-09-21 13:21:42 +02:00
James Cole
10057f05a5 Merge branch 'release/5.4.0' into main 2020-09-21 13:21:41 +02:00
James Cole
fc61b20f6d Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2020-09-21 12:47:48 +02:00
James Cole
40e7f019dd Meta data for new release, 5.4.0. 2020-09-21 12:47:40 +02:00
James Cole
2f32ddce6b Merge pull request #3814 from firefly-iii/dependabot/composer/develop/phpstan/phpstan-0.12.43
Bump phpstan/phpstan from 0.12.42 to 0.12.43
2020-09-21 07:52:53 +00:00
James Cole
459015c01e Merge pull request #3813 from firefly-iii/dependabot/composer/develop/doctrine/dbal-2.11.0
Bump doctrine/dbal from 2.10.4 to 2.11.0
2020-09-21 07:52:41 +00:00
dependabot[bot]
b669f96036 Bump phpstan/phpstan from 0.12.42 to 0.12.43
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 0.12.42 to 0.12.43.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Commits](https://github.com/phpstan/phpstan/compare/0.12.42...0.12.43)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-21 05:19:54 +00:00
dependabot[bot]
4524d1cfe3 Bump doctrine/dbal from 2.10.4 to 2.11.0
Bumps [doctrine/dbal](https://github.com/doctrine/dbal) from 2.10.4 to 2.11.0.
- [Release notes](https://github.com/doctrine/dbal/releases)
- [Commits](https://github.com/doctrine/dbal/compare/2.10.4...2.11.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-21 05:17:28 +00:00
James Cole
651029e284 Don't use isset. 2020-09-20 16:18:48 +02:00
James Cole
cbeb2675fd Fix second issue in #3809 2020-09-20 16:16:49 +02:00
James Cole
7a80caf26b Fix #3790 2020-09-18 16:14:17 +02:00
James Cole
f3eaf1dd4c Code for #3790 2020-09-18 16:07:59 +02:00
James Cole
af88e91a48 Update attributes 2020-09-18 12:25:41 +02:00
James Cole
706cb47065 Code changes for v540 2020-09-18 12:16:47 +02:00
James Cole
0d72aa9673 Default is MySQL 2020-09-18 09:46:22 +02:00
James Cole
039e654ef1 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2020-09-14 20:01:47 +02:00
James Cole
1c3fe93d59 Recognize a year. 2020-09-14 20:01:33 +02:00
James Cole
5ef45fd4a3 Merge pull request #3794 from firefly-iii/dependabot/composer/develop/league/commonmark-1.5.5
Bump league/commonmark from 1.5.4 to 1.5.5
2020-09-14 07:24:15 +00:00
James Cole
db700d6b80 Merge pull request #3793 from firefly-iii/dependabot/npm_and_yarn/develop/uiv-0.37.0
Bump uiv from 0.36.1 to 0.37.0
2020-09-14 07:16:35 +00:00
dependabot[bot]
7032bdc169 Bump league/commonmark from 1.5.4 to 1.5.5
Bumps [league/commonmark](https://github.com/thephpleague/commonmark) from 1.5.4 to 1.5.5.
- [Release notes](https://github.com/thephpleague/commonmark/releases)
- [Changelog](https://github.com/thephpleague/commonmark/blob/latest/CHANGELOG-1.x.md)
- [Commits](https://github.com/thephpleague/commonmark/compare/1.5.4...1.5.5)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-14 06:28:32 +00:00
dependabot[bot]
1dde09c738 Bump uiv from 0.36.1 to 0.37.0
Bumps [uiv](https://github.com/wxsms/uiv) from 0.36.1 to 0.37.0.
- [Release notes](https://github.com/wxsms/uiv/releases)
- [Commits](https://github.com/wxsms/uiv/compare/v0.36.1...v0.37.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-14 06:26:19 +00:00
James Cole
a54ca0141a Merge tag '5.4.0-beta.1' into develop
5.4.0-beta.1
2020-09-13 07:48:50 +02:00
James Cole
a553bfd142 Merge branch 'release/5.4.0-beta.1' into main 2020-09-13 07:48:47 +02:00
James Cole
ed01551ad4 Update composer. 2020-09-13 07:41:29 +02:00
James Cole
91473332b4 Update and rebuild JS. 2020-09-13 07:39:19 +02:00
James Cole
890de05394 Update localised JS 2020-09-13 07:38:31 +02:00
James Cole
29445e4ffb Fix #3695 2020-09-12 06:28:23 +02:00
James Cole
098eac8bab Fix #3765 2020-09-11 21:06:10 +02:00
James Cole
e2ad5c60c6 Fix #3761 2020-09-11 20:37:50 +02:00
James Cole
34e2595a3d Make sure you can't set negative amounts. 2020-09-11 20:29:30 +02:00
James Cole
284222c2ee Better call to date. 2020-09-11 07:12:33 +02:00
James Cole
0b308cb5f2 Fix #3791 2020-09-11 07:12:11 +02:00
James Cole
22dc03f32c Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2020-09-11 07:11:43 +02:00
James Cole
c2ff26b515 Fix lowercase 2020-09-11 07:11:37 +02:00
James Cole
402351a6b7 Fix #3789 2020-09-10 19:16:03 +02:00
James Cole
562be457ec Add more types. 2020-09-10 14:53:35 +02:00
James Cole
600e98cf47 Add hashtag 2020-09-10 14:50:49 +02:00
James Cole
bded065d42 Support date 2020-09-10 14:50:22 +02:00
James Cole
a526007957 Add URL compatibility 2020-09-10 14:48:02 +02:00
James Cole
c0af49f336 Fix missing search node type. 2020-09-09 06:10:00 +02:00
James Cole
ff6c0c26e0 Fix #3745 2020-09-07 13:03:10 +02:00
James Cole
a7af8d2195 Fix language issues #3772 2020-09-07 12:56:51 +02:00
James Cole
f5871898bd Fix #3768 2020-09-07 12:49:56 +02:00
James Cole
df8f23cba3 Merge pull request #3781 from firefly-iii/dependabot/composer/develop/nunomaduro/larastan-0.6.4
Bump nunomaduro/larastan from 0.6.2 to 0.6.4
2020-09-07 07:03:08 +00:00
James Cole
d9bc23725b Merge pull request #3782 from firefly-iii/dependabot/npm_and_yarn/frontend/develop/sass-loader-10.0.2
Bump sass-loader from 10.0.1 to 10.0.2 in /frontend
2020-09-07 07:02:01 +00:00
James Cole
a68ab28f00 Merge pull request #3778 from firefly-iii/dependabot/composer/develop/phpstan/phpstan-0.12.42
Bump phpstan/phpstan from 0.12.40 to 0.12.42
2020-09-07 07:01:11 +00:00
dependabot[bot]
676c282f11 Bump phpstan/phpstan from 0.12.40 to 0.12.42
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 0.12.40 to 0.12.42.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Commits](https://github.com/phpstan/phpstan/compare/0.12.40...0.12.42)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-07 06:46:22 +00:00
dependabot[bot]
f99731e90b Bump nunomaduro/larastan from 0.6.2 to 0.6.4
Bumps [nunomaduro/larastan](https://github.com/nunomaduro/larastan) from 0.6.2 to 0.6.4.
- [Release notes](https://github.com/nunomaduro/larastan/releases)
- [Changelog](https://github.com/nunomaduro/larastan/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nunomaduro/larastan/compare/v0.6.2...v0.6.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-07 06:45:43 +00:00
James Cole
302ece8ffc Merge pull request #3779 from firefly-iii/dependabot/composer/develop/predis/predis-1.1.4
Bump predis/predis from 1.1.3 to 1.1.4
2020-09-07 06:17:29 +00:00
James Cole
47100a8e51 Merge pull request #3780 from firefly-iii/dependabot/composer/develop/league/csv-9.6.1
Bump league/csv from 9.6.0 to 9.6.1
2020-09-07 06:17:02 +00:00
dependabot[bot]
7e59d8cb75 Bump sass-loader from 10.0.1 to 10.0.2 in /frontend
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 10.0.1 to 10.0.2.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v10.0.1...v10.0.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-07 06:16:10 +00:00
James Cole
7eaa00a5ad Merge pull request #3777 from firefly-iii/dependabot/composer/develop/vimeo/psalm-3.15
Bump vimeo/psalm from 3.14.2 to 3.15
2020-09-07 06:13:29 +00:00
dependabot[bot]
0713cc55b6 Bump league/csv from 9.6.0 to 9.6.1
Bumps [league/csv](https://github.com/thephpleague/csv) from 9.6.0 to 9.6.1.
- [Release notes](https://github.com/thephpleague/csv/releases)
- [Changelog](https://github.com/thephpleague/csv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/thephpleague/csv/compare/9.6.0...9.6.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-07 06:10:29 +00:00
James Cole
8329d68b22 Merge pull request #3776 from firefly-iii/dependabot/composer/develop/laravel/framework-7.27.0
Bump laravel/framework from 7.26.1 to 7.27.0
2020-09-07 06:10:23 +00:00
dependabot[bot]
3e05cf7306 Bump predis/predis from 1.1.3 to 1.1.4
Bumps [predis/predis](https://github.com/predis/predis) from 1.1.3 to 1.1.4.
- [Release notes](https://github.com/predis/predis/releases)
- [Changelog](https://github.com/predis/predis/blob/main/CHANGELOG.md)
- [Commits](https://github.com/predis/predis/compare/v1.1.3...v1.1.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-07 06:08:32 +00:00
James Cole
8c116e1bc5 Merge pull request #3775 from firefly-iii/dependabot/composer/develop/doctrine/dbal-2.10.3
Bump doctrine/dbal from 2.10.2 to 2.10.3
2020-09-07 06:06:37 +00:00
dependabot[bot]
7d61986025 Bump vimeo/psalm from 3.14.2 to 3.15
Bumps [vimeo/psalm](https://github.com/vimeo/psalm) from 3.14.2 to 3.15.
- [Release notes](https://github.com/vimeo/psalm/releases)
- [Commits](https://github.com/vimeo/psalm/compare/3.14.2...3.15)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-07 06:04:54 +00:00
dependabot[bot]
abff2a0a8f Bump laravel/framework from 7.26.1 to 7.27.0
Bumps [laravel/framework](https://github.com/laravel/framework) from 7.26.1 to 7.27.0.
- [Release notes](https://github.com/laravel/framework/releases)
- [Changelog](https://github.com/laravel/framework/blob/7.x/CHANGELOG-7.x.md)
- [Commits](https://github.com/laravel/framework/compare/v7.26.1...v7.27.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-07 06:03:09 +00:00
dependabot[bot]
65499bb9ff Bump doctrine/dbal from 2.10.2 to 2.10.3
Bumps [doctrine/dbal](https://github.com/doctrine/dbal) from 2.10.2 to 2.10.3.
- [Release notes](https://github.com/doctrine/dbal/releases)
- [Commits](https://github.com/doctrine/dbal/compare/2.10.2...2.10.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-07 06:01:41 +00:00
James Cole
6f219d1932 Short header 2020-09-06 07:27:46 +02:00
James Cole
9d35048fb9 Fix #3770 2020-09-06 07:27:36 +02:00
James Cole
af58c30249 Merge branch 'main' into develop 2020-09-05 07:37:40 +02:00
James Cole
366744ed69 Catch bad SSL config 2020-09-05 07:37:18 +02:00
James Cole
d809b77154 Catch errors 2020-09-05 07:37:10 +02:00
James Cole
03cec69af3 Fix cash account display. 2020-09-03 06:55:00 +02:00
James Cole
9ae41b1645 Link in clone. 2020-09-03 06:52:09 +02:00
James Cole
b0f18a0419 Consistent end dates for budget limits. 2020-09-03 06:49:22 +02:00
James Cole
5648a9197d Merge pull request #3758 from firefly-iii/dependabot/composer/symfony/http-kernel-5.1.5
Bump symfony/http-kernel from 5.1.3 to 5.1.5
2020-09-03 04:35:01 +00:00
James Cole
f0fe8bf5c7 Fix #3759 2020-09-03 06:34:48 +02:00
dependabot[bot]
8a690afc46 Bump symfony/http-kernel from 5.1.3 to 5.1.5
Bumps [symfony/http-kernel](https://github.com/symfony/http-kernel) from 5.1.3 to 5.1.5.
- [Release notes](https://github.com/symfony/http-kernel/releases)
- [Changelog](https://github.com/symfony/http-kernel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/symfony/http-kernel/compare/v5.1.3...v5.1.5)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-02 18:56:48 +00:00
James Cole
2bb6c48278 Add some debug logging. 2020-09-02 06:46:51 +02:00
James Cole
6eb661df19 Fix #3755 2020-09-02 06:40:05 +02:00
James Cole
8e578e1058 Fix #3755 2020-09-02 06:39:56 +02:00
James Cole
4f7683b1fd Merge pull request #3751 from firefly-iii/dependabot/composer/develop/barryvdh/laravel-debugbar-3.4.2
Bump barryvdh/laravel-debugbar from 3.4.1 to 3.4.2
2020-08-31 07:55:32 +00:00
James Cole
656456b9d9 Merge pull request #3752 from firefly-iii/dependabot/npm_and_yarn/frontend/develop/sass-loader-10.0.1
Bump sass-loader from 9.0.3 to 10.0.1 in /frontend
2020-08-31 07:14:07 +00:00
dependabot[bot]
8c1daa9eed Bump sass-loader from 9.0.3 to 10.0.1 in /frontend
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 9.0.3 to 10.0.1.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v9.0.3...v10.0.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-31 06:46:38 +00:00
dependabot[bot]
f146205208 Bump barryvdh/laravel-debugbar from 3.4.1 to 3.4.2
Bumps [barryvdh/laravel-debugbar](https://github.com/barryvdh/laravel-debugbar) from 3.4.1 to 3.4.2.
- [Release notes](https://github.com/barryvdh/laravel-debugbar/releases)
- [Changelog](https://github.com/barryvdh/laravel-debugbar/blob/master/changelog.md)
- [Commits](https://github.com/barryvdh/laravel-debugbar/compare/v3.4.1...v3.4.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-31 06:25:22 +00:00
James Cole
dce3c333da Merge pull request #3748 from psychowood/develop
Wrong query params in autocomplete
2020-08-31 04:25:38 +00:00
psychowood
b0d67f1637 Typos in b63e8d60bb 2020-08-31 00:27:55 +02:00
James Cole
bc913d0e9c Merge pull request #3746 from GrayStrider/patch-1
Fix anchor link
2020-08-29 11:41:14 +00:00
GrayStrider
415ef57458 Fix anchor link 2020-08-29 22:33:10 +11:00
James Cole
278ddf27bd Merge tag '5.4.0-alpha.3' into develop
5.4.0-alpha.3
2020-08-29 13:10:35 +02:00
James Cole
3566305c27 Merge branch 'release/5.4.0-alpha.3' into main 2020-08-29 13:10:33 +02:00
James Cole
b41b03e8f0 Update meta files for new alpha release. 2020-08-29 13:08:26 +02:00
James Cole
e5642b59d7 Update meta files 2020-08-29 12:10:13 +02:00
James Cole
e159de9a6a Expand warning with better text 2020-08-29 07:18:41 +02:00
James Cole
2a02e8a790 Clarify + warning 2020-08-28 22:03:27 +02:00
James Cole
798c73394d Code for #3712 2020-08-28 21:58:03 +02:00
James Cole
7f48043505 Fix #3729 2020-08-28 21:29:47 +02:00
James Cole
82b49a9e51 Add telemetry to get insight in update preferences. 2020-08-28 21:04:29 +02:00
James Cole
56ea680e46 Add telemetry to get insight into updates 2020-08-28 21:03:23 +02:00
James Cole
70f3d13626 Update translations 2020-08-28 16:36:03 +02:00
James Cole
093f34b7a8 Account search fixed. 2020-08-28 11:24:55 +02:00
James Cole
63794cab07 Fix bill from rule 2020-08-28 06:10:31 +02:00
James Cole
ed17600f79 Fix currency search. 2020-08-28 06:09:04 +02:00
James Cole
778af9f4e2 Fix #3735 2020-08-28 05:51:02 +02:00
James Cole
8c97754b64 Expand rule/search combination. 2020-08-27 20:22:52 +02:00
James Cole
2393344978 Correct params for search 2020-08-27 07:20:58 +02:00
James Cole
4c81a46af6 New features for rules in search. 2020-08-27 07:12:44 +02:00
James Cole
7df084dd3c Other way to test rule for #3735 2020-08-27 06:35:53 +02:00
James Cole
08aa61a2bf Basic bread crumb code to replace obsolete package 2020-08-27 06:19:41 +02:00
James Cole
a16ac479d5 Fix #3735 2020-08-27 06:19:16 +02:00
James Cole
483e7256f7 Disable layout caches. 2020-08-27 06:18:47 +02:00
James Cole
412b169e3a Kill 2FA 2020-08-26 20:37:30 +02:00
James Cole
bfcd743efa Fix copy/paste error [skip ci] 2020-08-26 20:22:11 +02:00
James Cole
f123c28540 Fix HTML 2020-08-26 20:21:12 +02:00
James Cole
56f9ce333c Updated strings. 2020-08-26 20:19:00 +02:00
James Cole
cd156d6991 Fix #3740 2020-08-26 20:18:27 +02:00
James Cole
c985683ee3 Fixed #3737 2020-08-26 19:41:50 +02:00
James Cole
59ab0c9f0d Fix #3739 2020-08-26 18:18:53 +02:00
James Cole
a24b6711d1 Clean up. 2020-08-26 18:06:47 +02:00
James Cole
7f7d7be646 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2020-08-26 18:06:24 +02:00
James Cole
2f2a02834b Fix #3736 2020-08-26 18:06:14 +02:00
James Cole
e2a3aa12a8 Merge pull request #3733 from glmdev/glmdev-ldap-filter
Add config option to specify custom LDAP filter
2020-08-26 14:53:11 +00:00
root
a4f70794a2 LdapFilterScope - make ldap filter config call coerce string 2020-08-26 14:46:01 +00:00
root
0ee3941b43 This adds support for the ADLDAP_AUTH_FILTER env var, and the
ldap_auth.custom_filter config option. These are optional.

If provided, the custom filter will be applied to the LDAP query
using the FireflyIII\Scopes\LdapFilterScope class.

This allows the integrator to specify a custom LDAP filter.
2020-08-26 14:07:47 +00:00
James Cole
4e05ce4c35 Wrong field when updating limit 2020-08-25 06:21:44 +02:00
James Cole
c5fa48ca46 Extra text 2020-08-25 06:12:43 +02:00
James Cole
20e39b2e12 Missing default variable. 2020-08-25 06:10:52 +02:00
James Cole
d28394c5d9 Update changelog. 2020-08-24 18:31:26 +02:00
James Cole
86fff1b61c Update packages. 2020-08-24 18:31:18 +02:00
James Cole
71ef5abea5 Remove references to unused code. 2020-08-24 18:31:10 +02:00
James Cole
6b83313ce8 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2020-08-24 18:05:27 +02:00
James Cole
2fe93656ee Delete unused files. 2020-08-24 18:05:05 +02:00
James Cole
ec8003245f Clean up configuration file. 2020-08-24 18:00:49 +02:00
James Cole
8f632cb57b Merge pull request #3725 from firefly-iii/dependabot/npm_and_yarn/frontend/develop/axios-0.20.0
Bump axios from 0.19.2 to 0.20.0 in /frontend
2020-08-24 08:07:53 +00:00
dependabot[bot]
d7b9129c33 Bump axios from 0.19.2 to 0.20.0 in /frontend
Bumps [axios](https://github.com/axios/axios) from 0.19.2 to 0.20.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.19.2...v0.20.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-24 08:04:30 +00:00
James Cole
82efe530ea Merge pull request #3724 from firefly-iii/dependabot/npm_and_yarn/frontend/develop/vue-chartjs-3.5.1
Bump vue-chartjs from 3.5.0 to 3.5.1 in /frontend
2020-08-24 08:03:39 +00:00
James Cole
b3805585a7 Merge pull request #3726 from firefly-iii/dependabot/npm_and_yarn/develop/axios-0.20.0
Bump axios from 0.19.2 to 0.20.0
2020-08-24 08:03:03 +00:00
James Cole
04b84fd0dc Merge pull request #3727 from firefly-iii/dependabot/composer/develop/vimeo/psalm-3.14.2
Bump vimeo/psalm from 3.14.1 to 3.14.2
2020-08-24 07:58:56 +00:00
dependabot[bot]
2ac405b2d0 Bump vimeo/psalm from 3.14.1 to 3.14.2
Bumps [vimeo/psalm](https://github.com/vimeo/psalm) from 3.14.1 to 3.14.2.
- [Release notes](https://github.com/vimeo/psalm/releases)
- [Commits](https://github.com/vimeo/psalm/compare/3.14.1...3.14.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-24 07:14:39 +00:00
dependabot[bot]
eb8571a508 Bump axios from 0.19.2 to 0.20.0
Bumps [axios](https://github.com/axios/axios) from 0.19.2 to 0.20.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.19.2...v0.20.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-24 07:03:01 +00:00
dependabot[bot]
f7e75e9b8a Bump vue-chartjs from 3.5.0 to 3.5.1 in /frontend
Bumps [vue-chartjs](https://github.com/apertureless/vue-chartjs) from 3.5.0 to 3.5.1.
- [Release notes](https://github.com/apertureless/vue-chartjs/releases)
- [Changelog](https://github.com/apertureless/vue-chartjs/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/apertureless/vue-chartjs/compare/v3.5.0...v3.5.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-24 06:43:49 +00:00
James Cole
0fd7dabbc1 Restore missing functions. 2020-08-24 07:38:33 +02:00
James Cole
9123454545 Migrate to new rule engine. 2020-08-24 07:31:50 +02:00
James Cole
3141ec0406 Expand search. 2020-08-24 07:03:17 +02:00
James Cole
4bf86500bd Fix tag search 2020-08-23 18:48:40 +02:00
James Cole
bee54146bf Fix #3721 2020-08-23 18:48:24 +02:00
James Cole
28698cc769 Make sure limits exist in rule engine. 2020-08-23 17:00:47 +02:00
James Cole
41f2339c8c Switch to new rule engine on command line. 2020-08-23 16:37:08 +02:00
James Cole
ce34e097a2 Add new rule engine to API commands. 2020-08-23 16:26:39 +02:00
James Cole
fecc9f7659 Fix actions and associated tests. 2020-08-23 16:12:16 +02:00
James Cole
139b3ffab4 Test more rule actions. 2020-08-23 09:21:50 +02:00
James Cole
febe60b3d1 Test more actions in new format. 2020-08-23 08:51:58 +02:00
James Cole
fc519c41bc Update conversion actions. 2020-08-23 08:03:28 +02:00
James Cole
6e074d9b8b Start testing new rule actions. 2020-08-23 07:42:14 +02:00
James Cole
d89a4d8a54 Fix #3720 2020-08-23 05:52:59 +02:00
James Cole
a504617425 Mark all triggers as deprecated. 2020-08-22 20:07:39 +02:00
James Cole
0c0fef6e84 Fix #3715 2020-08-22 19:48:00 +02:00
James Cole
1dfda62125 Fix #3715 2020-08-22 19:46:08 +02:00
James Cole
5e0c0e25f2 Fix #3716 2020-08-22 19:27:16 +02:00
James Cole
07220eb167 Update rule actions. 2020-08-22 17:18:15 +02:00
James Cole
216a0a186c Implement first version of the new rule engine. 2020-08-22 16:55:54 +02:00
James Cole
14df37712c Fix date searches. 2020-08-22 13:02:33 +02:00
James Cole
ab5a146277 Rename class. 2020-08-22 13:01:37 +02:00
James Cole
24a373abf4 Fix search 2020-08-22 12:25:00 +02:00
James Cole
ffca935ced Expand search. 2020-08-22 12:24:01 +02:00
James Cole
d69934ca8f Rule engine and search engine now use the same operators; making them interchangeable. 2020-08-21 08:23:44 +02:00
James Cole
3081911eae Merge tag '5.4.0-alpha.2' into develop
5.4.0-alpha.2
2020-08-21 08:04:50 +02:00
438 changed files with 24820 additions and 24882 deletions

View File

@@ -9,6 +9,7 @@ parameters:
- '#is not allowed to extend#'
- '#is neither abstract nor final#'
- '#Control structures using switch should not be used\.#'
- '#has a nullable return type declaration#'
paths:
- ../app
- ../database
@@ -16,4 +17,4 @@ parameters:
- ../bootstrap/app.php
# The level 8 is the highest level. original was 5
level: 0
level: 5

View File

@@ -9,7 +9,6 @@ fi_FI
fr_FR
hu_HU
it_IT
lt_LT
nb_NO
nl_NL
pl_PL

View File

@@ -10,7 +10,6 @@ APP_DEBUG=false
SITE_OWNER=mail@example.com
# The encryption key for your sessions. Keep this very secure.
# If you generate a new one all existing attachments must be considered LOST.
# Change it to a string of exactly 32 chars or use something like `php artisan key:generate` to generate it.
# If you use Docker or similar, you can set this variable from a file by using APP_KEY_FILE
APP_KEY=SomeRandomStringOf32CharsExactly
@@ -143,6 +142,7 @@ SPARKPOST_SECRET=
# Firefly III can send you the following messages
SEND_REGISTRATION_MAIL=true
SEND_ERROR_MESSAGE=true
SEND_LOGIN_NEW_IP_WARNING=true
# These messages contain (sensitive) transaction information:
SEND_REPORT_JOURNALS=true

3
.gitattributes vendored
View File

@@ -3,3 +3,6 @@
*.scss linguist-vendored
*.js linguist-vendored
CHANGELOG.md export-ignore
/tests export-ignore
/phpunit.xml export-ignore
/.ci export-ignore

7
.github/.mergify.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
pull_request_rules:
- name: PR on main is never approved.
conditions:
- base=main
actions:
close:
message: Please reopen this PR on the `develop` branch. Thank you.

1
.github/funding.yml vendored
View File

@@ -2,4 +2,3 @@
github: jc5
patreon: JC5
custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=L62W7DVD5ETPC&source=url

View File

@@ -145,6 +145,9 @@ class AvailableBudgetController extends Controller
public function store(AvailableBudgetRequest $request): JsonResponse
{
$data = $request->getAll();
$data['start']->startOfDay();
$data['end']->endOfDay();
/** @var TransactionCurrencyFactory $factory */
$factory = app(TransactionCurrencyFactory::class);
$currency = $factory->find($data['currency_id'], $data['currency_code']);

View File

@@ -54,6 +54,15 @@ abstract class Controller extends BaseController
{
// get global parameters
$this->parameters = $this->getParameters();
$this->middleware(
function ($request, $next) {
if (auth()->check()) {
$language = app('steam')->getLanguage();
app()->setLocale($language);
}
return $next($request);
});
}
/**

View File

@@ -28,21 +28,19 @@ use FireflyIII\Api\V1\Requests\RuleTestRequest;
use FireflyIII\Api\V1\Requests\RuleTriggerRequest;
use FireflyIII\Api\V1\Requests\RuleUpdateRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Rule;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\TransactionRules\Engine\RuleEngine;
use FireflyIII\TransactionRules\TransactionMatcher;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use FireflyIII\Transformers\RuleTransformer;
use FireflyIII\Transformers\TransactionGroupTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item;
use Log;
/**
* Class RuleController
@@ -215,29 +213,37 @@ class RuleController extends Controller
* @param RuleTestRequest $request
* @param Rule $rule
*
* @throws FireflyException
* @return JsonResponse
* @throws FireflyException
*/
public function testRule(RuleTestRequest $request, Rule $rule): JsonResponse
{
$pageSize = (int) app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data;
$parameters = $request->getTestParameters();
/** @var Rule $rule */
Log::debug(sprintf('Now testing rule #%d, "%s"', $rule->id, $rule->title));
/** @var TransactionMatcher $matcher */
$matcher = app(TransactionMatcher::class);
// set all parameters:
$matcher->setRule($rule);
$matcher->setStartDate($parameters['start_date']);
$matcher->setEndDate($parameters['end_date']);
$matcher->setSearchLimit($parameters['search_limit']);
$matcher->setTriggeredLimit($parameters['trigger_limit']);
$matcher->setAccounts($parameters['accounts']);
$matchingTransactions = $matcher->findTransactionsByRule();
$count = count($matchingTransactions);
$transactions = array_slice($matchingTransactions, ($parameters['page'] - 1) * $pageSize, $pageSize);
$paginator = new LengthAwarePaginator($transactions, $count, $pageSize, $this->parameters->get('page'));
/** @var RuleEngineInterface $ruleEngine */
$ruleEngine = app(RuleEngineInterface::class);
$ruleEngine->setRules(new Collection([$rule]));
// overrule the rule(s) if necessary.
if (array_key_exists('start', $parameters) && null !== $parameters['start']) {
// add a range:
$ruleEngine->addOperator(['type' => 'date_after', 'value' => $parameters['start']->format('Y-m-d')]);
}
if (array_key_exists('end', $parameters) && null !== $parameters['end']) {
// add a range:
$ruleEngine->addOperator(['type' => 'date_before', 'value' => $parameters['end']->format('Y-m-d')]);
}
if (array_key_exists('accounts', $parameters) && '' !== $parameters['accounts']) {
$ruleEngine->addOperator(['type' => 'account_id', 'value' => $parameters['accounts']]);
}
// file the rule(s)
$transactions = $ruleEngine->find();
$count = $transactions->count();
$paginator = new LengthAwarePaginator($transactions, $count, 31337, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.rules.test', [$rule->id]) . $this->buildParams());
// resulting list is presented as JSON thing.
@@ -246,7 +252,7 @@ class RuleController extends Controller
$transformer = app(TransactionGroupTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new FractalCollection($matchingTransactions, $transformer, 'transactions');
$resource = new FractalCollection($transactions, $transformer, 'transactions');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
@@ -265,28 +271,27 @@ class RuleController extends Controller
// Get parameters specified by the user
$parameters = $request->getTriggerParameters();
/** @var RuleEngine $ruleEngine */
$ruleEngine = app(RuleEngine::class);
$ruleEngine->setUser(auth()->user());
/** @var RuleEngineInterface $ruleEngine */
$ruleEngine = app(RuleEngineInterface::class);
$ruleEngine->setRules(new Collection([$rule]));
$rules = [$rule->id];
$ruleEngine->setRulesToApply($rules);
$ruleEngine->setTriggerMode(RuleEngine::TRIGGER_STORE);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($parameters['accounts']);
$collector->setRange($parameters['start_date'], $parameters['end_date']);
$journals = $collector->getExtractedJournals();
/** @var array $journal */
foreach ($journals as $journal) {
Log::debug('Start of new journal.');
$ruleEngine->processJournalArray($journal);
Log::debug('Done with all rules for this group + done with journal.');
// overrule the rule(s) if necessary.
if (array_key_exists('start', $parameters) && null !== $parameters['start']) {
// add a range:
$ruleEngine->addOperator(['type' => 'date_after', 'value' => $parameters['start']->format('Y-m-d')]);
}
if (array_key_exists('end', $parameters) && null !== $parameters['end']) {
// add a range:
$ruleEngine->addOperator(['type' => 'date_before', 'value' => $parameters['end']->format('Y-m-d')]);
}
if (array_key_exists('accounts', $parameters) && '' !== $parameters['accounts']) {
$ruleEngine->addOperator(['type' => 'account_id', 'value' => $parameters['accounts']]);
}
// file the rule(s)
$ruleEngine->fire();
return response()->json([], 204);
}

View File

@@ -28,13 +28,10 @@ use FireflyIII\Api\V1\Requests\RuleGroupRequest;
use FireflyIII\Api\V1\Requests\RuleGroupTestRequest;
use FireflyIII\Api\V1\Requests\RuleGroupTriggerRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\TransactionRules\Engine\RuleEngine;
use FireflyIII\TransactionRules\TransactionMatcher;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use FireflyIII\Transformers\RuleGroupTransformer;
use FireflyIII\Transformers\RuleTransformer;
use FireflyIII\Transformers\TransactionGroupTransformer;
@@ -45,7 +42,6 @@ use Illuminate\Support\Collection;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item;
use Log;
/**
* Class RuleGroupController
@@ -248,54 +244,51 @@ class RuleGroupController extends Controller
* @param RuleGroupTestRequest $request
* @param RuleGroup $group
*
* @return JsonResponse
* @throws FireflyException
*
* @return JsonResponse
*/
public function testGroup(RuleGroupTestRequest $request, RuleGroup $group): JsonResponse
{
$pageSize = (int) app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data;
Log::debug('Now in testGroup()');
/** @var Collection $rules */
$rules = $this->ruleGroupRepository->getActiveRules($group);
if (0 === $rules->count()) {
throw new FireflyException('200023: No rules in this rule group.');
}
$parameters = $request->getTestParameters();
$matchingTransactions = [];
$parameters = $request->getTestParameters();
Log::debug(sprintf('Going to test %d rules', $rules->count()));
/** @var Rule $rule */
foreach ($rules as $rule) {
Log::debug(sprintf('Now testing rule #%d, "%s"', $rule->id, $rule->title));
/** @var TransactionMatcher $matcher */
$matcher = app(TransactionMatcher::class);
// set all parameters:
$matcher->setRule($rule);
$matcher->setStartDate($parameters['start_date']);
$matcher->setEndDate($parameters['end_date']);
$matcher->setSearchLimit($parameters['search_limit']);
$matcher->setTriggeredLimit($parameters['trigger_limit']);
$matcher->setAccounts($parameters['accounts']);
/** @var RuleEngineInterface $ruleEngine */
$ruleEngine = app(RuleEngineInterface::class);
$ruleEngine->setRules($rules);
$result = $matcher->findTransactionsByRule();
/** @noinspection AdditionOperationOnArraysInspection */
$matchingTransactions = $result + $matchingTransactions;
// overrule the rule(s) if necessary.
if (array_key_exists('start', $parameters) && null !== $parameters['start']) {
// add a range:
$ruleEngine->addOperator(['type' => 'date_after', 'value' => $parameters['start']->format('Y-m-d')]);
}
// make paginator out of results.
$count = count($matchingTransactions);
$transactions = array_slice($matchingTransactions, ($parameters['page'] - 1) * $pageSize, $pageSize);
if (array_key_exists('end', $parameters) && null !== $parameters['end']) {
// add a range:
$ruleEngine->addOperator(['type' => 'date_before', 'value' => $parameters['end']->format('Y-m-d')]);
}
if (array_key_exists('accounts', $parameters) && '' !== $parameters['accounts']) {
$ruleEngine->addOperator(['type' => 'account_id', 'value' => $parameters['accounts']]);
}
// make paginator:
$paginator = new LengthAwarePaginator($transactions, $count, $pageSize, $parameters['page']);
// file the rule(s)
$transactions = $ruleEngine->find();
$count = $transactions->count();
$paginator = new LengthAwarePaginator($transactions, $count, 31337, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.rule_groups.test', [$group->id]) . $this->buildParams());
$manager = $this->getManager();
// resulting list is presented as JSON thing.
$manager = $this->getManager();
/** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new FractalCollection($matchingTransactions, $transformer, 'transactions');
$resource = new FractalCollection($transactions, $transformer, 'transactions');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
@@ -307,40 +300,40 @@ class RuleGroupController extends Controller
* @param RuleGroupTriggerRequest $request
* @param RuleGroup $group
*
* @throws Exception
* @return JsonResponse
* @throws Exception
*/
public function triggerGroup(RuleGroupTriggerRequest $request, RuleGroup $group): JsonResponse
{
/** @var Collection $rules */
$rules = $this->ruleGroupRepository->getActiveRules($group);
if (0 === $rules->count()) {
throw new FireflyException('200023: No rules in this rule group.');
}
// Get parameters specified by the user
$parameters = $request->getTriggerParameters();
/** @var Collection $collection */
$collection = $this->ruleGroupRepository->getActiveRules($group);
$rules = [];
/** @var Rule $item */
foreach ($collection as $item) {
$rules[] = $item->id;
/** @var RuleEngineInterface $ruleEngine */
$ruleEngine = app(RuleEngineInterface::class);
$ruleEngine->setRules($rules);
// overrule the rule(s) if necessary.
if (array_key_exists('start', $parameters) && null !== $parameters['start']) {
// add a range:
$ruleEngine->addOperator(['type' => 'date_after', 'value' => $parameters['start']->format('Y-m-d')]);
}
// start looping.
/** @var RuleEngine $ruleEngine */
$ruleEngine = app(RuleEngine::class);
$ruleEngine->setUser(auth()->user());
$ruleEngine->setRulesToApply($rules);
$ruleEngine->setTriggerMode(RuleEngine::TRIGGER_STORE);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($parameters['accounts']);
$collector->setRange($parameters['start_date'], $parameters['end_date']);
$journals = $collector->getExtractedJournals();
/** @var array $journal */
foreach ($journals as $journal) {
Log::debug('Start of new journal.');
$ruleEngine->processJournalArray($journal);
Log::debug('Done with all rules for this group + done with journal.');
if (array_key_exists('end', $parameters) && null !== $parameters['end']) {
// add a range:
$ruleEngine->addOperator(['type' => 'date_before', 'value' => $parameters['end']->format('Y-m-d')]);
}
if (array_key_exists('accounts', $parameters) && '' !== $parameters['accounts']) {
$ruleEngine->addOperator(['type' => 'account_id', 'value' => $parameters['accounts']]);
}
// file the rule(s)
$ruleEngine->fire();
return response()->json([], 204);
}

View File

@@ -328,7 +328,7 @@ class SummaryController extends Controller
private function getLeftToSpendInfo(Carbon $start, Carbon $end): array
{
$return = [];
$today = new Carbon;
$today = today(config('app.timezone'));
$available = $this->abRepository->getAvailableBudgetWithCurrency($start, $end);
$budgets = $this->budgetRepository->getActiveBudgets();
$spent = $this->opsRepository->sumExpenses($start, $end, null, $budgets);

View File

@@ -50,7 +50,6 @@ use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item;
use Log;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class TransactionController
*/
@@ -59,9 +58,7 @@ class TransactionController extends Controller
use TransactionFilter;
private TransactionGroupRepositoryInterface $groupRepository;
private JournalAPIRepositoryInterface $journalAPIRepository;
private JournalRepositoryInterface $repository;

View File

@@ -40,6 +40,7 @@ use Log;
class RuleGroupTestRequest extends FormRequest
{
use ConvertsDataTypes;
/**
* Authorize logged in users.
*
@@ -57,12 +58,9 @@ class RuleGroupTestRequest extends FormRequest
public function getTestParameters(): array
{
return [
'page' => $this->getPage(),
'start_date' => $this->getDate('start_date'),
'end_date' => $this->getDate('end_date'),
'search_limit' => $this->getSearchLimit(),
'trigger_limit' => $this->getTriggerLimit(),
'accounts' => $this->getAccounts(),
'start' => $this->getDate('start'),
'end' => $this->getDate('end'),
'accounts' => $this->getAccounts(),
];
}
@@ -71,31 +69,20 @@ class RuleGroupTestRequest extends FormRequest
*/
public function rules(): array
{
return [];
return [
'start' => 'date',
'end' => 'date|after:start',
'accounts' => '',
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
];
}
/**
* @return Collection
*/
private function getAccounts(): Collection
private function getAccounts(): string
{
$accountList = '' === (string) $this->query('accounts') ? [] : explode(',', $this->query('accounts'));
$accounts = new Collection;
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
foreach ($accountList as $accountId) {
Log::debug(sprintf('Searching for asset account with id "%s"', $accountId));
$account = $accountRepository->findNull((int) $accountId);
if ($this->validAccount($account)) {
/** @noinspection NullPointerExceptionInspection */
Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name));
$accounts->push($account);
}
}
return $accounts;
return (string) $this->query('accounts');
}
/**
@@ -111,39 +98,4 @@ class RuleGroupTestRequest extends FormRequest
return $result;
}
/**
* @return int
*/
private function getPage(): int
{
return 0 === (int) $this->query('page') ? 1 : (int) $this->query('page');
}
/**
* @return int
*/
private function getSearchLimit(): int
{
return 0 === (int) $this->query('search_limit') ? (int) config('firefly.test-triggers.limit') : (int) $this->query('search_limit');
}
/**
* @return int
*/
private function getTriggerLimit(): int
{
return 0 === (int) $this->query('triggered_limit') ? (int) config('firefly.test-triggers.range') : (int) $this->query('triggered_limit');
}
/**
* @param Account|null $account
*
* @return bool
*/
private function validAccount(?Account $account): bool
{
return null !== $account && AccountType::ASSET === $account->accountType->type;
}
}

View File

@@ -40,6 +40,7 @@ use Log;
class RuleGroupTriggerRequest extends FormRequest
{
use ConvertsDataTypes;
/**
* Authorize logged in users.
*
@@ -57,9 +58,9 @@ class RuleGroupTriggerRequest extends FormRequest
public function getTriggerParameters(): array
{
return [
'start_date' => $this->getDate('start_date'),
'end_date' => $this->getDate('end_date'),
'accounts' => $this->getAccounts(),
'start' => $this->getDate('start'),
'end' => $this->getDate('end'),
'accounts' => $this->getAccounts(),
];
}
@@ -69,33 +70,17 @@ class RuleGroupTriggerRequest extends FormRequest
public function rules(): array
{
return [
'start_date' => 'required|date',
'end_date' => 'required|date|after:start_date',
'start' => 'date',
'end' => 'date|after:start',
];
}
/**
* @return Collection
* @return string
*/
private function getAccounts(): Collection
private function getAccounts(): string
{
$accountList = '' === (string) $this->query('accounts') ? [] : explode(',', $this->query('accounts'));
$accounts = new Collection;
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
foreach ($accountList as $accountId) {
Log::debug(sprintf('Searching for asset account with id "%s"', $accountId));
$account = $accountRepository->findNull((int) $accountId);
if ($this->validAccount($account)) {
/** @noinspection NullPointerExceptionInspection */
Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name));
$accounts->push($account);
}
}
return $accounts;
return (string) $this->query('accounts');
}
/**
@@ -106,19 +91,7 @@ class RuleGroupTriggerRequest extends FormRequest
private function getDate(string $field): ?Carbon
{
/** @var Carbon $result */
$result = null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', $this->query($field));
return $result;
}
/**
* @param Account|null $account
*
* @return bool
*/
private function validAccount(?Account $account): bool
{
return null !== $account && AccountType::ASSET === $account->accountType->type;
return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', $this->query($field));
}
}

View File

@@ -25,6 +25,7 @@ namespace FireflyIII\Api\V1\Requests;
use FireflyIII\Rules\IsBoolean;
use FireflyIII\Support\Request\ConvertsDataTypes;
use FireflyIII\Support\Request\GetRuleConfiguration;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Validator;
use function is_array;
@@ -35,7 +36,8 @@ use function is_array;
*/
class RuleStoreRequest extends FormRequest
{
use ConvertsDataTypes;
use ConvertsDataTypes, GetRuleConfiguration;
/**
* Authorize logged in users.
*
@@ -88,11 +90,11 @@ class RuleStoreRequest extends FormRequest
*/
public function rules(): array
{
$validTriggers = array_keys(config('firefly.rule-triggers'));
$validTriggers = $this->getTriggers();
$validActions = array_keys(config('firefly.rule-actions'));
// some triggers and actions require text:
$contextTriggers = implode(',', config('firefly.context-rule-triggers'));
$contextTriggers = implode(',', $this->getTriggersWithContext());
$contextActions = implode(',', config('firefly.context-rule-actions'));
return [

View File

@@ -26,13 +26,8 @@ namespace FireflyIII\Api\V1\Requests;
use Carbon\Carbon;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Collection;
use Log;
/**
* Class RuleTestRequest
@@ -40,6 +35,7 @@ use Log;
class RuleTestRequest extends FormRequest
{
use ConvertsDataTypes;
/**
* Authorize logged in users.
*
@@ -57,12 +53,11 @@ class RuleTestRequest extends FormRequest
public function getTestParameters(): array
{
return [
'page' => $this->getPage(),
'start_date' => $this->getDate('start_date'),
'end_date' => $this->getDate('end_date'),
'search_limit' => $this->getSearchLimit(),
'trigger_limit' => $this->getTriggerLimit(),
'accounts' => $this->getAccounts(),
'page' => $this->getPage(),
'start' => $this->getDate('start'),
'end' => $this->getDate('end'),
'accounts' => $this->getAccounts(),
];
}
@@ -71,31 +66,20 @@ class RuleTestRequest extends FormRequest
*/
public function rules(): array
{
return [];
return [
'start' => 'date',
'end' => 'date|after:start',
'accounts' => '',
'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts',
];
}
/**
* @return Collection
* @return string
*/
private function getAccounts(): Collection
private function getAccounts(): string
{
$accountList = '' === (string) $this->query('accounts') ? [] : explode(',', $this->query('accounts'));
$accounts = new Collection;
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
foreach ($accountList as $accountId) {
Log::debug(sprintf('Searching for asset account with id "%s"', $accountId));
$account = $accountRepository->findNull((int) $accountId);
if ($this->validAccount($account)) {
/** @noinspection NullPointerExceptionInspection */
Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name));
$accounts->push($account);
}
}
return $accounts;
return (string) $this->query('accounts');
}
/**
@@ -120,30 +104,4 @@ class RuleTestRequest extends FormRequest
}
/**
* @return int
*/
private function getSearchLimit(): int
{
return 0 === (int) $this->query('search_limit') ? (int) config('firefly.test-triggers.limit') : (int) $this->query('search_limit');
}
/**
* @return int
*/
private function getTriggerLimit(): int
{
return 0 === (int) $this->query('triggered_limit') ? (int) config('firefly.test-triggers.range') : (int) $this->query('triggered_limit');
}
/**
* @param Account|null $account
*
* @return bool
*/
private function validAccount(?Account $account): bool
{
return null !== $account && AccountType::ASSET === $account->accountType->type;
}
}

View File

@@ -26,13 +26,8 @@ namespace FireflyIII\Api\V1\Requests;
use Carbon\Carbon;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Collection;
use Log;
/**
* Class RuleTriggerRequest
@@ -40,6 +35,7 @@ use Log;
class RuleTriggerRequest extends FormRequest
{
use ConvertsDataTypes;
/**
* Authorize logged in users.
*
@@ -57,9 +53,9 @@ class RuleTriggerRequest extends FormRequest
public function getTriggerParameters(): array
{
return [
'start_date' => $this->getDate('start_date'),
'end_date' => $this->getDate('end_date'),
'accounts' => $this->getAccounts(),
'start' => $this->getDate('start'),
'end' => $this->getDate('end'),
'accounts' => $this->getAccounts(),
];
}
@@ -69,33 +65,19 @@ class RuleTriggerRequest extends FormRequest
public function rules(): array
{
return [
'start_date' => 'required|date',
'end_date' => 'required|date|after:start_date',
'start' => 'date',
'end' => 'date|after:start',
'accounts' => '',
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
];
}
/**
* @return Collection
* @return string
*/
private function getAccounts(): Collection
private function getAccounts(): string
{
$accountList = '' === (string) $this->query('accounts') ? [] : explode(',', $this->query('accounts'));
$accounts = new Collection;
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
foreach ($accountList as $accountId) {
Log::debug(sprintf('Searching for asset account with id "%s"', $accountId));
$account = $accountRepository->findNull((int) $accountId);
if ($this->validAccount($account)) {
/** @noinspection NullPointerExceptionInspection */
Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name));
$accounts->push($account);
}
}
return $accounts;
return (string) $this->query('accounts');
}
/**
@@ -105,20 +87,7 @@ class RuleTriggerRequest extends FormRequest
*/
private function getDate(string $field): ?Carbon
{
/** @var Carbon $result */
$result = null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', $this->query($field));
return $result;
}
/**
* @param Account|null $account
*
* @return bool
*/
private function validAccount(?Account $account): bool
{
return null !== $account && AccountType::ASSET === $account->accountType->type;
return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', $this->query($field));
}
}

View File

@@ -25,6 +25,7 @@ namespace FireflyIII\Api\V1\Requests;
use FireflyIII\Rules\IsBoolean;
use FireflyIII\Support\Request\ConvertsDataTypes;
use FireflyIII\Support\Request\GetRuleConfiguration;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Validator;
use function is_array;
@@ -35,7 +36,7 @@ use function is_array;
*/
class RuleUpdateRequest extends FormRequest
{
use ConvertsDataTypes;
use ConvertsDataTypes, GetRuleConfiguration;
/**
* Authorize logged in users.
*
@@ -88,12 +89,12 @@ class RuleUpdateRequest extends FormRequest
*/
public function rules(): array
{
$validTriggers = array_keys(config('firefly.rule-triggers'));
$validTriggers = $this->getTriggers();
$validActions = array_keys(config('firefly.rule-actions'));
$rule = $this->route()->parameter('rule');
// some triggers and actions require text:
$contextTriggers = implode(',', config('firefly.context-rule-triggers'));
$contextTriggers = implode(',', $this->getTriggersWithContext());
$contextActions = implode(',', config('firefly.context-rule-actions'));
return [

View File

@@ -42,23 +42,12 @@ class TransactionUpdateRequest extends FormRequest
{
use TransactionValidation, GroupValidation, ConvertsDataTypes;
/** @var array Array values. */
private $arrayFields;
/** @var array Boolean values. */
private $booleanFields;
/** @var array Fields that contain date values. */
private $dateFields;
/** @var array Fields that contain integer values. */
private $integerFields;
/** @var array Fields that contain string values. */
private $stringFields;
/** @var array Fields that contain text (with newlines) */
private $textareaFields;
private array $arrayFields;
private array $booleanFields;
private array $dateFields;
private array $integerFields;
private array $stringFields;
private array $textareaFields;
/**
@@ -177,12 +166,12 @@ class TransactionUpdateRequest extends FormRequest
// currency info
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id',
'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id',
'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id',
'transactions.*.foreign_currency_code' => 'nullable|min:3|max:3|exists:transaction_currencies,code',
// amount
'transactions.*.amount' => 'numeric|gt:0|max:100000000000',
'transactions.*.foreign_amount' => 'numeric|gte:0',
'transactions.*.foreign_amount' => 'nullable|numeric|gte:0',
// description
'transactions.*.description' => 'nullable|between:1,1000',

View File

@@ -55,7 +55,7 @@ class CreateDatabase extends Command
*/
public function handle(): int
{
if ('mysql' !== env('DB_CONNECTION')) {
if ('mysql' !== env('DB_CONNECTION', 'mysql')) {
$this->info(sprintf('CreateDB does not apply to "%s", skipped.', env('DB_CONNECTION')));
return 0;

View File

@@ -230,7 +230,7 @@ class ExportData extends Command
return $date;
}
if ('end' === $field) {
$date = new Carbon;
$date = today(config('app.timezone'));
$date->endOfDay();
return $date;

View File

@@ -27,7 +27,6 @@ namespace FireflyIII\Console\Commands\Tools;
use Carbon\Carbon;
use FireflyIII\Console\Commands\VerifiesAccessToken;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup;
@@ -35,7 +34,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\TransactionRules\Engine\RuleEngine;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Log;
@@ -58,7 +57,7 @@ class ApplyRules extends Command
*
* @var string
*/
protected $signature
protected $signature
= 'firefly-iii:apply-rules
{--user=1 : The user ID.}
{--token= : The user\'s access token.}
@@ -68,44 +67,33 @@ class ApplyRules extends Command
{--all_rules : If set, will overrule both settings and simply apply ALL of your rules.}
{--start_date= : The date of the earliest transaction to be included (inclusive). If omitted, will be your very first transaction ever. Format: YYYY-MM-DD}
{--end_date= : The date of the latest transaction to be included (inclusive). If omitted, will be your latest transaction ever. Format: YYYY-MM-DD}';
/** @var array */
private $acceptedAccounts;
/** @var Collection */
private $accounts;
/** @var bool */
private $allRules;
/** @var Carbon */
private $endDate;
/** @var Collection */
private $groups;
/** @var RuleGroupRepositoryInterface */
private $ruleGroupRepository;
/** @var array */
private $ruleGroupSelection;
/** @var RuleRepositoryInterface */
private $ruleRepository;
/** @var array */
private $ruleSelection;
/** @var Carbon */
private $startDate;
private array $acceptedAccounts;
private Collection $accounts;
private bool $allRules;
private Carbon $endDate;
private Collection $groups;
private RuleGroupRepositoryInterface $ruleGroupRepository;
private array $ruleGroupSelection;
private RuleRepositoryInterface $ruleRepository;
private array $ruleSelection;
private Carbon $startDate;
/**
* Execute the console command.
*
* @throws FireflyException
* @return int
* @throws FireflyException
*/
public function handle(): int
{
$start = microtime(true);
$this->stupidLaravel();
// @codeCoverageIgnoreStart
if (!$this->verifyAccessToken()) {
$this->error('Invalid access token.');
return 1;
}
// @codeCoverageIgnoreEnd
// set user:
$this->ruleRepository->setUser($this->getUser());
@@ -124,7 +112,7 @@ class ApplyRules extends Command
// loop all groups and rules and indicate if they're included:
$rulesToApply = $this->getRulesToApply();
$count = count($rulesToApply);
$count = $rulesToApply->count();
if (0 === $count) {
$this->error('No rules or rule groups have been included.');
$this->warn('Make a selection using:');
@@ -136,48 +124,45 @@ class ApplyRules extends Command
return 1;
}
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->getUser());
$collector->setAccounts($this->accounts);
$collector->setRange($this->startDate, $this->endDate);
$journals = $collector->getExtractedJournals();
// create new rule engine:
/** @var RuleEngineInterface $ruleEngine */
$ruleEngine = app(RuleEngineInterface::class);
$ruleEngine->setRules($rulesToApply);
$ruleEngine->setUser($this->getUser());
// add the accounts as filter:
$accounts = [];
foreach($this->accounts as $account) {
$accounts[] = $account->id;
}
$list = implode(',', $accounts);
$ruleEngine->addOperator(['type' => 'account_id', 'value' => $list]);
// add the date as a filter:
$ruleEngine->addOperator(['type' => 'date_after', 'value' => $this->startDate->format('Y-m-d')]);
$ruleEngine->addOperator(['type' => 'date_before', 'value' => $this->endDate->format('Y-m-d')]);
// start running rules.
$this->line(sprintf('Will apply %d rule(s) to %d transaction(s).', $count, count($journals)));
$this->line(sprintf('Will apply %d rule(s) to your transaction(s).', $count));
// start looping.
/** @var RuleEngine $ruleEngine */
$ruleEngine = app(RuleEngine::class);
$ruleEngine->setUser($this->getUser());
$ruleEngine->setRulesToApply($rulesToApply);
// for this call, the rule engine only includes "store" rules:
$ruleEngine->setTriggerMode(RuleEngine::TRIGGER_STORE);
$bar = $this->output->createProgressBar(count($journals));
Log::debug(sprintf('Now looping %d transactions.', count($journals)));
/** @var array $journal */
foreach ($journals as $journal) {
Log::debug('Start of new journal.');
$ruleEngine->processJournalArray($journal);
Log::debug('Done with all rules for this group + done with journal.');
/** @noinspection DisconnectedForeachInstructionInspection */
$bar->advance();
}
$this->line('');
$this->line('Done!');
// file the rule(s)
$ruleEngine->fire();
app('telemetry')->feature('system.command.executed', $this->signature);
$this->line('');
$end = round(microtime(true) - $start, 2);
$this->line(sprintf('Done in %s seconds!', $end));
return 0;
}
/**
* @return array
* @return Collection
*/
private function getRulesToApply(): array
private function getRulesToApply(): Collection
{
$rulesToApply = [];
$rulesToApply = new Collection;
/** @var RuleGroup $group */
foreach ($this->groups as $group) {
$rules = $this->ruleGroupRepository->getActiveStoreRules($group);
@@ -187,7 +172,7 @@ class ApplyRules extends Command
$test = $this->includeRule($rule, $group);
if (true === $test) {
Log::debug(sprintf('Will include rule #%d "%s"', $rule->id, $rule->title));
$rulesToApply[] = $rule->id;
$rulesToApply->push($rule);
}
}
}
@@ -235,8 +220,8 @@ class ApplyRules extends Command
}
/**
* @throws FireflyException
* @return bool
* @throws FireflyException
*/
private function verifyInput(): bool
{
@@ -258,8 +243,8 @@ class ApplyRules extends Command
}
/**
* @throws FireflyException
* @return bool
* @throws FireflyException
*/
private function verifyInputAccounts(): bool
{

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Console\Commands;
use FireflyIII\Support\System\GeneratesInstallationId;
use FireflyIII\User;
use Illuminate\Console\Command;
use Illuminate\Database\QueryException;
/**
* Class UpgradeFireflyInstructions.
@@ -56,10 +57,10 @@ class UpgradeFireflyInstructions extends Command
public function handle(): int
{
$this->generateInstallationId();
if ('update' === (string) $this->argument('task')) {
if ('update' === (string)$this->argument('task')) {
$this->updateInstructions();
}
if ('install' === (string) $this->argument('task')) {
if ('install' === (string)$this->argument('task')) {
$this->installInstructions();
}
@@ -70,7 +71,11 @@ class UpgradeFireflyInstructions extends Command
app('telemetry')->feature('system.database.driver', env('DB_CONNECTION', '(unknown)'));
app('telemetry')->feature('system.os.is_docker', $isDocker);
app('telemetry')->feature('system.command.executed', $this->signature);
app('telemetry')->feature('system.users.count', (string) User::count());
try {
app('telemetry')->feature('system.users.count', (string)User::count());
} catch (QueryException $e) {
// ignore error.
}
return 0;
}
@@ -108,8 +113,8 @@ class UpgradeFireflyInstructions extends Command
{
/** @var string $version */
$version = config('firefly.version');
$config = config('upgrade.text.install');
$text = '';
$config = config('upgrade.text.install');
$text = '';
foreach (array_keys($config) as $compare) {
// if string starts with:
if (0 === strpos($version, $compare)) {
@@ -156,8 +161,8 @@ class UpgradeFireflyInstructions extends Command
{
/** @var string $version */
$version = config('firefly.version');
$config = config('upgrade.text.upgrade');
$text = '';
$config = config('upgrade.text.upgrade');
$text = '';
foreach (array_keys($config) as $compare) {
// if string starts with:
if (0 === strpos($version, $compare)) {

View File

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

View File

@@ -51,8 +51,8 @@ class GracefulNotFoundHandler extends ExceptionHandler
* @param Request $request
* @param Exception $exception
*
* @throws Exception
* @return mixed
* @throws Exception
*/
public function render($request, Throwable $exception)
{
@@ -75,7 +75,6 @@ class GracefulNotFoundHandler extends ExceptionHandler
return $this->handleAccount($request, $exception);
case 'transactions.show':
return $this->handleGroup($request, $exception);
break;
case 'attachments.show':
case 'attachments.edit':
// redirect to original attachment holder.
@@ -128,19 +127,21 @@ class GracefulNotFoundHandler extends ExceptionHandler
case 'transactions.mass.edit':
case 'transactions.mass.delete':
case 'transactions.bulk.edit':
$request->session()->reflash();
return redirect(route('index'));
break;
if ('POST' === $request->method()) {
$request->session()->reflash();
return redirect(route('index'));
}
return parent::render($request, $exception);
}
}
/**
* @param Request $request
* @param Throwable $exception
*
* @throws Exception
* @return Redirector|Response
* @throws Exception
*/
private function handleAccount(Request $request, Throwable $exception)
{
@@ -167,8 +168,8 @@ class GracefulNotFoundHandler extends ExceptionHandler
* @param Request $request
* @param Throwable $exception
*
* @throws Exception
* @return RedirectResponse|Redirector|Response
* @throws Exception
*/
private function handleAttachment(Request $request, Throwable $exception)
{
@@ -209,11 +210,11 @@ class GracefulNotFoundHandler extends ExceptionHandler
}
/**
* @param Throwable $request
* @param Throwable $request
* @param Exception $exception
*
* @throws Exception
* @return RedirectResponse|\Illuminate\Http\Response|Redirector|Response
* @throws Exception
*/
private function handleGroup(Request $request, Throwable $exception)
{

View File

@@ -56,9 +56,6 @@ class AccountFactory
*/
public function __construct()
{
if ('testing' === config('app.env')) {
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
}
$this->canHaveVirtual = [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD];
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->validAssetFields = ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth'];

View File

@@ -34,18 +34,6 @@ use Log;
*/
class AccountMetaFactory
{
/**
* Constructor.
*
* @codeCoverageIgnore
*/
public function __construct()
{
if ('testing' === config('app.env')) {
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
}
}
/**
* @param array $data
*

View File

@@ -36,21 +36,7 @@ use Log;
*/
class AttachmentFactory
{
/** @var User */
private $user;
/**
* Constructor.
*
* @codeCoverageIgnore
*/
public function __construct()
{
if ('testing' === config('app.env')) {
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
}
}
private User $user;
/**
* @param array $data

View File

@@ -35,23 +35,7 @@ use Log;
*/
class TagFactory
{
/** @var Collection */
private $tags;
/** @var User */
private $user;
/**
* Constructor.
*
* @codeCoverageIgnore
*/
public function __construct()
{
if ('testing' === config('app.env')) {
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
}
}
private User $user;
/**
* @param array $data

View File

@@ -37,18 +37,6 @@ use Log;
*/
class TransactionCurrencyFactory
{
/**
* TransactionCurrencyFactory constructor.
*
* @codeCoverageIgnore
*/
public function __construct()
{
if ('testing' === config('app.env')) {
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
}
}
/**
* @param array $data
*

View File

@@ -258,7 +258,7 @@ class TransactionJournalFactory
/** Some basic fields */
$type = $this->typeRepository->findTransactionType(null, $row['type']);
$carbon = $row['date'] ?? new Carbon;
$carbon = $row['date'] ?? today(config('app.timezone'));
$order = $row['order'] ?? 0;
$currency = $this->currencyRepository->findCurrency((int) $row['currency_id'], $row['currency_code']);
$foreignCurrency = $this->currencyRepository->findCurrencyNull($row['foreign_currency_id'], $row['foreign_currency_code']);

View File

@@ -138,16 +138,16 @@ class ChartJsGenerator implements GeneratorInterface
'type' => $set['type'] ?? 'line',
'data' => array_values($set['entries']),
];
if (isset($set['yAxisID'])) {
if (array_key_exists('yAxisID', $set)) {
$currentSet['yAxisID'] = $set['yAxisID'];
}
if (isset($set['fill'])) {
if (array_key_exists('fill', $set)) {
$currentSet['fill'] = $set['fill'];
}
if (isset($set['currency_symbol'])) {
if (array_key_exists('currency_symbol', $set)) {
$currentSet['currency_symbol'] = $set['currency_symbol'];
}
if (isset($set['backgroundColor'])) {
if (array_key_exists('backgroundColor', $set)) {
$currentSet['backgroundColor'] = $set['backgroundColor'];
}
$chartData['datasets'][] = $currentSet;

View File

@@ -24,7 +24,9 @@ namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\TransactionRules\Engine\RuleEngine;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use Log;
/**
@@ -46,17 +48,26 @@ class StoredGroupEventHandler
}
Log::debug('Now in StoredGroupEventHandler::processRules()');
/** @var RuleEngine $ruleEngine */
$ruleEngine = app(RuleEngine::class);
$ruleEngine->setUser($storedGroupEvent->transactionGroup->user);
$ruleEngine->setAllRules(true);
$ruleEngine->setTriggerMode(RuleEngine::TRIGGER_STORE);
$journals = $storedGroupEvent->transactionGroup->transactionJournals;
$array = [];
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {
$ruleEngine->processTransactionJournal($journal);
$array[] = $journal->id;
}
$journalIds = implode(',', $array);
Log::debug(sprintf('Add local operator for journal(s): %s', $journalIds));
// collect rules:
$ruleRepository = app(RuleRepositoryInterface::class);
$ruleRepository->setUser($storedGroupEvent->transactionGroup->user);
$rules = $ruleRepository->getStoreRules();
// file rule engine.
$newRuleEngine = app(RuleEngineInterface::class);
$newRuleEngine->setUser($storedGroupEvent->transactionGroup->user);
$newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalIds]);
$newRuleEngine->setRules($rules);
$newRuleEngine->fire();
}
}

View File

@@ -27,7 +27,9 @@ use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\TransactionRules\Engine\RuleEngine;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use Log;
/**
@@ -88,17 +90,26 @@ class UpdatedGroupEventHandler
return;
}
/** @var RuleEngine $ruleEngine */
$ruleEngine = app(RuleEngine::class);
$ruleEngine->setUser($updatedGroupEvent->transactionGroup->user);
$ruleEngine->setAllRules(true);
$ruleEngine->setTriggerMode(RuleEngine::TRIGGER_UPDATE);
$journals = $updatedGroupEvent->transactionGroup->transactionJournals;
$array = [];
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {
$ruleEngine->processTransactionJournal($journal);
$array[] = $journal->id;
}
$journalIds = implode(',', $array);
Log::debug(sprintf('Add local operator for journal(s): %s', $journalIds));
// collect rules:
$ruleRepository = app(RuleRepositoryInterface::class);
$ruleRepository->setUser($updatedGroupEvent->transactionGroup->user);
$rules = $ruleRepository->getUpdateRules();
// file rule engine.
$newRuleEngine = app(RuleEngineInterface::class);
$newRuleEngine->setUser($updatedGroupEvent->transactionGroup->user);
$newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalIds]);
$newRuleEngine->setRules($rules);
$newRuleEngine->fire();
}
}

View File

@@ -23,11 +23,14 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
use Carbon\Carbon;
use Exception;
use FireflyIII\Events\DetectedNewIPAddress;
use FireflyIII\Events\RegisteredUser;
use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Events\UserChangedEmail;
use FireflyIII\Mail\ConfirmEmailChangeMail;
use FireflyIII\Mail\NewIPAddressWarningMail;
use FireflyIII\Mail\RegisteredUser as RegisteredUserMail;
use FireflyIII\Mail\RequestedNewPassword as RequestedNewPasswordMail;
use FireflyIII\Mail\UndoEmailChangeMail;
@@ -125,6 +128,78 @@ class UserEventHandler
return true;
}
/**
* @param Login $event
*/
public function storeUserIPAddress(Login $event): void
{
/** @var User $user */
$user = $event->user;
/** @var array $preference */
$preference = app('preferences')->getForUser($user, 'login_ip_history', [])->data;
$inArray = false;
$ip = request()->ip();
Log::debug(sprintf('User logging in from IP address %s', $ip));
// update array if in array
foreach ($preference as $index => $row) {
if ($row['ip'] === $ip) {
Log::debug('Found IP in array, refresh time.');
$preference[$index]['time'] = now(config('app.timezone'))->format('Y-m-d H:i:s');
$inArray = true;
}
// clean up old entries (6 months)
$carbon = Carbon::createFromFormat('Y-m-d H:i:s', $preference[$index]['time']);
if ($carbon->diffInMonths(today()) > 6) {
Log::debug(sprintf('Entry for %s is very old, remove it.', $row['ip']));
unset($preference[$index]);
}
}
// add to array if not the case:
if (false === $inArray) {
$preference[] = [
'ip' => $ip,
'time' => now(config('app.timezone'))->format('Y-m-d H:i:s'),
'notified' => false,
];
}
$preference = array_values($preference);
app('preferences')->setForUser($user, 'login_ip_history', $preference);
if (false === $inArray && true === config('firefly.warn_new_ip')) {
event(new DetectedNewIPAddress($user, $ip));
}
}
/**
* @param DetectedNewIPAddress $event
*/
public function notifyNewIPAddress(DetectedNewIPAddress $event): void
{
$user = $event->user;
$email = $user->email;
$ipAddress = $event->ipAddress;
$list = app('preferences')->getForUser($user, 'login_ip_history', [])->data;
/** @var array $entry */
foreach ($list as $index => $entry) {
if (false === $entry['notified']) {
try {
Mail::to($email)->send(new NewIPAddressWarningMail($ipAddress));
// @codeCoverageIgnoreStart
} catch (Exception $e) {
Log::error($e->getMessage());
}
}
$list[$index]['notified'] = true;
}
app('preferences')->setForUser($user, 'login_ip_history', $list);
}
/**
* Send email to confirm email change.
*
@@ -167,7 +242,7 @@ class UserEventHandler
$ipAddress = $event->ipAddress;
$token = app('preferences')->getForUser($user, 'email_change_undo_token', 'invalid');
$hashed = hash('sha256', sprintf('%s%s', (string) config('app.key'), $oldEmail));
$uri = route('profile.undo-email-change', [$token->data,$hashed]);
$uri = route('profile.undo-email-change', [$token->data, $hashed]);
try {
Mail::to($oldEmail)->send(new UndoEmailChangeMail($newEmail, $oldEmail, $uri, $ipAddress));
// @codeCoverageIgnoreStart

View File

@@ -306,7 +306,7 @@ class AttachmentHelper implements AttachmentHelperInterface
$content = $fileObject->fread($file->getSize());
Log::debug(sprintf('Full file length is %d and upload size is %d.', strlen($content), $file->getSize()));
// store it:
// store it without encryption.
$this->uploadDisk->put($attachment->fileName(), $content);
$attachment->uploaded = true; // update attachment
$attachment->save();

View File

@@ -62,7 +62,7 @@ trait AmountCollection
{
$this->query->where(
function (EloquentBuilder $q) use ($amount) {
$q->where('destination.amount', '<', app('steam')->positive($amount));
$q->where('destination.amount', '<=', app('steam')->positive($amount));
}
);
@@ -80,7 +80,7 @@ trait AmountCollection
{
$this->query->where(
function (EloquentBuilder $q) use ($amount) {
$q->where('destination.amount', '>', app('steam')->positive($amount));
$q->where('destination.amount', '>=', app('steam')->positive($amount));
}
);

View File

@@ -61,6 +61,73 @@ trait MetaCollection
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function notesContain(string $value): GroupCollectorInterface
{
$this->withNotes();
$this->query->where('notes.text', 'LIKE', sprintf('%%%s%%', $value));
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function notesEndWith(string $value): GroupCollectorInterface
{
$this->withNotes();
$this->query->where('notes.text', 'LIKE', sprintf('%%%s', $value));
return $this;
}
/**
* @return GroupCollectorInterface
*/
public function withoutNotes(): GroupCollectorInterface
{
$this->withNotes();
$this->query->whereNull('notes.text');
return $this;
}
/**
* @return GroupCollectorInterface
*/
public function withAnyNotes(): GroupCollectorInterface
{
$this->withNotes();
$this->query->whereNotNull('notes.text');
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function notesExactly(string $value): GroupCollectorInterface
{
$this->withNotes();
$this->query->where('notes.text', '=', sprintf('%s', $value));
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function notesStartWith(string $value): GroupCollectorInterface
{
$this->withNotes();
$this->query->where('notes.text', 'LIKE', sprintf('%s%%', $value));
return $this;
}
/**
* Limit the search to a specific bill.
*
@@ -185,6 +252,32 @@ trait MetaCollection
return $this;
}
/**
* Where has no tags.
*
* @return GroupCollectorInterface
*/
public function withoutTags(): GroupCollectorInterface
{
$this->withTagInformation();
$this->query->whereNull('tag_transaction_journal.tag_id');
return $this;
}
/**
* Where has no tags.
*
* @return GroupCollectorInterface
*/
public function hasAnyTag(): GroupCollectorInterface
{
$this->withTagInformation();
$this->query->whereNotNull('tag_transaction_journal.tag_id');
return $this;
}
/**
* Will include bill name + ID, if any.
*
@@ -272,11 +365,20 @@ trait MetaCollection
public function withoutBudget(): GroupCollectorInterface
{
$this->withBudgetInformation();
$this->query->where(
function (EloquentBuilder $q) {
$q->whereNull('budget_transaction_journal.budget_id');
}
);
$this->query->whereNull('budget_transaction_journal.budget_id');
return $this;
}
/**
* Limit results to a transactions without a budget..
*
* @return GroupCollectorInterface
*/
public function withBudget(): GroupCollectorInterface
{
$this->withBudgetInformation();
$this->query->whereNotNull('budget_transaction_journal.budget_id');
return $this;
}
@@ -289,11 +391,20 @@ trait MetaCollection
public function withoutCategory(): GroupCollectorInterface
{
$this->withCategoryInformation();
$this->query->where(
function (EloquentBuilder $q) {
$q->whereNull('category_transaction_journal.category_id');
}
);
$this->query->whereNull('category_transaction_journal.category_id');
return $this;
}
/**
* Limit results to a transactions without a category.
*
* @return GroupCollectorInterface
*/
public function withCategory(): GroupCollectorInterface
{
$this->withCategoryInformation();
$this->query->whereNotNull('category_transaction_journal.category_id');
return $this;
}

View File

@@ -130,7 +130,7 @@ class GroupCollector implements GroupCollectorInterface
*/
public function dumpQuery(): void
{
echo $this->query->toSql();
echo $this->query->select($this->fields)->toSql();
echo '<pre>';
print_r($this->query->getBindings());
echo '</pre>';
@@ -232,6 +232,16 @@ class GroupCollector implements GroupCollectorInterface
return $this;
}
/**
* @inheritDoc
*/
public function setForeignCurrency(TransactionCurrency $currency): GroupCollectorInterface
{
$this->query->where('source.foreign_currency_id', $currency->id);
return $this;
}
/**
* Limit the result to a specific transaction group.
*
@@ -326,6 +336,79 @@ class GroupCollector implements GroupCollectorInterface
return $this;
}
/**
* @inheritDoc
*/
public function descriptionStarts(array $array): GroupCollectorInterface
{
$this->query->where(
static function (EloquentBuilder $q) use ($array) {
$q->where(
static function (EloquentBuilder $q1) use ($array) {
foreach ($array as $word) {
$keyword = sprintf('%s%%', $word);
$q1->where('transaction_journals.description', 'LIKE', $keyword);
}
}
);
$q->orWhere(
static function (EloquentBuilder $q2) use ($array) {
foreach ($array as $word) {
$keyword = sprintf('%s%%', $word);
$q2->where('transaction_groups.title', 'LIKE', $keyword);
}
}
);
}
);
return $this;
}
/**
* @inheritDoc
*/
public function descriptionEnds(array $array): GroupCollectorInterface
{
$this->query->where(
static function (EloquentBuilder $q) use ($array) {
$q->where(
static function (EloquentBuilder $q1) use ($array) {
foreach ($array as $word) {
$keyword = sprintf('%%%s', $word);
$q1->where('transaction_journals.description', 'LIKE', $keyword);
}
}
);
$q->orWhere(
static function (EloquentBuilder $q2) use ($array) {
foreach ($array as $word) {
$keyword = sprintf('%%%s', $word);
$q2->where('transaction_groups.title', 'LIKE', $keyword);
}
}
);
}
);
return $this;
}
/**
* @inheritDoc
*/
public function descriptionIs(string $value): GroupCollectorInterface
{
$this->query->where(
static function (EloquentBuilder $q) use ($value) {
$q->where('transaction_journals.description', '=', $value);
$q->orWhere('transaction_groups.title', '=', $value);
}
);
return $this;
}
/**
* Limit the search to one specific transaction group.
@@ -411,12 +494,26 @@ class GroupCollector implements GroupCollectorInterface
private function convertToInteger(array $array): array
{
foreach ($this->integerFields as $field) {
$array[$field] = isset($array[$field]) ? (int) $array[$field] : null;
$array[$field] = array_key_exists($field, $array) ? (int) $array[$field] : null;
}
return $array;
}
/**
* Has attachments
*
* @return GroupCollectorInterface
*/
public function hasAttachments(): GroupCollectorInterface
{
Log::debug('Add filter on attachment ID.');
$this->joinAttachmentTables();
$this->query->whereNotNull('attachments.attachable_id');
return $this;
}
/**
* Join table to get attachment information.
*/
@@ -444,7 +541,7 @@ class GroupCollector implements GroupCollectorInterface
private function mergeAttachments(array $existingJournal, TransactionJournal $newJournal): array
{
$newArray = $newJournal->toArray();
if (isset($newArray['attachment_id'])) {
if (array_key_exists('attachment_id', $newArray)) {
$attachmentId = (int) $newJournal['tag_id'];
$existingJournal['attachments'][$attachmentId] = [
'id' => $attachmentId,
@@ -463,7 +560,7 @@ class GroupCollector implements GroupCollectorInterface
private function mergeTags(array $existingJournal, TransactionJournal $newJournal): array
{
$newArray = $newJournal->toArray();
if (isset($newArray['tag_id'])) { // assume the other fields are present as well.
if (array_key_exists('tag_id', $newArray)) { // assume the other fields are present as well.
$tagId = (int) $newJournal['tag_id'];
$tagDate = null;
@@ -496,7 +593,7 @@ class GroupCollector implements GroupCollectorInterface
foreach ($collection as $augumentedJournal) {
$groupId = $augumentedJournal->transaction_group_id;
if (!isset($groups[$groupId])) {
if (!array_key_exists($groupId, $groups)) {
// make new array
$parsedGroup = $this->parseAugmentedJournal($augumentedJournal);
$groupArray = [
@@ -517,13 +614,13 @@ class GroupCollector implements GroupCollectorInterface
$journalId = (int) $augumentedJournal->transaction_journal_id;
if (isset($groups[$groupId]['transactions'][$journalId])) {
if (array_key_exists($journalId, $groups[$groupId]['transactions'])) {
// append data to existing group + journal (for multiple tags or multiple attachments)
$groups[$groupId]['transactions'][$journalId] = $this->mergeTags($groups[$groupId]['transactions'][$journalId], $augumentedJournal);
$groups[$groupId]['transactions'][$journalId] = $this->mergeAttachments($groups[$groupId]['transactions'][$journalId], $augumentedJournal);
}
if (!isset($groups[$groupId]['transactions'][$journalId])) {
if (!array_key_exists($journalId, $groups[$groupId]['transactions'])) {
// create second, third, fourth split:
$groups[$groupId]['count']++;
$groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedJournal($augumentedJournal);
@@ -655,7 +752,7 @@ class GroupCollector implements GroupCollectorInterface
'transactions as source',
function (JoinClause $join) {
$join->on('source.transaction_journal_id', '=', 'transaction_journals.id')
->where('source.amount', '<', 0);
->where('source.amount', '<', 0);
}
)
// join destination transaction
@@ -663,7 +760,7 @@ class GroupCollector implements GroupCollectorInterface
'transactions as destination',
function (JoinClause $join) {
$join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')
->where('destination.amount', '>', 0);
->where('destination.amount', '>', 0);
}
)
// left join transaction type.

View File

@@ -220,6 +220,15 @@ interface GroupCollectorInterface
*/
public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface;
/**
* Limit results to a specific foreign currency.
*
* @param TransactionCurrency $currency
*
* @return GroupCollectorInterface
*/
public function setForeignCurrency(TransactionCurrency $currency): GroupCollectorInterface;
/**
* Set destination accounts.
*
@@ -284,6 +293,33 @@ interface GroupCollectorInterface
*/
public function setSearchWords(array $array): GroupCollectorInterface;
/**
* Beginning of the description must match:
*
* @param array $array
*
* @return GroupCollectorInterface
*/
public function descriptionStarts(array $array): GroupCollectorInterface;
/**
* End of the description must match:
*
* @param array $array
*
* @return GroupCollectorInterface
*/
public function descriptionEnds(array $array): GroupCollectorInterface;
/**
* Description must be:
*
* @param string $value
*
* @return GroupCollectorInterface
*/
public function descriptionIs(string $value): GroupCollectorInterface;
/**
* Set source accounts.
*
@@ -311,6 +347,16 @@ interface GroupCollectorInterface
*/
public function setTags(Collection $tags): GroupCollectorInterface;
/**
* @return GroupCollectorInterface
*/
public function withoutTags(): GroupCollectorInterface;
/**
* @return GroupCollectorInterface
*/
public function hasAnyTag(): GroupCollectorInterface;
/**
* Limit the search to one specific transaction group.
*
@@ -377,6 +423,13 @@ interface GroupCollectorInterface
*/
public function withAttachmentInformation(): GroupCollectorInterface;
/**
* Has attachments
*
* @return GroupCollectorInterface
*/
public function hasAttachments(): GroupCollectorInterface;
/**
* Include bill name + ID.
*
@@ -405,6 +458,42 @@ interface GroupCollectorInterface
*/
public function withNotes(): GroupCollectorInterface;
/**
* Any notes, no matter what.
*
* @return GroupCollectorInterface
*/
public function withAnyNotes(): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function notesContain(string $value): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function withoutNotes(): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function notesStartWith(string $value): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function notesEndWith(string $value): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function notesExactly(string $value): GroupCollectorInterface;
/**
* Add tag info.
*
@@ -426,6 +515,20 @@ interface GroupCollectorInterface
*/
public function withoutCategory(): GroupCollectorInterface;
/**
* Limit results to a transactions with a category.
*
* @return GroupCollectorInterface
*/
public function withCategory(): GroupCollectorInterface;
/**
* Limit results to a transactions with a budget.
*
* @return GroupCollectorInterface
*/
public function withBudget(): GroupCollectorInterface;
/**
* Look for specific external ID's.
*

View File

@@ -117,7 +117,7 @@ class NetWorth implements NetWorthInterface
Log::debug(sprintf('Balance corrected to %s because of virtual balance (%s)', $balance, $virtualBalance));
if (!isset($netWorth[$currencyId])) {
if (!array_key_exists($currencyId, $netWorth)) {
$netWorth[$currencyId] = '0';
}
$netWorth[$currencyId] = bcadd($balance, $netWorth[$currencyId]);

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Account;
use Carbon\Carbon;
use Exception;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
@@ -32,7 +33,7 @@ use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\View\View;
use Exception;
use Log;
/**
*
@@ -41,8 +42,8 @@ use Exception;
class IndexController extends Controller
{
use BasicDataSupport;
/** @var AccountRepositoryInterface The account repository */
private $repository;
private AccountRepositoryInterface $repository;
/**
* IndexController constructor.
@@ -116,51 +117,37 @@ class IndexController extends Controller
}
/**
* @return \Illuminate\Contracts\Foundation\Application|Factory|View
*/
public function emptyIndex(?string $objectType = null)
{
return view('accounts.empty-index', compact('objectType'));
}
/**
* Show list of accounts.
*
* @param Request $request
* @param string $objectType
*
* @throws Exception
* @return Factory|View
* @throws Exception
*/
public function index(Request $request, string $objectType)
{
// temp catch for layout.
if ('v2' === config('firefly.layout')) {
return $this->emptyIndex($objectType);
}
// reset account order:
Log::debug(sprintf('Now at %s', __METHOD__));
$objectType = $objectType ?? 'asset';
$subTitle = (string) trans(sprintf('firefly.%s_accounts', $objectType));
$subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType));
$types = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType));
if (1 === random_int(0, 20)) {
Log::debug('Will reset order.');
$this->repository->resetAccountOrder($types);
}
$collection = $this->repository->getActiveAccountsByType($types);
$total = $collection->count();
$page = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
$accounts = $collection->slice(($page - 1) * $pageSize, $pageSize);
$inactiveCount = $this->repository->getInactiveAccountsByType($types)->count();
Log::debug(sprintf('Count of collection: %d, count of accounts: %d', $total, $accounts->count()));
unset($collection);
/** @var Carbon $start */
$start = clone session('start', Carbon::now()->startOfMonth());
@@ -187,9 +174,14 @@ class IndexController extends Controller
}
);
// make paginator:
Log::debug(sprintf('Count of accounts before LAP: %d', $accounts->count()));
/** @var LengthAwarePaginator $accounts */
$accounts = new LengthAwarePaginator($accounts, $total, $pageSize, $page);
$accounts->setPath(route('accounts.index', [$objectType]));
Log::debug(sprintf('Count of accounts after LAP (1): %d', $accounts->count()));
Log::debug(sprintf('Count of accounts after LAP (2): %d', $accounts->getCollection()->count()));
return view('accounts.index', compact('objectType', 'inactiveCount', 'subTitleIcon', 'subTitle', 'page', 'accounts'));
}

View File

@@ -92,11 +92,6 @@ class ShowController extends Controller
{
$objectType = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type));
// temp catch for layout.
if ('v2' === config('firefly.layout')) {
return view('accounts.empty-index', compact('objectType'));
}
if (!$this->isEditableAccount($account)) {
return $this->redirectAccountToAccount($account); // @codeCoverageIgnore
}
@@ -111,7 +106,7 @@ class ShowController extends Controller
}
$location = $this->repository->getLocation($account);
$attachments = $this->repository->getAttachments($account);
$today = new Carbon;
$today = today(config('app.timezone'));
$subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type));
$page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
@@ -178,8 +173,8 @@ class ShowController extends Controller
$isLiability = $this->repository->isLiability($account);
$attachments = $this->repository->getAttachments($account);
$objectType = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type));
$end = new Carbon;
$today = new Carbon;
$end = today(config('app.timezone'));
$today = today(config('app.timezone'));
$start = $this->repository->oldestJournalDate($account) ?? Carbon::now()->startOfMonth();
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$page = (int) $request->get('page');

View File

@@ -87,7 +87,7 @@ class TelemetryController extends Controller
public function submit()
{
$job = app(SubmitTelemetryData::class);
$job->setDate(new Carbon);
$job->setDate(today(config('app.timezone')));
$job->setForce(true);
$job->handle();
session()->flash('info', trans('firefly.telemetry_submission_executed'));

View File

@@ -61,9 +61,9 @@ class UpdateController extends Controller
/**
* Show page with update options.
*
* @throws NotFoundExceptionInterface
* @throws ContainerExceptionInterface
* @return Factory|View
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function index()
{
@@ -100,6 +100,11 @@ class UpdateController extends Controller
$checkForUpdates = (int) $request->get('check_for_updates');
$channel = $request->get('update_channel');
$channel = in_array($channel, ['stable', 'beta', 'alpha'], true) ? $channel : 'stable';
// store as telemetry
app('telemetry')->feature('admin.update.channel', $channel);
app('telemetry')->feature('admin.update.permission', (string) $checkForUpdates);
app('fireflyconfig')->set('permission_update_check', $checkForUpdates);
app('fireflyconfig')->set('last_update_check', time());
app('fireflyconfig')->set('update_channel', $channel);

View File

@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Api\V1\Requests\UserUpdateRequest;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Http\Requests\UserFormRequest;
@@ -36,8 +37,8 @@ use Log;
*/
class UserController extends Controller
{
/** @var UserRepositoryInterface */
private $repository;
private UserRepositoryInterface $repository;
protected bool $externalIdentity;
/**
* UserController constructor.
@@ -56,17 +57,23 @@ class UserController extends Controller
}
);
$this->middleware(IsDemoUser::class)->except(['index', 'show']);
$loginProvider = config('firefly.login_provider');
$authGuard = config('firefly.authentication_guard');
$this->externalIdentity = 'eloquent' !== $loginProvider || 'web' !== $authGuard;
}
/**
* Delete a user.
*
* @param User $user
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|RedirectResponse|Redirector|\Illuminate\View\View
*/
public function delete(User $user)
{
if ($this->externalIdentity) {
request()->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
return redirect(route('admin.users'));
}
$subTitle = (string) trans('firefly.delete_user', ['email' => $user->email]);
return view('admin.users.delete', compact('user', 'subTitle'));
@@ -81,6 +88,11 @@ class UserController extends Controller
*/
public function destroy(User $user)
{
if ($this->externalIdentity) {
request()->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
return redirect(route('admin.users'));
}
$this->repository->destroy($user);
session()->flash('success', (string) trans('firefly.user_deleted'));
@@ -96,6 +108,10 @@ class UserController extends Controller
*/
public function edit(User $user)
{
$canEditDetails = true;
if ($this->externalIdentity) {
$canEditDetails = false;
}
// put previous url in session if not redirect from store (not "return_to_edit").
if (true !== session('users.edit.fromUpdate')) {
$this->rememberPreviousUri('users.edit.uri');
@@ -113,7 +129,7 @@ class UserController extends Controller
'email_changed' => (string) trans('firefly.block_code_email_changed'),
];
return view('admin.users.edit', compact('user', 'subTitle', 'subTitleIcon', 'codes', 'currentUser','isAdmin'));
return view('admin.users.edit', compact('user', 'canEditDetails', 'subTitle', 'subTitleIcon', 'codes', 'currentUser', 'isAdmin'));
}
/**
@@ -179,8 +195,10 @@ class UserController extends Controller
Log::debug('Actually here');
$data = $request->getUserData();
var_dump($data);
// update password
if ('' !== $data['password']) {
if (array_key_exists('password', $data) && '' !== $data['password']) {
$this->repository->changePassword($user, $data['password']);
}
if (true === $data['is_owner']) {

View File

@@ -66,13 +66,6 @@ class LoginController extends Controller
{
parent::__construct();
$this->middleware('guest')->except('logout');
$loginProvider = config('firefly.login_provider');
$authGuard = config('firefly.authentication_guard');
if ('eloquent' !== $loginProvider || 'web' !== $authGuard) {
throw new FireflyException('Using external identity provider. Cannot continue.');
}
}
@@ -112,6 +105,8 @@ class LoginController extends Controller
Log::channel('audit')->info(sprintf('User "%s" has been logged in.', $request->get('email')));
Log::debug(sprintf('Redirect after login is %s.', $this->redirectPath()));
// if you just logged in, it can't be that you have a valid 2FA cookie.
return $this->sendLoginResponse($request);
}
@@ -132,7 +127,7 @@ class LoginController extends Controller
*/
public function showLoginForm(Request $request)
{
Log::channel('audit')->info('Show login form (1.0).');
Log::channel('audit')->info('Show login form (1.1).');
$count = DB::table('users')->count();
$loginProvider = config('firefly.login_provider');
@@ -158,7 +153,11 @@ class LoginController extends Controller
$email = $request->old('email');
$remember = $request->old('remember');
// todo must forget 2FA if user ends up here.
$storeInCookie = config('google2fa.store_in_cookie', false);
if (false !== $storeInCookie) {
$cookieName = config('google2fa.cookie_name', 'google2fa_token');
request()->cookies->set($cookieName, 'invalid');
}
return view('auth.login', compact('allowRegistration', 'email', 'remember', 'allowReset', 'title'));

View File

@@ -36,21 +36,6 @@ use Preferences;
*/
class TwoFactorController extends Controller
{
/**
* Create a new controller instance.
*/
public function __construct()
{
parent::__construct();
$loginProvider = config('firefly.login_provider');
$authGuard = config('firefly.authentication_guard');
if ('eloquent' !== $loginProvider || 'web' !== $authGuard) {
throw new FireflyException('Using external identity provider. Cannot continue.');
}
}
/**
* What to do if 2FA lost?
*

View File

@@ -31,7 +31,7 @@ use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\Bill;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\TransactionRules\TransactionMatcher;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use FireflyIII\Transformers\AttachmentTransformer;
use FireflyIII\Transformers\BillTransformer;
use Illuminate\Contracts\View\Factory;
@@ -73,14 +73,15 @@ class ShowController extends Controller
}
);
}
/**
* Rescan bills for transactions.
*
* @param Request $request
* @param Bill $bill
*
* @throws FireflyException
* @return RedirectResponse|Redirector
* @throws FireflyException
*/
public function rescan(Request $request, Bill $bill)
{
@@ -104,18 +105,13 @@ class ShowController extends Controller
// unlink all journals:
$this->repository->unlinkAll($bill);
foreach ($set as $rule) {
// simply fire off all rules?
/** @var TransactionMatcher $matcher */
$matcher = app(TransactionMatcher::class);
$matcher->setSearchLimit(100000); // large upper limit
$matcher->setTriggeredLimit(100000); // large upper limit
$matcher->setRule($rule);
$matchingTransactions = $matcher->findTransactionsByRule();
$total += count($matchingTransactions);
$this->repository->linkCollectionToBill($bill, $matchingTransactions);
}
// fire the rules:
/** @var RuleEngineInterface $ruleEngine */
$ruleEngine = app(RuleEngineInterface::class);
$ruleEngine->setRules($set);
// file the rule(s)
$ruleEngine->fire();
$request->session()->flash('success', (string) trans_choice('firefly.rescanned_bill', $total));
app('preferences')->mark();
@@ -124,7 +120,6 @@ class ShowController extends Controller
}
/**
* Show a bill.
*

View File

@@ -185,7 +185,7 @@ class AvailableBudgetController extends Controller
return redirect(route('budgets.index', [$start->format('Y-m-d'), $end->format('Y-m-d')]));
}
if (0 === bccomp('0', $amount)) {
if (bccomp($amount, '0') <= 0) {
session()->flash('error', trans('firefly.invalid_amount'));
return redirect(route('budgets.index', [$start->format('Y-m-d'), $end->format('Y-m-d')]));
@@ -198,7 +198,8 @@ class AvailableBudgetController extends Controller
return redirect(route('budgets.index', [$start->format('Y-m-d'), $end->format('Y-m-d')]));
}
$start->startOfDay();
$end->endOfDay();
// find existing AB
$existing = $this->abRepository->find($currency, $start, $end);
if (null === $existing) {
@@ -238,7 +239,7 @@ class AvailableBudgetController extends Controller
return redirect(route('budgets.index', [$start->format('Y-m-d'), $end->format('Y-m-d')]));
}
if (0 === bccomp('0', $amount)) {
if (bccomp($amount, '0') <= 0) {
session()->flash('error', trans('firefly.invalid_amount'));
return redirect(route('budgets.index', [$start->format('Y-m-d'), $end->format('Y-m-d')]));

View File

@@ -52,14 +52,10 @@ class BudgetLimitController extends Controller
{
use DateCalculation;
/** @var BudgetLimitRepositoryInterface */
private $blRepository;
/** @var CurrencyRepositoryInterface */
private $currencyRepos;
/** @var OperationsRepositoryInterface */
private $opsRepository;
/** @var BudgetRepositoryInterface The budget repository */
private $repository;
private BudgetLimitRepositoryInterface $blRepository;
private CurrencyRepositoryInterface $currencyRepos;
private OperationsRepositoryInterface $opsRepository;
private BudgetRepositoryInterface $repository;
/**
* AmountController constructor.

View File

@@ -39,11 +39,8 @@ use Illuminate\View\View;
*/
class CreateController extends Controller
{
/** @var BudgetRepositoryInterface The budget repository */
private $repository;
/** @var AttachmentHelperInterface Helper for attachments. */
private $attachments;
private BudgetRepositoryInterface $repository;
private AttachmentHelperInterface $attachments;
/**
* CreateController constructor.

View File

@@ -49,16 +49,11 @@ use Log;
class IndexController extends Controller
{
use DateCalculation;
/** @var AvailableBudgetRepositoryInterface */
private $abRepository;
/** @var BudgetLimitRepositoryInterface */
private $blRepository;
/** @var CurrencyRepositoryInterface */
private $currencyRepository;
/** @var OperationsRepositoryInterface */
private $opsRepository;
/** @var BudgetRepositoryInterface The budget repository */
private $repository;
private AvailableBudgetRepositoryInterface $abRepository;
private BudgetLimitRepositoryInterface $blRepository;
private CurrencyRepositoryInterface $currencyRepository;
private OperationsRepositoryInterface $opsRepository;
private BudgetRepositoryInterface $repository;
/**
* IndexController constructor.

View File

@@ -123,7 +123,7 @@ class ShowController extends Controller
$subTitle = (string) trans('firefly.all_journals_without_budget');
$first = $this->journalRepos->firstNull();
$start = null === $first ? new Carbon : $first->date;
$end = new Carbon;
$end = today(config('app.timezone'));
$page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
@@ -150,11 +150,7 @@ class ShowController extends Controller
{
/** @var Carbon $start */
$allStart = session('first', Carbon::now()->startOfYear());
$allEnd = new Carbon;
// use session range:
$start = session('start');
$end = session('end');
$allEnd = today();
$page = (int) $request->get('page');
@@ -166,7 +162,7 @@ class ShowController extends Controller
// collector:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->setBudget($budget)
$collector->setRange($allStart, $allEnd)->setBudget($budget)
->withAccountInformation()
->setLimit($pageSize)->setPage($page)->withBudgetInformation()->withCategoryInformation();
$groups = $collector->getPaginatedGroups();
@@ -215,7 +211,7 @@ class ShowController extends Controller
$groups->setPath(route('budgets.show', [$budget->id, $budgetLimit->id]));
/** @var Carbon $start */
$start = session('first', Carbon::now()->startOfYear());
$end = new Carbon;
$end = today(config('app.timezone'));
$attachments = $this->repository->getAttachments($budget);
$limits = $this->getLimits($budget, $start, $end);

View File

@@ -126,7 +126,7 @@ class NoCategoryController extends Controller
$subTitle = (string) trans('firefly.all_journals_without_category');
$first = $this->journalRepos->firstNull();
$start = null === $first ? new Carbon : $first->date;
$end = new Carbon;
$end = today(config('app.timezone'));
Log::debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
Log::debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));

View File

@@ -129,8 +129,8 @@ class ShowController extends Controller
$subTitle = (string) trans('firefly.all_journals_for_category', ['name' => $category->name]);
$first = $this->repository->firstUseDate($category);
/** @var Carbon $start */
$start = $first ?? new Carbon;
$end = new Carbon;
$start = $first ?? today(config('app.timezone'));
$end = today(config('app.timezone'));
$path = route('categories.show.all', [$category->id]);
$attachments = $this->repository->getAttachments($category);

View File

@@ -48,6 +48,7 @@ use Illuminate\Support\Collection;
class BudgetController extends Controller
{
use DateCalculation, AugumentData;
/** @var GeneratorInterface Chart generation methods. */
protected $generator;
/** @var OperationsRepositoryInterface */
@@ -92,9 +93,9 @@ class BudgetController extends Controller
public function budget(Budget $budget): JsonResponse
{
/** @var Carbon $start */
$start = $this->repository->firstUseDate($budget) ?? session('start', new Carbon);
$start = $this->repository->firstUseDate($budget) ?? session('start', today(config('app.timezone')));
/** @var Carbon $end */
$end = session('end', new Carbon);
$end = session('end', today(config('app.timezone')));
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
@@ -102,7 +103,7 @@ class BudgetController extends Controller
$cache->addProperty($budget->id);
if ($cache->has()) {
return response()->json($cache->get()); // @codeCoverageIgnore
//return response()->json($cache->get()); // @codeCoverageIgnore
}
$step = $this->calculateStep($start, $end); // depending on diff, do something with range of chart.
$collection = new Collection([$budget]);
@@ -114,9 +115,6 @@ class BudgetController extends Controller
while ($end >= $loopStart) {
/** @var Carbon $currentEnd */
$loopEnd = app('navigation')->endOfPeriod($loopStart, $step);
if ('1Y' === $step) {
$loopEnd->subDay(); // @codeCoverageIgnore
}
$spent = $this->opsRepository->sumExpenses($loopStart, $loopEnd, null, $collection);
$label = trim(app('navigation')->periodShow($loopStart, $step));
@@ -141,7 +139,7 @@ class BudgetController extends Controller
'entries' => $defaultEntries,
];
foreach ($currency['spent'] as $label => $spent) {
$chartData[$currencyId]['entries'][$label] = round(bcmul($spent, '-1'), $currency['currency_decimal_places']);
$chartData[$currencyId]['entries'][$label] = bcmul($spent, '-1');
}
}
$data = $this->generator->multiSet(array_values($chartData));
@@ -179,19 +177,19 @@ class BudgetController extends Controller
if ($cache->has()) {
return response()->json($cache->get()); // @codeCoverageIgnore
}
$locale = app('steam')->getLocale();
$locale = app('steam')->getLocale();
$entries = [];
$amount = $budgetLimit->amount;
$budgetCollection = new Collection([$budget]);
while ($start <= $end) {
$spent = $this->opsRepository->spentInPeriod($budgetCollection, new Collection, $start, $start);
$amount = bcadd($amount, $spent);
$format = $start->formatLocalized((string)trans('config.month_and_day', [], $locale));
$format = $start->formatLocalized((string) trans('config.month_and_day', [], $locale));
$entries[$format] = $amount;
$start->addDay();
}
$data = $this->generator->singleSet((string)trans('firefly.left'), $entries);
$data = $this->generator->singleSet((string) trans('firefly.left'), $entries);
// add currency symbol from budget limit:
$data['datasets'][0]['currency_symbol'] = $budgetLimit->transactionCurrency->symbol;
$data['datasets'][0]['currency_code'] = $budgetLimit->transactionCurrency->code;
@@ -218,8 +216,9 @@ class BudgetController extends Controller
$cache->addProperty($budget->id);
$cache->addProperty($budgetLimitId);
$cache->addProperty('chart.budget.expense-asset');
$start = session()->get('start');
$end = session()->get('end');
$start = session('first', Carbon::now()->startOfYear());
$end = today();
if (null !== $budgetLimit) {
$start = $budgetLimit->start_date;
$end = $budgetLimit->end_date;
@@ -239,7 +238,7 @@ class BudgetController extends Controller
// group by asset account ID:
foreach ($journals as $journal) {
$key = sprintf('%d-%d', (int)$journal['source_account_id'], $journal['currency_id']);
$key = sprintf('%d-%d', (int) $journal['source_account_id'], $journal['currency_id']);
$result[$key] = $result[$key] ?? [
'amount' => '0',
'currency_symbol' => $journal['currency_symbol'],
@@ -252,13 +251,13 @@ class BudgetController extends Controller
$names = $this->getAccountNames(array_keys($result));
foreach ($result as $combinedId => $info) {
$parts = explode('-', $combinedId);
$assetId = (int)$parts[0];
$assetId = (int) $parts[0];
$title = sprintf('%s (%s)', $names[$assetId] ?? '(empty)', $info['currency_name']);
$chartData[$title]
= [
'amount' => $info['amount'],
'currency_symbol' => $info['currency_symbol'],
'currency_code' => $info['currency_code'],
'currency_code' => $info['currency_code'],
];
}
@@ -286,8 +285,8 @@ class BudgetController extends Controller
$cache->addProperty($budget->id);
$cache->addProperty($budgetLimitId);
$cache->addProperty('chart.budget.expense-category');
$start = session()->get('start');
$end = session()->get('end');
$start = session('first', Carbon::now()->startOfYear());
$end = today();
if (null !== $budgetLimit) {
$start = $budgetLimit->start_date;
$end = $budgetLimit->end_date;
@@ -319,12 +318,12 @@ class BudgetController extends Controller
$names = $this->getCategoryNames(array_keys($result));
foreach ($result as $combinedId => $info) {
$parts = explode('-', $combinedId);
$categoryId = (int)$parts[0];
$categoryId = (int) $parts[0];
$title = sprintf('%s (%s)', $names[$categoryId] ?? '(empty)', $info['currency_name']);
$chartData[$title] = [
'amount' => $info['amount'],
'currency_symbol' => $info['currency_symbol'],
'currency_code' => $info['currency_code'],
'currency_code' => $info['currency_code'],
];
}
$data = $this->generator->multiCurrencyPieChart($chartData);
@@ -352,8 +351,8 @@ class BudgetController extends Controller
$cache->addProperty($budget->id);
$cache->addProperty($budgetLimitId);
$cache->addProperty('chart.budget.expense-expense');
$start = session()->get('start');
$end = session()->get('end');
$start = session('first', Carbon::now()->startOfYear());
$end = today();
if (null !== $budgetLimit) {
$start = $budgetLimit->start_date;
$end = $budgetLimit->end_date;
@@ -385,13 +384,13 @@ class BudgetController extends Controller
$names = $this->getAccountNames(array_keys($result));
foreach ($result as $combinedId => $info) {
$parts = explode('-', $combinedId);
$opposingId = (int)$parts[0];
$opposingId = (int) $parts[0];
$name = $names[$opposingId] ?? 'no name';
$title = sprintf('%s (%s)', $name, $info['currency_name']);
$chartData[$title] = [
'amount' => $info['amount'],
'currency_symbol' => $info['currency_symbol'],
'currency_code' => $info['currency_code'],
'currency_code' => $info['currency_code'],
];
}
@@ -421,13 +420,13 @@ class BudgetController extends Controller
$cache->addProperty($end);
$cache->addProperty('chart.budget.frontpage');
if ($cache->has()) {
return response()->json($cache->get()); // @codeCoverageIgnore
return response()->json($cache->get()); // @codeCoverageIgnore
}
$budgets = $this->repository->getActiveBudgets();
$chartData = [
['label' => (string)trans('firefly.spent_in_budget'), 'entries' => [], 'type' => 'bar'],
['label' => (string)trans('firefly.left_to_spend'), 'entries' => [], 'type' => 'bar'],
['label' => (string)trans('firefly.overspent'), 'entries' => [], 'type' => 'bar'],
['label' => (string) trans('firefly.spent_in_budget'), 'entries' => [], 'type' => 'bar'],
['label' => (string) trans('firefly.left_to_spend'), 'entries' => [], 'type' => 'bar'],
['label' => (string) trans('firefly.overspent'), 'entries' => [], 'type' => 'bar'],
];
/** @var Budget $budget */
@@ -439,8 +438,8 @@ class BudgetController extends Controller
foreach ($spent as $entry) {
$title = sprintf('%s (%s)', $budget->name, $entry['currency_name']);
$chartData[0]['entries'][$title] = bcmul($entry['sum'], '-1'); // spent
$chartData[1]['entries'][$title] = 0; // left to spend
$chartData[2]['entries'][$title] = 0; // overspent
$chartData[1]['entries'][$title] = 0; // left to spend
$chartData[2]['entries'][$title] = 0; // overspent
}
}
if (0 !== $limits->count()) {
@@ -596,7 +595,7 @@ class BudgetController extends Controller
$currentStart = app('navigation')->addPeriod($currentStart, $preferredRange, 0);
}
$data = $this->generator->singleSet((string)trans('firefly.spent'), $chartData);
$data = $this->generator->singleSet((string) trans('firefly.spent'), $chartData);
$cache->store($data);
return response()->json($data);

View File

@@ -230,7 +230,7 @@ class CategoryController extends Controller
{
$carbon = null;
try {
$carbon = new Carbon;
$carbon = today(config('app.timezone'));
} catch (Exception $e) {
$e->getMessage();
}

View File

@@ -79,7 +79,7 @@ class PiggyBankController extends Controller
$locale =app('steam')->getLocale();
// get first event or start date of piggy bank or today
$startDate = $piggyBank->start_date ?? new Carbon;
$startDate = $piggyBank->start_date ?? today(config('app.timezone'));
/** @var PiggyBankEvent $first */
$firstEvent = $set->first();
@@ -87,7 +87,7 @@ class PiggyBankController extends Controller
// which ever is older:
$oldest = $startDate->lt($firstDate) ? $startDate : $firstDate;
$today = new Carbon;
$today = today(config('app.timezone'));
// depending on diff, do something with range of chart.
$step = $this->calculateStep($oldest, $today);

View File

@@ -142,6 +142,10 @@ class DebugController extends Controller
$bcscale = bcscale();
$layout = env('FIREFLY_III_LAYOUT');
// expected + found DB version:
$expectedDBversion = config('firefly.db_version');
$foundDBversion = \FireflyConfig::get('db_version',1)->data;
// some new vars.
$telemetry = true === config('firefly.send_telemetry') && true === config('firefly.feature_flags.telemetry');
$defaultLanguage = (string) config('firefly.default_language');
@@ -190,6 +194,8 @@ class DebugController extends Controller
compact(
'phpVersion',
'localeAttempts',
'expectedDBversion',
'foundDBversion',
'appEnv',
'appDebug',
'logChannel',

View File

@@ -79,7 +79,7 @@ class IndexController extends Controller
$generator->setExportTransactions(true);
// get first transaction in DB:
$firstDate = new Carbon;
$firstDate = today(config('app.timezone'));
$firstDate->subYear();
$journal = $this->journalRepository->firstNull();
if (null !== $journal) {

View File

@@ -126,7 +126,7 @@ class HomeController extends Controller
$end = session('end', Carbon::now()->endOfMonth());
/** @noinspection NullPointerExceptionInspection */
$accounts = $repository->getAccountsById($frontPage->data);
$today = new Carbon;
$today = today(config('app.timezone'));
/** @var BillRepositoryInterface $billRepository */
$billRepository = app(BillRepositoryInterface::class);

View File

@@ -68,7 +68,7 @@ class BoxController extends Controller
$start = session('start', Carbon::now()->startOfMonth());
/** @var Carbon $end */
$end = session('end', Carbon::now()->endOfMonth());
$today = new Carbon;
$today = today(config('app.timezone'));
$display = 2; // see method docs.
$boxTitle = (string) trans('firefly.spent');

View File

@@ -72,25 +72,24 @@ class RuleController extends Controller
*/
public function trigger(Request $request): JsonResponse
{
$count = (int) $request->get('count') > 0 ? (int) $request->get('count') : 1;
$keys = array_keys(config('firefly.rule-triggers'));
$triggers = [];
foreach ($keys as $key) {
if ('user_action' !== $key) {
$triggers[$key] = (string) trans('firefly.rule_trigger_' . $key . '_choice');
$count = (int) $request->get('count') > 0 ? (int) $request->get('count') : 1;
$operators = config('firefly.search.operators');
$triggers = [];
foreach ($operators as $key => $operator) {
if ('user_action' !== $key && false === $operator['alias']) {
$triggers[$key] = (string) trans(sprintf('firefly.rule_trigger_%s_choice', $key));
}
}
asort($triggers);
try {
$view = view('rules.partials.trigger', compact('triggers', 'count'))->render();
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::error(sprintf('Cannot render rules.partials.trigger: %s', $e->getMessage()));
$view = 'Could not render view.';
}
// @codeCoverageIgnoreEnd
return response()->json(['html' => $view]);
}

View File

@@ -76,7 +76,7 @@ class AmountController extends Controller
*/
public function add(PiggyBank $piggyBank)
{
$leftOnAccount = $this->piggyRepos->leftOnAccount($piggyBank, new Carbon);
$leftOnAccount = $this->piggyRepos->leftOnAccount($piggyBank, today(config('app.timezone')));
$savedSoFar = $this->piggyRepos->getCurrentAmount($piggyBank);
$leftToSave = bcsub($piggyBank->targetamount, $savedSoFar);
$maxAmount = min($leftOnAccount, $leftToSave);
@@ -95,7 +95,7 @@ class AmountController extends Controller
public function addMobile(PiggyBank $piggyBank)
{
/** @var Carbon $date */
$date = session('end', new Carbon);
$date = session('end', today(config('app.timezone')));
$leftOnAccount = $this->piggyRepos->leftOnAccount($piggyBank, $date);
$savedSoFar = $this->piggyRepos->getCurrentAmount($piggyBank);
$leftToSave = bcsub($piggyBank->targetamount, $savedSoFar);

View File

@@ -96,7 +96,7 @@ class CreateController extends Controller
{
$data = $request->getPiggyBankData();
if (null === $data['startdate']) {
$data['startdate'] = new Carbon;
$data['startdate'] = today(config('app.timezone'));
}
$piggyBank = $this->piggyRepos->store($data);

View File

@@ -82,6 +82,7 @@ class ProfileController extends Controller
$loginProvider = config('firefly.login_provider');
$authGuard = config('firefly.authentication_guard');
$this->externalIdentity = 'eloquent' !== $loginProvider || 'web' !== $authGuard;
Log::debug(sprintf('ProfileController::__construct(). Login provider is "%s", authentication guard is "%s"',$loginProvider, $authGuard));
$this->middleware(IsDemoUser::class)->except(['index']);
}
@@ -345,7 +346,6 @@ class ProfileController extends Controller
$userId = $user->id;
$enabled2FA = null !== $user->mfa_secret;
$mfaBackupCount = count(app('preferences')->get('mfa_recovery', [])->data);
$this->createOAuthKeys();
if (0 === $count) {

View File

@@ -85,7 +85,7 @@ class CreateController extends Controller
{
$budgets = app('expandedform')->makeSelectListWithEmpty($this->budgets->getActiveBudgets());
$defaultCurrency = app('amount')->getDefaultCurrency();
$tomorrow = new Carbon;
$tomorrow = today(config('app.timezone'));
$oldRepetitionType = $request->old('repetition_type');
$tomorrow->addDay();
@@ -179,7 +179,7 @@ class CreateController extends Controller
{
$budgets = app('expandedform')->makeSelectListWithEmpty($this->budgets->getActiveBudgets());
$defaultCurrency = app('amount')->getDefaultCurrency();
$tomorrow = new Carbon;
$tomorrow = today(config('app.timezone'));
$oldRepetitionType = $request->old('repetition_type');
$tomorrow->addDay();

View File

@@ -85,8 +85,8 @@ class IndexController extends Controller
$page = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
$collection = $this->recurring->get();
$today = new Carbon;
$year = new Carbon;
$today = today(config('app.timezone'));
$year = today(config('app.timezone'));
// split collection
$total = $collection->count();

View File

@@ -84,7 +84,7 @@ class ShowController extends Controller
$array = $transformer->transform($recurrence);
$groups = $this->recurring->getTransactions($recurrence);
$today = new Carbon;
$today = today(config('app.timezone'));
$array['repeat_until'] = null !== $array['repeat_until'] ? new Carbon($array['repeat_until']) : null;
// transform dates back to Carbon objects:

View File

@@ -263,7 +263,7 @@ class ReportController extends Controller
public function index(AccountRepositoryInterface $repository)
{
/** @var Carbon $start */
$start = clone session('first', new Carbon);
$start = clone session('first', today(config('app.timezone')));
$months = $this->helper->listOfMonths($start);
$customFiscalYear = app('preferences')->get('customFiscalYear', 0)->data;
$accounts = $repository->getAccountsByType(

View File

@@ -32,11 +32,15 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Support\Http\Controllers\ModelInformation;
use FireflyIII\Support\Http\Controllers\RuleManagement;
use FireflyIII\Support\Search\OperatorQuerySearch;
use FireflyIII\Support\Search\SearchInterface;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
use Illuminate\View\View;
use Log;
use Throwable;
/**
* Class CreateController
@@ -44,8 +48,8 @@ use Illuminate\View\View;
class CreateController extends Controller
{
use RuleManagement, ModelInformation;
/** @var RuleRepositoryInterface Rule repository */
private $ruleRepos;
private RuleRepositoryInterface $ruleRepos;
/**
* RuleController constructor.
@@ -71,8 +75,8 @@ class CreateController extends Controller
/**
* Create a new rule. It will be stored under the given $ruleGroup.
*
* @param Request $request
* @param RuleGroup $ruleGroup
* @param Request $request
* @param RuleGroup|null $ruleGroup
*
* @return Factory|View
*/
@@ -86,6 +90,20 @@ class CreateController extends Controller
$oldTriggers = [];
$oldActions = [];
// build triggers from query, if present.
$query = (string) $request->get('from_query');
if ('' !== $query) {
$search = app(SearchInterface::class);
$search->parseQuery($query);
$words = $search->getWordsAsString();
$operators = $search->getOperators()->toArray();
if ('' !== $words) {
session()->flash('warning', trans('firefly.rule_from_search_words', ['string' => $words]));
array_push($operators, ['type' => 'description_contains', 'value' => $words]);
}
$oldTriggers = $this->parseFromOperators($operators);
}
// restore actions and triggers from old input:
if ($request->old()) {
$oldTriggers = $this->getPreviousTriggers($request);
@@ -143,6 +161,12 @@ class CreateController extends Controller
$oldTriggers = $this->getTriggersForBill($bill);
$oldActions = $this->getActionsForBill($bill);
// restore actions and triggers from old input:
if ($request->old()) {
$oldTriggers = $this->getPreviousTriggers($request);
$oldActions = $this->getPreviousActions($request);
}
$triggerCount = count($oldTriggers);
$actionCount = count($oldActions);
$subTitleIcon = 'fa-clone';
@@ -177,10 +201,8 @@ class CreateController extends Controller
$subTitle = (string) trans('firefly.make_new_rule_no_group');
// get triggers and actions for journal.
$oldTriggers = $this->getTriggersForJournal($journal);
$oldActions = [];
$triggerCount = count($oldTriggers);
$actionCount = count($oldActions);
$oldTriggers = $this->getTriggersForJournal($journal);
$oldActions = [];
$this->createDefaultRuleGroup();
$this->createDefaultRule();
@@ -192,6 +214,15 @@ class CreateController extends Controller
'description' => (string) trans('firefly.new_rule_for_journal_description', ['description' => $journal->description]),
];
// restore actions and triggers from old input:
if ($request->old()) {
$oldTriggers = $this->getPreviousTriggers($request);
$oldActions = $this->getPreviousActions($request);
}
$triggerCount = count($oldTriggers);
$actionCount = count($oldActions);
// flash old data
$request->session()->flash('preFilled', $preFilled);

View File

@@ -30,6 +30,8 @@ use FireflyIII\Models\Rule;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Support\Http\Controllers\RenderPartialViews;
use FireflyIII\Support\Http\Controllers\RuleManagement;
use FireflyIII\Support\Search\OperatorQuerySearch;
use FireflyIII\Support\Search\SearchInterface;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@@ -81,15 +83,31 @@ class EditController extends Controller
$actionCount = 0;
$oldActions = [];
$oldTriggers = [];
// build triggers from query, if present.
$query = (string) $request->get('from_query');
if ('' !== $query) {
$search = app(SearchInterface::class);
$search->parseQuery($query);
$words = $search->getWordsAsString();
$operators = $search->getOperators()->toArray();
if ('' !== $words) {
session()->flash('warning', trans('firefly.rule_from_search_words', ['string' => $words]));
array_push($operators, ['type' => 'description_contains', 'value' => $words]);
}
$oldTriggers = $this->parseFromOperators($operators);
}
// has old input?
if (count($request->old()) > 0) {
$oldTriggers = $this->getPreviousTriggers($request);
$triggerCount = count($oldTriggers);
$oldActions = $this->getPreviousActions($request);
$actionCount = count($oldActions);
}
$triggerCount = count($oldTriggers);
$actionCount = count($oldActions);
// overrule old input when it has no rule data:
// overrule old input and query data when it has no rule data:
if (0 === $triggerCount && 0 === $actionCount) {
$oldTriggers = $this->getCurrentTriggers($rule);
$triggerCount = count($oldTriggers);
@@ -146,4 +164,45 @@ class EditController extends Controller
return $redirect;
}
/**
* @param array $submittedOperators
* @return array
*/
private function parseFromOperators(array $submittedOperators): array
{
// TODO duplicated code.
$operators = config('firefly.search.operators');
$renderedEntries = [];
$triggers = [];
foreach ($operators as $key => $operator) {
if ('user_action' !== $key && false === $operator['alias']) {
$triggers[$key] = (string) trans(sprintf('firefly.rule_trigger_%s_choice', $key));
}
}
asort($triggers);
$index = 0;
foreach ($submittedOperators as $operator) {
try {
$renderedEntries[] = view(
'rules.partials.trigger',
[
'oldTrigger' => OperatorQuerySearch::getRootOperator($operator['type']),
'oldValue' => $operator['value'],
'oldChecked' => 1 === (int) ($oldTrigger['stop_processing'] ?? '0'),
'count' => $index + 1,
'triggers' => $triggers,
]
)->render();
} catch (Throwable $e) {
Log::debug(sprintf('Throwable was thrown in getPreviousTriggers(): %s', $e->getMessage()));
Log::error($e->getTraceAsString());
}
$index++;
}
return $renderedEntries;
}
}

View File

@@ -25,12 +25,15 @@ namespace FireflyIII\Http\Controllers\Rule;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup;
use FireflyIII\Models\RuleTrigger;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\Support\Http\Controllers\RuleManagement;
use FireflyIII\Support\Search\OperatorQuerySearch;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
@@ -40,10 +43,9 @@ use Illuminate\View\View;
class IndexController extends Controller
{
use RuleManagement;
/** @var RuleGroupRepositoryInterface Rule group repository */
private $ruleGroupRepos;
/** @var RuleRepositoryInterface Rule repository. */
private $ruleRepos;
private RuleGroupRepositoryInterface $ruleGroupRepos;
private RuleRepositoryInterface $ruleRepos;
/**
* RuleController constructor.
@@ -82,6 +84,20 @@ class IndexController extends Controller
return view('rules.index', compact('ruleGroups'));
}
/**
* @param Rule $rule
* @return RedirectResponse
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function search(Rule $rule): RedirectResponse
{
$route = route('search.index');
$query = $this->ruleRepos->getSearchQuery($rule);
$route = sprintf('%s?%s', $route, http_build_query(['search' => $query, 'rule' => $rule->id]));
return redirect($route);
}
/**
* Stop action for reordering of rule actions.
*

View File

@@ -25,17 +25,14 @@ namespace FireflyIII\Http\Controllers\Rule;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\SelectTransactionsRequest;
use FireflyIII\Http\Requests\TestRuleFormRequest;
use FireflyIII\Jobs\ExecuteRuleOnExistingTransactions;
use FireflyIII\Models\Rule;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Models\RuleTrigger;
use FireflyIII\Support\Http\Controllers\RequestInformation;
use FireflyIII\Support\Http\Controllers\RuleManagement;
use FireflyIII\TransactionRules\Engine\RuleEngine;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use FireflyIII\TransactionRules\TransactionMatcher;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
@@ -53,8 +50,6 @@ use Throwable;
class SelectController extends Controller
{
use RuleManagement, RequestInformation;
/** @var AccountRepositoryInterface The account repository */
private $accountRepos;
/**
* RuleController constructor.
@@ -68,8 +63,6 @@ class SelectController extends Controller
app('view')->share('title', (string) trans('firefly.rules'));
app('view')->share('mainTitleIcon', 'fa-random');
$this->accountRepos = app(AccountRepositoryInterface::class);
return $next($request);
}
);
@@ -88,29 +81,22 @@ class SelectController extends Controller
// Get parameters specified by the user
/** @var User $user */
$user = auth()->user();
$accounts = $this->accountRepos->getAccountsById($request->get('accounts'));
$startDate = new Carbon($request->get('start_date'));
$endDate = new Carbon($request->get('end_date'));
$rules = [$rule->id];
$accounts = implode(',', $request->get('accounts'));
$startDate = new Carbon($request->get('start'));
$endDate = new Carbon($request->get('end'));
/** @var RuleEngine $ruleEngine */
$ruleEngine = app(RuleEngine::class);
$ruleEngine->setUser(auth()->user());
$ruleEngine->setRulesToApply($rules);
$ruleEngine->setTriggerMode(RuleEngine::TRIGGER_BOTH);
// create new rule engine:
$newRuleEngine = app(RuleEngineInterface::class);
$newRuleEngine->setUser($user);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($accounts);
$collector->setRange($startDate, $endDate);
$journals = $collector->getExtractedJournals();
// add extra operators:
$newRuleEngine->addOperator(['type' => 'date_after', 'value' => $startDate->format('Y-m-d')]);
$newRuleEngine->addOperator(['type' => 'date_before', 'value' => $endDate->format('Y-m-d')]);
$newRuleEngine->addOperator(['type' => 'account_id', 'value' => $accounts]);
/** @var array $journal */
foreach ($journals as $journal) {
Log::debug('Start of new journal.');
$ruleEngine->processJournalArray($journal);
Log::debug('Done with all rules for this group + done with journal.');
}
// set rules:
$newRuleEngine->setRules(new Collection([$rule]));
$newRuleEngine->fire();
// Tell the user that the job is queued
session()->flash('success', (string) trans('firefly.applied_rule_selection', ['title' => $rule->title]));
@@ -128,8 +114,8 @@ class SelectController extends Controller
*/
public function selectTransactions(Rule $rule)
{
if(false===$rule->active) {
session()->flash('warning',trans('firefly.cannot_fire_inactive_rules'));
if (false === $rule->active) {
session()->flash('warning', trans('firefly.cannot_fire_inactive_rules'));
return redirect(route('rules.index'));
}
// does the user have shared accounts?
@@ -144,10 +130,6 @@ class SelectController extends Controller
* This method allows the user to test a certain set of rule triggers. The rule triggers are passed along
* using the URL parameters (GET), and are usually put there using a Javascript thing.
*
* This method will parse and validate those rules and create a "TransactionMatcher" which will attempt
* to find transaction journals matching the users input. A maximum range of transactions to try (range) and
* a maximum number of transactions to return (limit) are set as well.
*
* @param TestRuleFormRequest $request
*
* @return JsonResponse
@@ -155,46 +137,46 @@ class SelectController extends Controller
*/
public function testTriggers(TestRuleFormRequest $request): JsonResponse
{
// build trigger array from response
$triggers = $this->getValidTriggerList($request);
// build fake rule
$rule = new Rule;
$triggers = new Collection;
$rule->strict = '1' === $request->get('strict');
if (0 === count($triggers)) {
// build trigger array from response
$textTriggers = $this->getValidTriggerList($request);
// warn if nothing.
if (0 === count($textTriggers)) {
return response()->json(['html' => '', 'warning' => (string) trans('firefly.warning_no_valid_triggers')]); // @codeCoverageIgnore
}
$limit = (int) config('firefly.test-triggers.limit');
$range = (int) config('firefly.test-triggers.range');
$matchingTransactions = new Collection;
$strict = '1' === $request->get('strict');
/** @var TransactionMatcher $matcher */
$matcher = app(TransactionMatcher::class);
$matcher->setSearchLimit($range);
$matcher->setTriggeredLimit($limit);
$matcher->setTriggers($triggers);
$matcher->setStrict($strict);
try {
$matchingTransactions = $matcher->findTransactionsByTriggers();
// @codeCoverageIgnoreStart
} catch (FireflyException $exception) {
Log::error(sprintf('Could not grab transactions in testTriggers(): %s', $exception->getMessage()));
Log::error($exception->getTraceAsString());
foreach ($textTriggers as $textTrigger) {
$trigger = new RuleTrigger;
$trigger->trigger_type = $textTrigger['type'];
$trigger->trigger_value = $textTrigger['value'];
$triggers->push($trigger);
}
// @codeCoverageIgnoreStart
$rule->ruleTriggers = $triggers;
// create new rule engine:
$newRuleEngine = app(RuleEngineInterface::class);
// set rules:
$newRuleEngine->setRules(new Collection([$rule]));
$collection = $newRuleEngine->find();
$collection = $collection->slice(0, 20);
// Warn the user if only a subset of transactions is returned
$warning = '';
if (count($matchingTransactions) === $limit) {
$warning = (string) trans('firefly.warning_transaction_subset', ['max_num_transactions' => $limit]); // @codeCoverageIgnore
}
if (0 === count($matchingTransactions)) {
$warning = (string) trans('firefly.warning_no_matching_transactions', ['num_transactions' => $range]); // @codeCoverageIgnore
if (0 === count($collection)) {
$warning = (string) trans('firefly.warning_no_matching_transactions'); // @codeCoverageIgnore
}
// Return json response
$view = 'ERROR, see logs.';
try {
$view = view('list.journals-array-tiny', ['journals' => $matchingTransactions])->render();
$view = view('list.journals-array-tiny', ['groups' => $collection])->render();
// @codeCoverageIgnoreStart
} catch (Throwable $exception) {
Log::error(sprintf('Could not render view in testTriggers(): %s', $exception->getMessage()));
@@ -211,10 +193,6 @@ class SelectController extends Controller
* This method allows the user to test a certain set of rule triggers. The rule triggers are grabbed from
* the rule itself.
*
* This method will parse and validate those rules and create a "TransactionMatcher" which will attempt
* to find transaction journals matching the users input. A maximum range of transactions to try (range) and
* a maximum number of transactions to return (limit) are set as well.
*
* @param Rule $rule
*
* @return JsonResponse
@@ -228,37 +206,24 @@ class SelectController extends Controller
return response()->json(['html' => '', 'warning' => (string) trans('firefly.warning_no_valid_triggers')]); // @codeCoverageIgnore
}
$limit = (int) config('firefly.test-triggers.limit');
$range = (int) config('firefly.test-triggers.range');
$matchingTransactions = new Collection;
/** @var TransactionMatcher $matcher */
$matcher = app(TransactionMatcher::class);
$matcher->setTriggeredLimit($limit);
$matcher->setSearchLimit($range);
$matcher->setRule($rule);
try {
$matchingTransactions = $matcher->findTransactionsByRule();
// @codeCoverageIgnoreStart
} catch (FireflyException $exception) {
Log::error(sprintf('Could not grab transactions in testTriggersByRule(): %s', $exception->getMessage()));
Log::error($exception->getTraceAsString());
}
// @codeCoverageIgnoreEnd
// create new rule engine:
$newRuleEngine = app(RuleEngineInterface::class);
// set rules:
$newRuleEngine->setRules(new Collection([$rule]));
$collection = $newRuleEngine->find();
$collection = $collection->slice(0, 20);
// Warn the user if only a subset of transactions is returned
$warning = '';
if (count($matchingTransactions) === $limit) {
$warning = (string) trans('firefly.warning_transaction_subset', ['max_num_transactions' => $limit]); // @codeCoverageIgnore
}
if (0 === count($matchingTransactions)) {
$warning = (string) trans('firefly.warning_no_matching_transactions', ['num_transactions' => $range]); // @codeCoverageIgnore
if (0 === count($collection)) {
$warning = (string) trans('firefly.warning_no_matching_transactions'); // @codeCoverageIgnore
}
// Return json response
$view = 'ERROR, see logs.';
try {
$view = view('list.journals-array-tiny', ['journals' => $matchingTransactions])->render();
$view = view('list.journals-array-tiny', ['groups' => $collection])->render();
// @codeCoverageIgnoreStart
} catch (Throwable $exception) {
Log::error(sprintf('Could not render view in testTriggersByRule(): %s', $exception->getMessage()));

View File

@@ -26,29 +26,22 @@ namespace FireflyIII\Http\Controllers\RuleGroup;
use Carbon\Carbon;
use Exception;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\SelectTransactionsRequest;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\TransactionRules\Engine\RuleEngine;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
use Log;
/**
* Class ExecutionController
*/
class ExecutionController extends Controller
{
/** @var AccountRepositoryInterface */
private $repository;
/** @var RuleGroupRepositoryInterface */
private $ruleGroupRepository;
private RuleGroupRepositoryInterface $ruleGroupRepository;
/**
* ExecutionController constructor.
@@ -64,7 +57,6 @@ class ExecutionController extends Controller
app('view')->share('title', (string) trans('firefly.rules'));
app('view')->share('mainTitleIcon', 'fa-random');
$this->repository = app(AccountRepositoryInterface::class);
$this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
return $next($request);
@@ -79,42 +71,30 @@ class ExecutionController extends Controller
* @param SelectTransactionsRequest $request
* @param RuleGroup $ruleGroup
*
* @throws Exception
* @return RedirectResponse
* @throws Exception
*/
public function execute(SelectTransactionsRequest $request, RuleGroup $ruleGroup): RedirectResponse
{
// Get parameters specified by the user
$accounts = $this->repository->getAccountsById($request->get('accounts'));
$startDate = new Carbon($request->get('start_date'));
$endDate = new Carbon($request->get('end_date'));
/** @var User $user */
$user = auth()->user();
$accounts = implode(',', $request->get('accounts'));
$startDate = new Carbon($request->get('start'));
$endDate = new Carbon($request->get('end'));
$rules = $this->ruleGroupRepository->getActiveRules($ruleGroup);
// create new rule engine:
$newRuleEngine = app(RuleEngineInterface::class);
$newRuleEngine->setUser($user);
// start looping.
/** @var RuleEngine $ruleEngine */
$ruleEngine = app(RuleEngine::class);
$ruleEngine->setUser(auth()->user());
// add extra operators:
$newRuleEngine->addOperator(['type' => 'date_after', 'value' => $startDate->format('Y-m-d')]);
$newRuleEngine->addOperator(['type' => 'date_before', 'value' => $endDate->format('Y-m-d')]);
$newRuleEngine->addOperator(['type' => 'account_id', 'value' => $accounts]);
$rules = [];
/** @var Rule $rule */
foreach ($this->ruleGroupRepository->getActiveRules($ruleGroup) as $rule) {
$rules[] = $rule->id;
}
$ruleEngine->setRulesToApply($rules);
$ruleEngine->setTriggerMode(RuleEngine::TRIGGER_STORE);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($accounts);
$collector->setRange($startDate, $endDate);
$journals = $collector->getExtractedJournals();
/** @var array $journal */
foreach ($journals as $journal) {
Log::debug('Start of new journal.');
$ruleEngine->processJournalArray($journal);
Log::debug('Done with all rules for this group + done with journal.');
}
// set rules:
$newRuleEngine->setRules($rules);
$newRuleEngine->fire();
// Tell the user that the job is queued
session()->flash('success', (string) trans('firefly.applied_rule_group_selection', ['title' => $ruleGroup->title]));

View File

@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Support\Search\SearchInterface;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\JsonResponse;
@@ -62,15 +63,33 @@ class SearchController extends Controller
*/
public function index(Request $request, SearchInterface $searcher)
{
$fullQuery = (string) $request->get('search');
$page = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page');
// search params:
$fullQuery = (string) $request->get('search');
$page = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page');
$ruleId = (int) $request->get('rule');
$rule = null;
$ruleChanged = false;
// find rule, check if query is different, offer to update.
$ruleRepository = app(RuleRepositoryInterface::class);
$rule = $ruleRepository->find($ruleId);
if (null !== $rule) {
$originalQuery = $ruleRepository->getSearchQuery($rule);
if ($originalQuery !== $fullQuery) {
$ruleChanged = true;
}
}
// parse search terms:
$searcher->parseQuery($fullQuery);
$query = $searcher->getWordsAsString();
$modifiers = $searcher->getModifiers();
$subTitle = (string) trans('breadcrumbs.search_result', ['query' => $query]);
return view('search.index', compact('query', 'modifiers', 'page', 'fullQuery', 'subTitle'));
// words from query and operators:
$query = $searcher->getWordsAsString();
$operators = $searcher->getOperators();
$subTitle = (string) trans('breadcrumbs.search_result', ['query' => $fullQuery]);
return view('search.index', compact('query', 'operators', 'page', 'rule', 'fullQuery', 'subTitle', 'ruleId', 'ruleChanged'));
}
/**
@@ -94,6 +113,7 @@ class SearchController extends Controller
$parameters = ['search' => $fullQuery];
$url = route('search.index') . '?' . http_build_query($parameters);
$groups->setPath($url);
try {
$html = view('search.search', compact('groups', 'hasPages', 'searchTime'))->render();
// @codeCoverageIgnoreStart

View File

@@ -180,8 +180,9 @@ class TagController extends Controller
public function index(TagRepositoryInterface $repository)
{
// start with oldest tag
$oldestTagDate = null === $repository->oldestTag() ? clone session('first') : $repository->oldestTag()->date;
$newestTagDate = null === $repository->newestTag() ? new Carbon : $repository->newestTag()->date;
$first = session('first', today()) ?? today();
$oldestTagDate = null === $repository->oldestTag() ? clone $first : $repository->oldestTag()->date;
$newestTagDate = null === $repository->newestTag() ? today() : $repository->newestTag()->date;
$oldestTagDate->startOfYear();
$newestTagDate->endOfYear();
$tags = [];
@@ -250,7 +251,7 @@ class TagController extends Controller
);
$startPeriod = $this->repository->firstUseDate($tag);
$startPeriod = $startPeriod ?? new Carbon;
$startPeriod = $startPeriod ?? today(config('app.timezone'));
$endPeriod = clone $end;
$periods = $this->getTagPeriodOverview($tag, $startPeriod, $endPeriod);
$path = route('tags.show', [$tag->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
@@ -284,8 +285,8 @@ class TagController extends Controller
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
$periods = [];
$subTitle = (string) trans('firefly.all_journals_for_tag', ['tag' => $tag->tag]);
$start = $this->repository->firstUseDate($tag) ?? new Carbon;
$end = $this->repository->lastUseDate($tag) ?? new Carbon;
$start = $this->repository->firstUseDate($tag) ?? today(config('app.timezone'));
$end = $this->repository->lastUseDate($tag) ?? today(config('app.timezone'));
$attachments = $this->repository->getAttachments($tag);
$path = route('tags.show', [$tag->id, 'all']);
$location = $this->repository->getLocation($tag);

View File

@@ -54,8 +54,7 @@ class ConvertController extends Controller
{
use ModelInformation, UserNavigation;
/** @var JournalRepositoryInterface Journals and transactions overview */
private $repository;
private JournalRepositoryInterface $repository;
/**
* ConvertController constructor.
@@ -260,7 +259,7 @@ class ConvertController extends Controller
// group accounts:
/** @var Account $account */
foreach ($accountList as $account) {
$balance = app('steam')->balance($account);
$balance = app('steam')->balance($account, today());
$currency = $repository->getAccountCurrency($account) ?? $defaultCurrency;
$role = (string) $repository->getMetaValue($account, 'account_role');
if ('' === $role) {
@@ -289,7 +288,7 @@ class ConvertController extends Controller
// group accounts:
/** @var Account $account */
foreach ($accountList as $account) {
$balance = app('steam')->balance($account);
$balance = app('steam')->balance($account, today());
$currency = $repository->getAccountCurrency($account) ?? $defaultCurrency;
$role = 'l_' . $account->accountType->type;
$key = (string) trans('firefly.opt_group_' . $role);

View File

@@ -71,8 +71,9 @@ class CreateController extends Controller
app('preferences')->mark();
$title = $newGroup->title ?? $newGroup->transactionJournals->first()->description;
$link = route('transactions.show', [$newGroup->id]);
session()->flash('success', trans('firefly.stored_journal', ['description' => $title]));
session()->flash('success_uri', $link);
return redirect(route('transactions.show', [$newGroup->id]));
}

View File

@@ -144,7 +144,7 @@ class IndexController extends Controller
$first = $repository->firstNull();
$start = null === $first ? new Carbon : $first->date;
$last = $this->repository->getLast();
$end = $last ? $last->date : new Carbon;
$end = $last ? $last->date : today(config('app.timezone'));
$subTitle = (string) trans('firefly.all_' . $objectType);
/** @var GroupCollectorInterface $collector */

View File

@@ -18,7 +18,6 @@
* 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/>.
*/
/** @noinspection PhpDynamicAsStaticMethodCallInspection */
declare(strict_types=1);
namespace FireflyIII\Http\Requests;

View File

@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Requests;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
/**
@@ -31,6 +32,7 @@ use Illuminate\Foundation\Http\FormRequest;
*/
class NewUserFormRequest extends FormRequest
{
use ConvertsDataTypes;
/**
* Verify the request.
*

View File

@@ -144,7 +144,7 @@ class RecurrenceFormRequest extends FormRequest
*/
public function rules(): array
{
$today = new Carbon;
$today = today(config('app.timezone'));
$tomorrow = Carbon::now()->addDay();
$rules = [
// mandatory info for recurrence.

View File

@@ -151,7 +151,7 @@ class ReportFormRequest extends FormRequest
*/
public function getEndDate(): Carbon
{
$date = new Carbon;
$date = today(config('app.timezone'));
$range = $this->get('daterange');
$parts = explode(' - ', (string) $range);
if (2 === count($parts)) {
@@ -179,7 +179,7 @@ class ReportFormRequest extends FormRequest
*/
public function getStartDate(): Carbon
{
$date = new Carbon;
$date = today(config('app.timezone'));
$range = $this->get('daterange');
$parts = explode(' - ', (string) $range);
if (2 === count($parts)) {

View File

@@ -24,6 +24,7 @@ namespace FireflyIII\Http\Requests;
use FireflyIII\Models\Rule;
use FireflyIII\Support\Request\ConvertsDataTypes;
use FireflyIII\Support\Request\GetRuleConfiguration;
use Illuminate\Foundation\Http\FormRequest;
/**
@@ -31,7 +32,7 @@ use Illuminate\Foundation\Http\FormRequest;
*/
class RuleFormRequest extends FormRequest
{
use ConvertsDataTypes;
use ConvertsDataTypes, GetRuleConfiguration;
/**
* Verify the request.
*
@@ -73,14 +74,14 @@ class RuleFormRequest extends FormRequest
*/
public function rules(): array
{
$validTriggers = array_keys(config('firefly.rule-triggers'));
$validTriggers = $this->getTriggers();
$validActions = array_keys(config('firefly.rule-actions'));
// some actions require text (aka context):
$contextActions = implode(',', config('firefly.context-rule-actions'));
// some triggers require text (aka context):
$contextTriggers = implode(',', config('firefly.context-rule-triggers'));
$contextTriggers = implode(',', $this->getTriggersWithContext());
// initial set of rules:
$rules = [

View File

@@ -57,8 +57,8 @@ class SelectTransactionsRequest extends FormRequest
$today = Carbon::now()->addDay()->format('Y-m-d');
return [
'start_date' => 'required|date|after:' . $first,
'end_date' => 'required|date|before:' . $today,
'start' => 'required|date|after:' . $first,
'end' => 'required|date|before:' . $today,
'accounts' => 'required',
'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts',
];

View File

@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Requests;
use FireflyIII\Support\Request\GetRuleConfiguration;
use Illuminate\Foundation\Http\FormRequest;
/**
@@ -31,6 +32,7 @@ use Illuminate\Foundation\Http\FormRequest;
*/
class TestRuleFormRequest extends FormRequest
{
use GetRuleConfiguration;
/**
* Verify the request.
*
@@ -51,7 +53,7 @@ class TestRuleFormRequest extends FormRequest
public function rules(): array
{
// fixed
$validTriggers = array_keys(config('firefly.rule-triggers'));
$validTriggers = $this->getTriggers();
$rules = [
'rule-trigger.*' => 'required|min:1|in:' . implode(',', $validTriggers),
'rule-trigger-value.*' => 'required|min:1|ruleTriggerValue',

View File

@@ -151,7 +151,7 @@ class SubmitTelemetryData implements ShouldQueue
{
$telemetry->each(
static function (Telemetry $entry) {
$entry->submitted = new Carbon;
$entry->submitted = today(config('app.timezone'));
$entry->save();
}
);

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/*
* NewIPAddressWarningMail.php
* Copyright (c) 2020 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\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Laravel\Passport\Client;
/**
* Class NewIPAddressWarningMail
*/
class NewIPAddressWarningMail extends Mailable
{
use Queueable, SerializesModels;
public string $ipAddress;
public string $time;
public string $host;
/**
* OAuthTokenCreatedMail constructor.
*
* @param string $ipAddress
*/
public function __construct(string $ipAddress)
{
$this->ipAddress = $ipAddress;
}
/**
* Build the message.
*
* @return $this
*/
public function build(): self
{
// time
$this->time = now()->formatLocalized((string)trans('config.date_time'));
$this->host = '';
$host = gethostbyaddr($this->ipAddress);
if($host !== $this->ipAddress) {
$this->host = $host;
}
return $this->view('emails.new-ip-html')->text('emails.new-ip-text')
->subject((string) trans('email.login_from_new_ip'));
}
}

View File

@@ -24,6 +24,7 @@ namespace FireflyIII\Providers;
use Exception;
use FireflyIII\Events\AdminRequestedTestMessage;
use FireflyIII\Events\DetectedNewIPAddress;
use FireflyIII\Events\RegisteredUser;
use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Events\RequestedReportOnJournals;
@@ -67,6 +68,10 @@ class EventServiceProvider extends ServiceProvider
Login::class => [
'FireflyIII\Handlers\Events\UserEventHandler@checkSingleUserIsAdmin',
'FireflyIII\Handlers\Events\UserEventHandler@demoUserBackToEnglish',
'FireflyIII\Handlers\Events\UserEventHandler@storeUserIPAddress',
],
DetectedNewIPAddress::class => [
'FireflyIII\Handlers\Events\UserEventHandler@notifyNewIPAddress',
],
RequestedVersionCheckStatus::class => [
'FireflyIII\Handlers\Events\VersionCheckEventHandler@checkForUpdates',

View File

@@ -22,7 +22,6 @@ declare(strict_types=1);
namespace FireflyIII\Providers;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Generator\Chart\Basic\ChartJsGenerator;
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Helpers\Attachments\AttachmentHelper;
@@ -45,11 +44,8 @@ use FireflyIII\Repositories\TransactionType\TransactionTypeRepository;
use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface;
use FireflyIII\Repositories\User\UserRepository;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Services\Currency\ExchangeRateInterface;
use FireflyIII\Services\FireflyIIIOrg\Update\UpdateRequest;
use FireflyIII\Services\FireflyIIIOrg\Update\UpdateRequestInterface;
use FireflyIII\Services\IP\IpifyOrg;
use FireflyIII\Services\IP\IPRetrievalInterface;
use FireflyIII\Services\Password\PwndVerifierV2;
use FireflyIII\Services\Password\Verifier;
use FireflyIII\Support\Amount;
@@ -63,6 +59,8 @@ use FireflyIII\Support\Navigation;
use FireflyIII\Support\Preferences;
use FireflyIII\Support\Steam;
use FireflyIII\Support\Telemetry;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use FireflyIII\TransactionRules\Engine\SearchRuleEngine;
use FireflyIII\Validation\FireflyValidator;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;
@@ -192,6 +190,19 @@ class FireflyServiceProvider extends ServiceProvider
}
);
$this->app->bind(
RuleEngineInterface::class,
static function (Application $app) {
/** @var SearchRuleEngine $engine */
$engine = app(SearchRuleEngine::class);
if ($app->auth->check()) {
$engine->setUser(auth()->user());
}
return $engine;
}
);
// more generators:
$this->app->bind(PopupReportInterface::class, PopupReport::class);
$this->app->bind(HelpInterface::class, Help::class);
@@ -200,18 +211,9 @@ class FireflyServiceProvider extends ServiceProvider
$this->app->bind(UpdateRequestInterface::class, UpdateRequest::class);
$this->app->bind(TelemetryRepositoryInterface::class, TelemetryRepository::class);
$class = (string) config(sprintf('firefly.cer_providers.%s', (string) config('firefly.cer_provider')));
if ('' === $class) {
throw new FireflyException('Invalid currency exchange rate provider. Cannot continue.');
}
$this->app->bind(ExchangeRateInterface::class, $class);
// password verifier thing
$this->app->bind(Verifier::class, PwndVerifierV2::class);
// IP thing:
$this->app->bind(IPRetrievalInterface::class, IpifyOrg::class);
// net worth thing.
$this->app->bind(NetWorthInterface::class, NetWorth::class);
}

View File

@@ -22,7 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Providers;
use FireflyIII\Support\Search\BetterQuerySearch;
use FireflyIII\Support\Search\OperatorQuerySearch;
use FireflyIII\Support\Search\SearchInterface;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;
@@ -48,8 +48,8 @@ class SearchServiceProvider extends ServiceProvider
$this->app->bind(
SearchInterface::class,
function (Application $app) {
/** @var BetterQuerySearch $search */
$search = app(BetterQuerySearch::class);
/** @var OperatorQuerySearch $search */
$search = app(OperatorQuerySearch::class);
if ($app->auth->check()) {
$search->setUser(auth()->user());
}

View File

@@ -38,6 +38,7 @@ use FireflyIII\Services\Internal\Destroy\AccountDestroyService;
use FireflyIII\Services\Internal\Update\AccountUpdateService;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Relations\HasMany;
use \Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Support\Collection;
use Log;
use Storage;
@@ -346,7 +347,7 @@ class AccountRepository implements AccountRepositoryInterface
return null;
}
if (1 === $result->count()) {
return (string)$result->first()->data;
return (string) $result->first()->data;
}
return null;
}
@@ -704,4 +705,38 @@ class AccountRepository implements AccountRepositoryInterface
$account->save();
}
}
/**
* @inheritDoc
*/
public function searchAccountNr(string $query, array $types, int $limit): Collection
{
$dbQuery = $this->user->accounts()->distinct()
->leftJoin('account_meta', 'accounts.id', 'account_meta.account_id')
->where('accounts.active', 1)
->orderBy('accounts.order', 'ASC')
->orderBy('accounts.account_type_id', 'ASC')
->orderBy('accounts.name', 'ASC')
->with(['accountType', 'accountMeta']);
if ('' !== $query) {
// split query on spaces just in case:
$parts = explode(' ', $query);
foreach ($parts as $part) {
$search = sprintf('%%%s%%', $part);
$dbQuery->where(function (EloquentBuilder $q1) use ($search) {
$q1->where('accounts.iban', 'LIKE', $search);
$q1->orWhere(function (EloquentBuilder $q2) use ($search) {
$q2->where('account_meta.name', '=', 'account_number');
$q2->where('account_meta.data', 'LIKE', $search);
});
});
}
}
if (count($types) > 0) {
$dbQuery->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
$dbQuery->whereIn('account_types.type', $types);
}
return $dbQuery->take($limit)->get(['accounts.*']);
}
}

View File

@@ -289,6 +289,15 @@ interface AccountRepositoryInterface
*/
public function searchAccount(string $query, array $types, int $limit): Collection;
/**
* @param string $query
* @param array $types
* @param int $limit
*
* @return Collection
*/
public function searchAccountNr(string $query, array $types, int $limit): Collection;
/**
* @param User $user
*/

View File

@@ -39,19 +39,7 @@ use Log;
*/
class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
{
/** @var User */
private $user;
/**
* Constructor.
*/
public function __construct()
{
if ('testing' === config('app.env')) {
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
die(get_class($this));
}
}
private User $user;
/**
* @param AvailableBudget $availableBudget
@@ -78,8 +66,8 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
{
return $this->user->availableBudgets()
->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d 00:00:00'))
->where('end_date', $end->format('Y-m-d 00:00:00'))
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))
->first();
}
@@ -98,8 +86,8 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
if (null !== $start && null !== $end) {
$query->where(
static function (Builder $q1) use ($start, $end) {
$q1->where('start_date', '=', $start->format('Y-m-d 00:00:00'));
$q1->where('end_date', '=', $end->format('Y-m-d 00:00:00'));
$q1->where('start_date', '=', $start->format('Y-m-d'));
$q1->where('end_date', '=', $end->format('Y-m-d'));
}
);
}
@@ -119,8 +107,8 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
$amount = '0';
$availableBudget = $this->user->availableBudgets()
->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d 00:00:00'))
->where('end_date', $end->format('Y-m-d 00:00:00'))->first();
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->first();
if (null !== $availableBudget) {
$amount = (string)$availableBudget->amount;
}
@@ -174,10 +162,10 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
$query = $this->user->availableBudgets();
if (null !== $start) {
$query->where('start_date', '>=', $start->format('Y-m-d H:i:s'));
$query->where('start_date', '>=', $start->format('Y-m-d'));
}
if (null !== $end) {
$query->where('end_date', '<=', $end->format('Y-m-d H:i:s'));
$query->where('end_date', '<=', $end->format('Y-m-d'));
}
return $query->get();
@@ -196,14 +184,14 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
{
$availableBudget = $this->user->availableBudgets()
->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d 00:00:00'))
->where('end_date', $end->format('Y-m-d 00:00:00'))->first();
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->first();
if (null === $availableBudget) {
$availableBudget = new AvailableBudget;
$availableBudget->user()->associate($this->user);
$availableBudget->transactionCurrency()->associate($currency);
$availableBudget->start_date = $start->format('Y-m-d 00:00:00');
$availableBudget->end_date = $end->format('Y-m-d 00:00:00');
$availableBudget->start_date = $start->format('Y-m-d');
$availableBudget->end_date = $end->format('Y-m-d');
}
$availableBudget->amount = $amount;
$availableBudget->save();
@@ -226,13 +214,21 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
*/
public function store(array $data): ?AvailableBudget
{
$start = $data['start'];
if($start instanceof Carbon) {
$start = $data['start']->startOfDay();
}
$end = $data['end'];
if($end instanceof Carbon) {
$end = $data['end']->endOfDay();
}
return AvailableBudget::create(
[
'user_id' => $this->user->id,
'transaction_currency_id' => $data['currency']->id,
'amount' => $data['amount'],
'start_date' => $data['start'],
'end_date' => $data['end'],
'start_date' => $start,
'end_date' => $end,
]
);
@@ -265,17 +261,27 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
{
$existing = $this->user->availableBudgets()
->where('transaction_currency_id', $data['currency_id'])
->where('start_date', $data['start']->format('Y-m-d 00:00:00'))
->where('end_date', $data['end']->format('Y-m-d 00:00:00'))
->where('start_date', $data['start']->format('Y-m-d'))
->where('end_date', $data['end']->format('Y-m-d'))
->where('id', '!=', $availableBudget->id)
->first();
if (null !== $existing) {
throw new FireflyException(sprintf('An entry already exists for these parameters: available budget object with ID #%d', $existing->id));
}
$start = $data['start'];
if($start instanceof Carbon) {
$start = $data['start']->startOfDay();
}
$end = $data['end'];
if($end instanceof Carbon) {
$end = $data['end']->endOfDay();
}
$availableBudget->transaction_currency_id = $data['currency_id'];
$availableBudget->start_date = $data['start'];
$availableBudget->end_date = $data['end'];
$availableBudget->start_date = $start;
$availableBudget->end_date = $end;
$availableBudget->amount = $data['amount'];
$availableBudget->save();
@@ -299,7 +305,7 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
return $this->user
->availableBudgets()
->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d 00:00:00'))
->where('end_date', $end->format('Y-m-d 00:00:00'))->first();
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->first();
}
}

View File

@@ -44,17 +44,6 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
{
private User $user;
/**
* Constructor.
*/
public function __construct()
{
if ('testing' === config('app.env')) {
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
die(get_class($this));
}
}
/**
* Tells you which amount has been budgeted (for the given budgets)
* in the selected query. Returns a positive amount as a string.
@@ -115,8 +104,8 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
{
return $budget->budgetlimits()
->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->first();
->where('start_date', $start->format('Y-m-d 00:00:00'))
->where('end_date', $end->format('Y-m-d 23:59:59'))->first();
}
/**
@@ -307,7 +296,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
// find limit with same date range and currency.
$limit = $budget->budgetlimits()
->where('budget_limits.start_date', $data['start_date']->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $data['end_date']->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $data['end_date']->format('Y-m-d 23:59:59'))
->where('budget_limits.transaction_currency_id', $currency->id)
->get(['budget_limits.*'])->first();
if (null !== $limit) {
@@ -319,7 +308,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$limit = new BudgetLimit;
$limit->budget()->associate($budget);
$limit->start_date = $data['start_date']->format('Y-m-d 00:00:00');
$limit->end_date = $data['end_date']->format('Y-m-d 00:00:00');
$limit->end_date = $data['end_date']->format('Y-m-d 23:59:59');
$limit->amount = $data['amount'];
$limit->transaction_currency_id = $currency->id;
$limit->save();
@@ -337,9 +326,9 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
public function update(BudgetLimit $budgetLimit, array $data): BudgetLimit
{
$budgetLimit->amount = array_key_exists('amount',$data) ? $data['amount'] : $budgetLimit->amount;
$budgetLimit->budget_id = array_key_exists('budget_id', $data) ? $data['budget_id'] : $budgetLimit->id;
$budgetLimit->budget_id = array_key_exists('budget_id', $data) ? $data['budget_id'] : $budgetLimit->budget_id;
$budgetLimit->start_date = array_key_exists('start_date', $data) ? $data['start_date']->format('Y-m-d 00:00:00') : $budgetLimit->start_date;
$budgetLimit->end_date = array_key_exists('end_date', $data) ? $data['end_date']->format('Y-m-d 00:00:00') : $budgetLimit->end_date;
$budgetLimit->end_date = array_key_exists('end_date', $data) ? $data['end_date']->format('Y-m-d 23:59:59') : $budgetLimit->end_date;
// if no currency has been provided, use the user's default currency:
$currency = null;

View File

@@ -307,7 +307,7 @@ class BudgetRepository implements BudgetRepositoryInterface
$autoBudget->save();
// create initial budget limit.
$today = new Carbon;
$today = today(config('app.timezone'));
$start = app('navigation')->startOfPeriod($today, $autoBudget->period);
$end = app('navigation')->endOfPeriod($start, $autoBudget->period);
@@ -317,8 +317,8 @@ class BudgetRepository implements BudgetRepositoryInterface
[
'budget_id' => $newBudget->id,
'transaction_currency_id' => $autoBudget->transaction_currency_id,
'start_date' => $start->format('Y-m-d'),
'end_date' => $end->format('Y-m-d'),
'start_date' => $start,
'end_date' => $end,
'amount' => $autoBudget->amount,
]
);

View File

@@ -41,19 +41,7 @@ use Log;
*/
class OperationsRepository implements OperationsRepositoryInterface
{
/** @var User */
private $user;
/**
* Constructor.
*/
public function __construct()
{
if ('testing' === config('app.env')) {
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
die(get_class($this));
}
}
private User $user;
/**
* A method that returns the amount of money budgeted per day for this budget,

View File

@@ -47,18 +47,7 @@ use Log;
*/
class CurrencyRepository implements CurrencyRepositoryInterface
{
/** @var User */
private $user;
/**
* Constructor.
*/
public function __construct()
{
if ('testing' === config('app.env')) {
Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
}
}
private User $user;
/**
* @param TransactionCurrency $currency

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