mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-08-16 10:54:39 +00:00
Compare commits
254 Commits
develop-20
...
v6.1.24
Author | SHA1 | Date | |
---|---|---|---|
|
17b0b1f43f | ||
|
b61df5ec19 | ||
|
1ac7275f83 | ||
|
cd10d04907 | ||
|
f9b76fcb8b | ||
|
093fa067e6 | ||
|
fa655f065b | ||
|
c8f2244912 | ||
|
f3a20e14a6 | ||
|
33ad47b115 | ||
|
775424d3b7 | ||
|
c9c86bbd1d | ||
|
f76a6ad85c | ||
|
2138b14d89 | ||
|
1bf61f57f5 | ||
|
07b55bd71f | ||
|
8d2d3d4002 | ||
|
d182b4b4a6 | ||
|
60f6a91fe4 | ||
|
ec89a2f956 | ||
|
87113d7181 | ||
|
59fae290e5 | ||
|
1a8ba2ce53 | ||
|
dddaa25d86 | ||
|
f28341587a | ||
|
5593bf3e08 | ||
|
92574a7a9d | ||
|
e049266f5d | ||
|
5b3e6fcb07 | ||
|
b0bfb556db | ||
|
484acbcb45 | ||
|
cdc802cfb8 | ||
|
582671ca84 | ||
|
22498b5804 | ||
|
87f277a482 | ||
|
ae0d74f57a | ||
|
0ae5593dde | ||
|
0d11769590 | ||
|
b7d8daf013 | ||
|
a9c0126b05 | ||
|
6bc5a57d10 | ||
|
2714ee96f1 | ||
|
524d382b7a | ||
|
2723e05d2a | ||
|
6dd9bda6b4 | ||
|
44449bc716 | ||
|
b17d8edb50 | ||
|
578072238a | ||
|
b4edd3dcc4 | ||
|
068094caac | ||
|
deb58e617d | ||
|
baca0c1120 | ||
|
02543438a4 | ||
|
d507e59038 | ||
|
9d0fd7ef1b | ||
|
dbef5e2143 | ||
|
04eca755d2 | ||
|
7883692196 | ||
|
8f64977cb9 | ||
|
f94fdc4979 | ||
|
a0a0e28447 | ||
|
f6f7783b94 | ||
|
d233cc1de8 | ||
|
37671499c8 | ||
|
c83b79998d | ||
|
ed842c2b42 | ||
|
8c5f114339 | ||
|
8b2f1d0b4f | ||
|
591a1b3050 | ||
|
42ec3fe02b | ||
|
370a398b5e | ||
|
554d89b6e9 | ||
|
cb049f5dda | ||
|
0728668d41 | ||
|
dfc187874e | ||
|
225588f3e7 | ||
|
06cc6c29aa | ||
|
b2d4469908 | ||
|
c398383905 | ||
|
7af9dce33b | ||
|
038790a5d6 | ||
|
fb3295bde1 | ||
|
43a4fd2ecb | ||
|
899c72d068 | ||
|
d118c0d886 | ||
|
6d4004d1ed | ||
|
ae60cd5b28 | ||
|
ab31a72199 | ||
|
2c1b9534f3 | ||
|
7028cb1546 | ||
|
dc1ecf6a42 | ||
|
3a27f9d02c | ||
|
4b27ab38f8 | ||
|
40de147611 | ||
|
df5756dc86 | ||
|
bb4f90d730 | ||
|
d89d46aaec | ||
|
304d720c4c | ||
|
7eff160190 | ||
|
8b2e18ed9d | ||
|
7001051833 | ||
|
b4b9752c05 | ||
|
acadc89eaa | ||
|
6ff84b8e90 | ||
|
7f3e3fc3bf | ||
|
02233fd7a4 | ||
|
50d3db0643 | ||
|
3751831779 | ||
|
14a24e47fb | ||
|
b7e78cb0e6 | ||
|
a8f65f42fc | ||
|
d3385a116d | ||
|
e0c446dd13 | ||
|
33d11b4780 | ||
|
07c49d1d04 | ||
|
9463285ac9 | ||
|
b41fc43e64 | ||
|
562763c938 | ||
|
ec60194110 | ||
|
1e472ee095 | ||
|
5597327448 | ||
|
cdd5baf5be | ||
|
7b5978059b | ||
|
da0b41e45c | ||
|
d0be2afba5 | ||
|
d99851231a | ||
|
7e02c141f9 | ||
|
d03960e379 | ||
|
16d3984ffc | ||
|
856a194988 | ||
|
1bff966bfe | ||
|
1948b6118b | ||
|
20c25d3ca2 | ||
|
a153735ac3 | ||
|
62509f7c18 | ||
|
9b48b67158 | ||
|
cbd50634a4 | ||
|
f475393bc1 | ||
|
abcddb09bf | ||
|
cf71a0fc55 | ||
|
78253f9e1e | ||
|
ebd0848c7f | ||
|
c8461eb0b5 | ||
|
a4cbdeaeac | ||
|
3e1ce69d52 | ||
|
08a26b976e | ||
|
5fc55381a2 | ||
|
dbf3d24ae7 | ||
|
cc7c6e02c5 | ||
|
b45aa85853 | ||
|
e7526ac5e3 | ||
|
441ada70b8 | ||
|
dedc06a46b | ||
|
b0adf1b277 | ||
|
28f65e9f44 | ||
|
a013af5f0d | ||
|
9552701662 | ||
|
ef52f0aad1 | ||
|
0b6f04905a | ||
|
cdb36357d4 | ||
|
8938622bd9 | ||
|
b210294aa9 | ||
|
5b02f20775 | ||
|
fac382a5df | ||
|
88d88bebc9 | ||
|
755fb9c29b | ||
|
51a835ab51 | ||
|
c9895ab182 | ||
|
e71d46a4e5 | ||
|
8d1d5f37c1 | ||
|
525a68682d | ||
|
715648d0d8 | ||
|
9452e93f22 | ||
|
a6aa145471 | ||
|
25aa6dcb59 | ||
|
bb2270b274 | ||
|
d7f6b4143e | ||
|
0cf0e26fa8 | ||
|
cc23197d60 | ||
|
bc1721d95e | ||
|
0d19173da6 | ||
|
1983f07d3c | ||
|
aad1b91cc2 | ||
|
8cb1057a33 | ||
|
b178032985 | ||
|
561213e95d | ||
|
44fa7c4306 | ||
|
e2169563e2 | ||
|
845344e003 | ||
|
cdb48453e8 | ||
|
9669cef518 | ||
|
f962f71ed7 | ||
|
94ed4021fb | ||
|
1765855c57 | ||
|
55cf3e7d44 | ||
|
9f1840dc05 | ||
|
78dab2e5f9 | ||
|
103b9d5005 | ||
|
1b75b778d8 | ||
|
7e665dbdfc | ||
|
b6897ec3a9 | ||
|
660260174a | ||
|
78d32865b5 | ||
|
edfa92c1aa | ||
|
63012f269c | ||
|
7d0e7f779f | ||
|
b8e18f80f4 | ||
|
481b01e4f7 | ||
|
edf2030251 | ||
|
bd1cfffb61 | ||
|
629f70d27d | ||
|
57f5ebc0f9 | ||
|
b4f51e7b47 | ||
|
d78d254e86 | ||
|
eb0c113699 | ||
|
23045ebd59 | ||
|
2e5931f304 | ||
|
a620b07c00 | ||
|
cb724145f2 | ||
|
8ef17f6686 | ||
|
debfd9160c | ||
|
f2482e4ace | ||
|
d98d757f8b | ||
|
0c9a41a929 | ||
|
e26f78bf50 | ||
|
ed265f68ba | ||
|
d2e9b64bf5 | ||
|
3811aff206 | ||
|
762d898fee | ||
|
5e6034fc86 | ||
|
9da10459d6 | ||
|
ff80cedd6b | ||
|
b213148ae8 | ||
|
c8646e20cb | ||
|
76a41fec50 | ||
|
0e705bd038 | ||
|
f33ffb98ff | ||
|
faa0d59340 | ||
|
5af0219884 | ||
|
dafd99f155 | ||
|
3560f0388c | ||
|
b2954658d8 | ||
|
44581d9983 | ||
|
02dcfeb227 | ||
|
d8bafb349d | ||
|
889598a4c8 | ||
|
7e37d10016 | ||
|
ebaebb09d1 | ||
|
531a3a4b6c | ||
|
b3e313821b | ||
|
51958af422 | ||
|
e3b21ccdba | ||
|
31bb208835 | ||
|
8c97e805a2 |
256
.ci/php-cs-fixer/composer.lock
generated
256
.ci/php-cs-fixer/composer.lock
generated
@@ -72,27 +72,27 @@
|
||||
},
|
||||
{
|
||||
"name": "composer/pcre",
|
||||
"version": "3.2.0",
|
||||
"version": "3.3.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/pcre.git",
|
||||
"reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90"
|
||||
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/ea4ab6f9580a4fd221e0418f2c357cdd39102a90",
|
||||
"reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
|
||||
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpstan/phpstan": "<1.11.8"
|
||||
"phpstan/phpstan": "<1.11.10"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.11.8",
|
||||
"phpstan/phpstan-strict-rules": "^1.1",
|
||||
"phpstan/phpstan": "^1.12 || ^2",
|
||||
"phpstan/phpstan-strict-rules": "^1 || ^2",
|
||||
"phpunit/phpunit": "^8 || ^9"
|
||||
},
|
||||
"type": "library",
|
||||
@@ -131,7 +131,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/composer/pcre/issues",
|
||||
"source": "https://github.com/composer/pcre/tree/3.2.0"
|
||||
"source": "https://github.com/composer/pcre/tree/3.3.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -147,28 +147,28 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-25T09:36:02+00:00"
|
||||
"time": "2024-11-12T16:29:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"version": "3.4.2",
|
||||
"version": "3.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/semver.git",
|
||||
"reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6"
|
||||
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6",
|
||||
"reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
|
||||
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.3.2 || ^7.0 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.4",
|
||||
"symfony/phpunit-bridge": "^4.2 || ^5"
|
||||
"phpstan/phpstan": "^1.11",
|
||||
"symfony/phpunit-bridge": "^3 || ^7"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -212,7 +212,7 @@
|
||||
"support": {
|
||||
"irc": "ircs://irc.libera.chat:6697/composer",
|
||||
"issues": "https://github.com/composer/semver/issues",
|
||||
"source": "https://github.com/composer/semver/tree/3.4.2"
|
||||
"source": "https://github.com/composer/semver/tree/3.4.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -228,7 +228,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-12T11:35:52+00:00"
|
||||
"time": "2024-09-19T14:15:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/xdebug-handler",
|
||||
@@ -345,16 +345,16 @@
|
||||
},
|
||||
{
|
||||
"name": "fidry/cpu-core-counter",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/theofidry/cpu-core-counter.git",
|
||||
"reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42"
|
||||
"reference": "8520451a140d3f46ac33042715115e290cf5785f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42",
|
||||
"reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42",
|
||||
"url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f",
|
||||
"reference": "8520451a140d3f46ac33042715115e290cf5785f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -394,7 +394,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/theofidry/cpu-core-counter/issues",
|
||||
"source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0"
|
||||
"source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -402,20 +402,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-02-07T09:43:46+00:00"
|
||||
"time": "2024-08-06T10:04:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.60.0",
|
||||
"version": "v3.64.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "e595e4e070d17c5d42ed8c4206f630fcc5f401a4"
|
||||
"reference": "58dd9c931c785a79739310aef5178928305ffa67"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/e595e4e070d17c5d42ed8c4206f630fcc5f401a4",
|
||||
"reference": "e595e4e070d17c5d42ed8c4206f630fcc5f401a4",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/58dd9c931c785a79739310aef5178928305ffa67",
|
||||
"reference": "58dd9c931c785a79739310aef5178928305ffa67",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -497,7 +497,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.60.0"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.64.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -505,7 +505,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-25T09:26:51+00:00"
|
||||
"time": "2024-08-30T23:09:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
@@ -612,16 +612,16 @@
|
||||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/log.git",
|
||||
"reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
|
||||
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
|
||||
"reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
|
||||
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -656,9 +656,9 @@
|
||||
"psr-3"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/log/tree/3.0.0"
|
||||
"source": "https://github.com/php-fig/log/tree/3.0.2"
|
||||
},
|
||||
"time": "2021-07-14T16:46:02+00:00"
|
||||
"time": "2024-09-11T13:17:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "react/cache",
|
||||
@@ -1259,16 +1259,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v7.1.3",
|
||||
"version": "v7.1.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9"
|
||||
"reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9",
|
||||
"reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/ff04e5b5ba043d2badfb308197b9e6b42883fcd5",
|
||||
"reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1332,7 +1332,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v7.1.3"
|
||||
"source": "https://github.com/symfony/console/tree/v7.1.8"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1348,7 +1348,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-26T12:41:01+00:00"
|
||||
"time": "2024-11-06T14:23:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@@ -1419,16 +1419,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v7.1.1",
|
||||
"version": "v7.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
"reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7"
|
||||
"reference": "87254c78dd50721cfd015b62277a8281c5589702"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7",
|
||||
"reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87254c78dd50721cfd015b62277a8281c5589702",
|
||||
"reference": "87254c78dd50721cfd015b62277a8281c5589702",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1479,7 +1479,7 @@
|
||||
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1"
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v7.1.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1495,7 +1495,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T14:57:53+00:00"
|
||||
"time": "2024-09-25T14:20:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher-contracts",
|
||||
@@ -1575,16 +1575,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v7.1.2",
|
||||
"version": "v7.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
"reference": "92a91985250c251de9b947a14bb2c9390b1a562c"
|
||||
"reference": "c835867b3c62bb05c7fe3d637c871c7ae52024d4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c",
|
||||
"reference": "92a91985250c251de9b947a14bb2c9390b1a562c",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/c835867b3c62bb05c7fe3d637c871c7ae52024d4",
|
||||
"reference": "c835867b3c62bb05c7fe3d637c871c7ae52024d4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1621,7 +1621,7 @@
|
||||
"description": "Provides basic utilities for the filesystem",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/filesystem/tree/v7.1.2"
|
||||
"source": "https://github.com/symfony/filesystem/tree/v7.1.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1637,20 +1637,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-06-28T10:03:55+00:00"
|
||||
"time": "2024-10-25T15:11:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v7.1.3",
|
||||
"version": "v7.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "717c6329886f32dc65e27461f80f2a465412fdca"
|
||||
"reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/717c6329886f32dc65e27461f80f2a465412fdca",
|
||||
"reference": "717c6329886f32dc65e27461f80f2a465412fdca",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/2cb89664897be33f78c65d3d2845954c8d7a43b8",
|
||||
"reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1685,7 +1685,7 @@
|
||||
"description": "Finds files and directories via an intuitive fluent interface",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/finder/tree/v7.1.3"
|
||||
"source": "https://github.com/symfony/finder/tree/v7.1.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1701,20 +1701,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-24T07:08:44+00:00"
|
||||
"time": "2024-10-01T08:31:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
"version": "v7.1.1",
|
||||
"version": "v7.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/options-resolver.git",
|
||||
"reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55"
|
||||
"reference": "85e95eeede2d41cd146146e98c9c81d9214cae85"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55",
|
||||
"reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55",
|
||||
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/85e95eeede2d41cd146146e98c9c81d9214cae85",
|
||||
"reference": "85e95eeede2d41cd146146e98c9c81d9214cae85",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1752,7 +1752,7 @@
|
||||
"options"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/options-resolver/tree/v7.1.1"
|
||||
"source": "https://github.com/symfony/options-resolver/tree/v7.1.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1768,24 +1768,24 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T14:57:53+00:00"
|
||||
"time": "2024-09-25T14:20:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.30.0",
|
||||
"version": "v1.31.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "0424dff1c58f028c451efff2045f5d92410bd540"
|
||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540",
|
||||
"reference": "0424dff1c58f028c451efff2045f5d92410bd540",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
@@ -1831,7 +1831,7 @@
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0"
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1847,24 +1847,24 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T15:07:36+00:00"
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-grapheme",
|
||||
"version": "v1.30.0",
|
||||
"version": "v1.31.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
|
||||
"reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a"
|
||||
"reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a",
|
||||
"reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
|
||||
"reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "For best performance"
|
||||
@@ -1909,7 +1909,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1925,24 +1925,24 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T15:07:36+00:00"
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-normalizer",
|
||||
"version": "v1.30.0",
|
||||
"version": "v1.31.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
||||
"reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb"
|
||||
"reference": "3833d7255cc303546435cb650316bff708a1c75c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb",
|
||||
"reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
|
||||
"reference": "3833d7255cc303546435cb650316bff708a1c75c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "For best performance"
|
||||
@@ -1990,7 +1990,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2006,24 +2006,24 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T15:07:36+00:00"
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.30.0",
|
||||
"version": "v1.31.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c"
|
||||
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c",
|
||||
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
|
||||
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-mbstring": "*"
|
||||
@@ -2070,7 +2070,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2086,24 +2086,24 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-06-19T12:30:46+00:00"
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.30.0",
|
||||
"version": "v1.31.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "77fa7995ac1b21ab60769b7323d600a991a90433"
|
||||
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433",
|
||||
"reference": "77fa7995ac1b21ab60769b7323d600a991a90433",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
|
||||
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -2150,7 +2150,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0"
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2166,24 +2166,24 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T15:07:36+00:00"
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php81",
|
||||
"version": "v1.30.0",
|
||||
"version": "v1.31.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php81.git",
|
||||
"reference": "3fb075789fb91f9ad9af537c4012d523085bd5af"
|
||||
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af",
|
||||
"reference": "3fb075789fb91f9ad9af537c4012d523085bd5af",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
|
||||
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -2226,7 +2226,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0"
|
||||
"source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2242,20 +2242,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-06-19T12:30:46+00:00"
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v7.1.3",
|
||||
"version": "v7.1.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca"
|
||||
"reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca",
|
||||
"reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/42783370fda6e538771f7c7a36e9fa2ee3a84892",
|
||||
"reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2287,7 +2287,7 @@
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v7.1.3"
|
||||
"source": "https://github.com/symfony/process/tree/v7.1.8"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2303,7 +2303,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-26T12:44:47+00:00"
|
||||
"time": "2024-11-06T14:23:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
@@ -2390,16 +2390,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/stopwatch",
|
||||
"version": "v7.1.1",
|
||||
"version": "v7.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/stopwatch.git",
|
||||
"reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d"
|
||||
"reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d",
|
||||
"reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d",
|
||||
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/8b4a434e6e7faf6adedffb48783a5c75409a1a05",
|
||||
"reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2432,7 +2432,7 @@
|
||||
"description": "Provides a way to profile code",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/stopwatch/tree/v7.1.1"
|
||||
"source": "https://github.com/symfony/stopwatch/tree/v7.1.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2448,20 +2448,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T14:57:53+00:00"
|
||||
"time": "2024-09-25T14:20:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v7.1.3",
|
||||
"version": "v7.1.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "ea272a882be7f20cad58d5d78c215001617b7f07"
|
||||
"reference": "591ebd41565f356fcd8b090fe64dbb5878f50281"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07",
|
||||
"reference": "ea272a882be7f20cad58d5d78c215001617b7f07",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/591ebd41565f356fcd8b090fe64dbb5878f50281",
|
||||
"reference": "591ebd41565f356fcd8b090fe64dbb5878f50281",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2519,7 +2519,7 @@
|
||||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v7.1.3"
|
||||
"source": "https://github.com/symfony/string/tree/v7.1.8"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2535,16 +2535,16 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-22T10:25:37+00:00"
|
||||
"time": "2024-11-13T13:31:21+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"stability-flags": {},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"platform": {},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
@@ -1,9 +1,10 @@
|
||||
parameters:
|
||||
scanFiles:
|
||||
- ../_ide_helper_models.php
|
||||
universalObjectCratesClasses:
|
||||
- Illuminate\Database\Eloquent\Model
|
||||
# TODO: slowly remove these parameters and fix the issues found.
|
||||
reportUnmatchedIgnoredErrors: false
|
||||
checkGenericClassInNonGenericObjectType: false # remove this rule when all other issues are solved.
|
||||
ignoreErrors:
|
||||
# TODO: slowly remove these exceptions and fix the issues found.
|
||||
- '#Dynamic call to static method#' # all the Laravel ORM things depend on this.
|
||||
@@ -11,6 +12,7 @@ parameters:
|
||||
- '#with no value type specified in iterable type array#' # remove this rule when all other issues are solved.
|
||||
- '#has no value type specified in iterable type array#' # remove this rule when all other issues are solved.
|
||||
- '#is not allowed to extend#'
|
||||
- '#does not specify its types#'
|
||||
- '#switch is forbidden to use#'
|
||||
- '#is neither abstract nor final#'
|
||||
- '#on left side of \?\?\= always exists and is not nullable#'
|
||||
|
@@ -176,6 +176,7 @@ MAILGUN_ENDPOINT=api.mailgun.net
|
||||
# If you use Docker or similar, you can set these variables from a file by appending them with _FILE
|
||||
MANDRILL_SECRET=
|
||||
SPARKPOST_SECRET=
|
||||
MAILERSEND_API_KEY=
|
||||
|
||||
# Firefly III can send you the following messages.
|
||||
SEND_ERROR_MESSAGE=true
|
||||
@@ -312,6 +313,12 @@ PUSHER_ID=
|
||||
DEMO_USERNAME=
|
||||
DEMO_PASSWORD=
|
||||
|
||||
#
|
||||
# Disable or enable the running balance column data
|
||||
# Please disable this. It's a very experimental feature.
|
||||
#
|
||||
USE_RUNNING_BALANCE=false
|
||||
|
||||
#
|
||||
# The v2 layout is very experimental. If it breaks you get to keep both parts.
|
||||
# Be wary of data loss.
|
||||
|
2
.github/label-actions.yml
vendored
2
.github/label-actions.yml
vendored
@@ -4,6 +4,7 @@
|
||||
feature:
|
||||
issues:
|
||||
# Post a comment, `{issue-author}` is an optional placeholder
|
||||
unlabel: feature
|
||||
comment: |
|
||||
Hi there!
|
||||
|
||||
@@ -32,6 +33,7 @@ epic:
|
||||
Thank you for your contributions.
|
||||
|
||||
enhancement:
|
||||
unlabel: enhancement
|
||||
issues:
|
||||
# Post a comment, `{issue-author}` is an optional placeholder
|
||||
comment: |
|
||||
|
1
.github/pull_request_template.md
vendored
1
.github/pull_request_template.md
vendored
@@ -6,6 +6,7 @@ Thank you for submitting new code to Firefly III, or any of the related projects
|
||||
- Please do not open PRs to "discuss" possible solutions or to "get feedback" on your code. I simply don't have time for that.
|
||||
- Pull requests for the MAIN branch will be closed.
|
||||
- DO NOT include translated strings in your PR.
|
||||
- PRs (or parts thereof) that only fix issues inside code comments will not be accepted.
|
||||
|
||||
If it feels necessary to open an issue first, please do so, before you open a PR.
|
||||
|
||||
|
2
.github/workflows/close-duplicates.yml
vendored
2
.github/workflows/close-duplicates.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
close_duplicates:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: github/command@v1.2.1
|
||||
- uses: github/command@v1.2.2
|
||||
id: command
|
||||
with:
|
||||
allowed_contexts: "issue"
|
||||
|
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
@@ -12,6 +12,7 @@ jobs:
|
||||
permissions:
|
||||
issues: write # for actions/stale to close stale issues
|
||||
pull-requests: write # for actions/stale to close stale PRs
|
||||
actions: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
@@ -35,4 +36,5 @@ jobs:
|
||||
Thank you for your contributions.
|
||||
days-before-stale: 14
|
||||
days-before-close: 7
|
||||
exempt-issue-labels: 'enhancement,feature,bug,announcement,epic,triage'
|
||||
exempt-all-milestones: true
|
||||
exempt-issue-labels: 'triage'
|
||||
|
@@ -4,6 +4,12 @@ Over time, many people have contributed to Firefly III. Their efforts are not al
|
||||
Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution.
|
||||
|
||||
## 2024
|
||||
- Antônio Franco
|
||||
- yparitcher
|
||||
- Jhon Pedroza
|
||||
- mzhubail
|
||||
- tasnim
|
||||
- withbest
|
||||
- Steve Wasiura
|
||||
- imlonghao
|
||||
- Rahman Yusuf
|
||||
|
@@ -112,7 +112,12 @@ class StoreController extends Controller
|
||||
|
||||
return response()->json([], 422);
|
||||
}
|
||||
$helper->saveAttachmentFromApi($attachment, $body);
|
||||
$result = $helper->saveAttachmentFromApi($attachment, $body);
|
||||
if (false === $result) {
|
||||
app('log')->error('Could not save attachment from API.');
|
||||
|
||||
return response()->json([], 422);
|
||||
}
|
||||
|
||||
return response()->json([], 204);
|
||||
}
|
||||
|
@@ -69,7 +69,7 @@ class UpdateController extends Controller
|
||||
*/
|
||||
public function update(UpdateRequest $request, TransactionGroup $transactionGroup): JsonResponse
|
||||
{
|
||||
app('log')->debug('Now in update routine for transaction group!');
|
||||
app('log')->debug('Now in update routine for transaction group');
|
||||
$data = $request->getAll();
|
||||
|
||||
// Fixes 8750.
|
||||
|
@@ -34,6 +34,7 @@ use FireflyIII\Support\Http\Api\TransactionFilter;
|
||||
use FireflyIII\Transformers\CurrencyTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use League\Fractal\Resource\Item;
|
||||
|
||||
/**
|
||||
@@ -164,6 +165,7 @@ class UpdateController extends Controller
|
||||
public function update(UpdateRequest $request, TransactionCurrency $currency): JsonResponse
|
||||
{
|
||||
$data = $request->getAll();
|
||||
Log::debug(__METHOD__, $data);
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
@@ -173,6 +175,11 @@ class UpdateController extends Controller
|
||||
if (array_key_exists('enabled', $data) && false === $data['enabled'] && 1 === count($set) && $set->first()->id === $currency->id) {
|
||||
return response()->json([], 409);
|
||||
}
|
||||
// second safety catch on currency disable.
|
||||
if (array_key_exists('enabled', $data) && false === $data['enabled'] && $this->repository->currencyInUse($currency)) {
|
||||
return response()->json([], 409);
|
||||
}
|
||||
|
||||
$currency = $this->repository->update($currency, $data);
|
||||
|
||||
app('preferences')->mark();
|
||||
|
@@ -52,7 +52,7 @@ class AboutController extends Controller
|
||||
$data
|
||||
= [
|
||||
'version' => config('firefly.version'),
|
||||
'api_version' => config('firefly.api_version'),
|
||||
'api_version' => config('firefly.version'),
|
||||
'php_version' => $phpVersion,
|
||||
'os' => $phpOs,
|
||||
'driver' => $currentDriver,
|
||||
|
@@ -102,6 +102,8 @@ class PreferencesController extends Controller
|
||||
* TODO This endpoint is not documented.
|
||||
*
|
||||
* Return a single preference by name.
|
||||
*
|
||||
* @param Collection<int, Preference> $collection
|
||||
*/
|
||||
public function showList(Collection $collection): JsonResponse
|
||||
{
|
||||
|
@@ -46,6 +46,8 @@ class DateRequest extends FormRequest
|
||||
{
|
||||
$start = $this->getCarbonDate('start');
|
||||
$end = $this->getCarbonDate('end');
|
||||
$start->startOfDay();
|
||||
$end->endOfDay();
|
||||
if ($start->diffInYears($end, true) > 5) {
|
||||
throw new FireflyException('Date range out of range.');
|
||||
}
|
||||
|
@@ -79,12 +79,12 @@ class StoreRequest extends FormRequest
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
'date' => 'date|required',
|
||||
'end_date' => 'date|after:date',
|
||||
'extension_date' => 'date|after:date',
|
||||
'end_date' => 'nullable|date|after:date',
|
||||
'extension_date' => 'nullable|date|after:date',
|
||||
'repeat_freq' => 'in:weekly,monthly,quarterly,half-year,yearly|required',
|
||||
'skip' => 'min:0|max:31|numeric',
|
||||
'active' => [new IsBoolean()],
|
||||
'notes' => 'min:1|max:32768',
|
||||
'notes' => 'nullable|min:1|max:32768',
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -138,7 +138,7 @@ class StoreRequest extends FormRequest
|
||||
// all custom fields:
|
||||
'internal_reference' => $this->clearString((string)$object['internal_reference']),
|
||||
'external_id' => $this->clearString((string)$object['external_id']),
|
||||
'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')),
|
||||
'original_source' => sprintf('ff3-v%s', config('firefly.version')),
|
||||
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
|
||||
'bunq_payment_id' => $this->clearString((string)$object['bunq_payment_id']),
|
||||
'external_url' => $this->clearString((string)$object['external_url']),
|
||||
|
@@ -68,9 +68,9 @@ class AccountController extends Controller
|
||||
*/
|
||||
public function accounts(AutocompleteRequest $request): JsonResponse
|
||||
{
|
||||
$queryParameters = $request->getParameters();
|
||||
$result = $this->repository->searchAccount($queryParameters['query'], $queryParameters['account_types'], $queryParameters['size']);
|
||||
$return = [];
|
||||
$params = $request->getParameters();
|
||||
$result = $this->repository->searchAccount($params['query'], $params['account_types'], $params['page'], $params['size']);
|
||||
$return = [];
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($result as $account) {
|
||||
@@ -89,6 +89,7 @@ class AccountController extends Controller
|
||||
'title' => $account->name,
|
||||
'meta' => [
|
||||
'type' => $account->accountType->type,
|
||||
// TODO is multi currency property.
|
||||
'currency_id' => null === $currency ? null : (string) $currency->id,
|
||||
'currency_code' => $currency?->code,
|
||||
'currency_symbol' => $currency?->symbol,
|
||||
|
@@ -48,7 +48,7 @@ class BalanceController extends Controller
|
||||
private AccountRepositoryInterface $repository;
|
||||
private GroupCollectorInterface $collector;
|
||||
private ChartData $chartData;
|
||||
private TransactionCurrency $default;
|
||||
// private TransactionCurrency $default;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -61,7 +61,7 @@ class BalanceController extends Controller
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
$this->collector->setUserGroup($userGroup);
|
||||
$this->chartData = new ChartData();
|
||||
$this->default = app('amount')->getDefaultCurrency();
|
||||
// $this->default = app('amount')->getDefaultCurrency();
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -84,10 +84,6 @@ class BalanceController extends Controller
|
||||
$queryParameters = $request->getParameters();
|
||||
$accounts = $this->getAccountList($queryParameters);
|
||||
|
||||
// move date to end of day
|
||||
$queryParameters['start']->startOfDay();
|
||||
$queryParameters['end']->endOfDay();
|
||||
|
||||
// prepare for currency conversion and data collection:
|
||||
/** @var TransactionCurrency $default */
|
||||
$default = app('amount')->getDefaultCurrency();
|
||||
|
@@ -46,7 +46,7 @@ class AccountController extends Controller
|
||||
use Actions\Destroy;
|
||||
use Actions\DetachRelationship;
|
||||
|
||||
// use Actions\FetchMany;
|
||||
use Actions\FetchMany;
|
||||
// use Actions\FetchOne;
|
||||
use Actions\FetchRelated;
|
||||
use Actions\FetchRelationship;
|
||||
@@ -81,12 +81,13 @@ class AccountController extends Controller
|
||||
*/
|
||||
public function show(AccountSchema $schema, AccountSingleQuery $request, Account $account)
|
||||
{
|
||||
$model = $schema
|
||||
->repository()
|
||||
Log::debug(__METHOD__);
|
||||
$model = $schema->repository()
|
||||
->queryOne($account)
|
||||
->withRequest($request)
|
||||
->first()
|
||||
;
|
||||
Log::debug(sprintf('%s again!', __METHOD__));
|
||||
|
||||
// do something custom...
|
||||
|
||||
|
@@ -23,15 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V2\Request\Autocomplete;
|
||||
|
||||
use FireflyIII\JsonApi\Rules\IsValidFilter;
|
||||
use FireflyIII\JsonApi\Rules\IsValidPage;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\Support\Http\Api\ParsesQueryFilters;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use LaravelJsonApi\Core\Query\QueryParameters;
|
||||
use LaravelJsonApi\Validation\Rule as JsonApiRule;
|
||||
|
||||
/**
|
||||
* Class AutocompleteRequest
|
||||
@@ -51,35 +48,46 @@ class AutocompleteRequest extends FormRequest
|
||||
*/
|
||||
public function getParameters(): array
|
||||
{
|
||||
$queryParameters = QueryParameters::cast($this->all());
|
||||
|
||||
return [
|
||||
'date' => $this->dateOrToday($queryParameters, 'date'),
|
||||
'query' => $this->arrayOfStrings($queryParameters, 'query'),
|
||||
'size' => $this->integerFromQueryParams($queryParameters, 'size', 50),
|
||||
'account_types' => $this->getAccountTypeParameter($this->arrayOfStrings($queryParameters, 'account_types')),
|
||||
$array = [
|
||||
'date' => $this->convertDateTime('date'),
|
||||
'query' => $this->clearString((string) $this->get('query')),
|
||||
'size' => $this->integerFromValue('size'),
|
||||
'page' => $this->integerFromValue('page'),
|
||||
'account_types' => $this->arrayFromValue($this->get('account_types')),
|
||||
'transaction_types' => $this->arrayFromValue($this->get('transaction_types')),
|
||||
];
|
||||
$array['size'] = $array['size'] < 1 || $array['size'] > 100 ? 15 : $array['size'];
|
||||
$array['page'] = max($array['page'], 1);
|
||||
if (null === $array['account_types']) {
|
||||
$array['account_types'] = [];
|
||||
}
|
||||
if (null === $array['transaction_types']) {
|
||||
$array['transaction_types'] = [];
|
||||
}
|
||||
|
||||
// remove 'initial balance' from allowed types. its internal
|
||||
$array['account_types'] = array_diff($array['account_types'], [AccountType::INITIAL_BALANCE, AccountType::RECONCILIATION, AccountType::CREDITCARD]);
|
||||
$array['account_types'] = $this->getAccountTypeParameter($array['account_types']);
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
$valid = array_keys($this->types);
|
||||
|
||||
return [
|
||||
'fields' => JsonApiRule::notSupported(),
|
||||
'filter' => ['nullable', 'array', new IsValidFilter(['query', 'date', 'account_types'])],
|
||||
'include' => JsonApiRule::notSupported(),
|
||||
'page' => ['nullable', 'array', new IsValidPage(['size'])],
|
||||
'sort' => JsonApiRule::notSupported(),
|
||||
'date' => 'nullable|date|after:1900-01-01|before:2100-01-01',
|
||||
'query' => 'nullable|string',
|
||||
'size' => 'nullable|integer|min:1|max:100',
|
||||
'page' => 'nullable|integer|min:1',
|
||||
'account_types' => sprintf('nullable|in:%s', implode(',', $valid)),
|
||||
'transaction_types' => 'nullable|in:todo',
|
||||
];
|
||||
}
|
||||
|
||||
private function getAccountTypeParameter(mixed $types): array
|
||||
private function getAccountTypeParameter(array $types): array
|
||||
{
|
||||
if (is_string($types) && str_contains($types, ',')) {
|
||||
$types = explode(',', $types);
|
||||
}
|
||||
if (!is_iterable($types)) {
|
||||
$types = [$types];
|
||||
}
|
||||
$return = [];
|
||||
foreach ($types as $type) {
|
||||
$return = array_merge($return, $this->mapAccountTypes($type));
|
||||
|
@@ -24,8 +24,6 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V2\Request\Chart;
|
||||
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\JsonApi\Rules\IsValidFilter;
|
||||
use FireflyIII\Rules\IsFilterValueIn;
|
||||
use FireflyIII\Support\Http\Api\ParsesQueryFilters;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
@@ -33,8 +31,6 @@ use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Validator;
|
||||
use LaravelJsonApi\Core\Query\QueryParameters;
|
||||
use LaravelJsonApi\Validation\Rule as JsonApiRule;
|
||||
|
||||
/**
|
||||
* Class ChartRequest
|
||||
@@ -50,50 +46,29 @@ class ChartRequest extends FormRequest
|
||||
|
||||
public function getParameters(): array
|
||||
{
|
||||
$queryParameters = QueryParameters::cast($this->all());
|
||||
|
||||
// $queryParameters = QueryParameters::cast($this->all());
|
||||
return [
|
||||
'start' => $this->dateOrToday($queryParameters, 'start'),
|
||||
'end' => $this->dateOrToday($queryParameters, 'end'),
|
||||
'preselected' => $this->stringFromQueryParams($queryParameters, 'preselected', 'empty'),
|
||||
'period' => $this->stringFromQueryParams($queryParameters, 'period', '1M'),
|
||||
'accounts' => $this->arrayOfStrings($queryParameters, 'accounts'),
|
||||
// preselected heeft maar een paar toegestane waardes, dat moet ook goed gaan.
|
||||
// 'query' => $this->arrayOfStrings($queryParameters, 'query'),
|
||||
// 'size' => $this->integerFromQueryParams($queryParameters,'size', 50),
|
||||
// 'account_types' => $this->getAccountTypeParameter($this->arrayOfStrings($queryParameters, 'account_types')),
|
||||
'start' => $this->convertDateTime('start')?->startOfDay(),
|
||||
'end' => $this->convertDateTime('end')?->endOfDay(),
|
||||
'preselected' => $this->convertString('preselected', 'empty'),
|
||||
'period' => $this->convertString('period', '1M'),
|
||||
'accounts' => $this->arrayFromValue($this->get('accounts')),
|
||||
];
|
||||
// collect accounts based on this list?
|
||||
}
|
||||
|
||||
// return [
|
||||
// 'accounts' => $this->getAccountList(),
|
||||
// 'preselected' => $this->convertString('preselected'),
|
||||
// ];
|
||||
// }
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'fields' => JsonApiRule::notSupported(),
|
||||
'filter' => ['nullable', 'array',
|
||||
new IsValidFilter(['start', 'end', 'preselected', 'accounts']),
|
||||
new IsFilterValueIn('preselected', config('firefly.preselected_accounts')),
|
||||
],
|
||||
'include' => JsonApiRule::notSupported(),
|
||||
'page' => JsonApiRule::notSupported(),
|
||||
'sort' => JsonApiRule::notSupported(),
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31|before_or_equal:end',
|
||||
'end' => 'required|date|after:1900-01-01|before:2099-12-31|after_or_equal:start',
|
||||
'preselected' => sprintf('nullable|in:%s', implode(',', config('firefly.preselected_accounts'))),
|
||||
'period' => sprintf('nullable|in:%s', implode(',', config('firefly.valid_view_ranges'))),
|
||||
'accounts.*' => 'exists:accounts,id',
|
||||
];
|
||||
|
||||
// return [
|
||||
// 'start' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
// 'end' => 'required|date|after_or_equal:start|before:2099-12-31|after:1900-01-01',
|
||||
// 'preselected' => sprintf('in:%s', implode(',', config('firefly.preselected_accounts'))),
|
||||
// 'accounts.*' => 'exists:accounts,id',
|
||||
// ];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
|
@@ -44,7 +44,7 @@ class DateRequest extends FormRequest
|
||||
public function getAll(): array
|
||||
{
|
||||
return [
|
||||
'start' => $this->getCarbonDate('start'),
|
||||
'start' => $this->getCarbonDate('start')->startOfDay(),
|
||||
'end' => $this->getCarbonDate('end')->endOfDay(),
|
||||
];
|
||||
}
|
||||
|
@@ -147,7 +147,7 @@ class StoreRequest extends FormRequest
|
||||
// all custom fields:
|
||||
'internal_reference' => $this->clearString((string)$object['internal_reference']),
|
||||
'external_id' => $this->clearString((string)$object['external_id']),
|
||||
'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')),
|
||||
'original_source' => sprintf('ff3-v%s', config('firefly.version')),
|
||||
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
|
||||
'bunq_payment_id' => $this->clearString((string)$object['bunq_payment_id']),
|
||||
'external_url' => $this->clearString((string)$object['external_url']),
|
||||
|
@@ -48,8 +48,19 @@ class StoreRequest extends FormRequest
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
$roles = [];
|
||||
foreach (UserRoleEnum::cases() as $role) {
|
||||
$roles[] = $role->value;
|
||||
}
|
||||
$string = implode(',', $roles);
|
||||
|
||||
return [
|
||||
'title' => 'unique:user_groups,title|required|min:1|max:255',
|
||||
'title' => 'unique:user_groups,title|required|min:1|max:255',
|
||||
'members' => 'required|min:1',
|
||||
'members.*.user_email' => 'email|missing_with:members.*.user_id',
|
||||
'members.*.user_id' => 'integer|exists:users,id|missing_with:members.*.user_email',
|
||||
'members.*.roles' => 'required|array|min:1',
|
||||
'members.*.roles.*' => sprintf('required|in:%s', $string),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
44
app/Casts/SeparateTimezoneCaster.php
Normal file
44
app/Casts/SeparateTimezoneCaster.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Casts;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Class SeparateTimezoneCaster
|
||||
*
|
||||
* Checks if the object has a separate _tz value. If it does, it will use that timezone to parse the date.
|
||||
* If it is NULL, it will use the system's timezone.
|
||||
*
|
||||
* At some point a user's database consists entirely of UTC dates, and we won't need this anymore. However,
|
||||
* the completeness of this migration is not yet guaranteed.
|
||||
*/
|
||||
class SeparateTimezoneCaster implements CastsAttributes
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $attributes
|
||||
*/
|
||||
public function get(Model $model, string $key, mixed $value, array $attributes): ?Carbon
|
||||
{
|
||||
if ('' === $value || null === $value) {
|
||||
return null;
|
||||
}
|
||||
$timeZone = $attributes[sprintf('%s_tz', $key)] ?? config('app.timezone');
|
||||
|
||||
return Carbon::parse($value, $timeZone)->setTimezone(config('app.timezone'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the given value for storage.
|
||||
*
|
||||
* @param array<string, mixed> $attributes
|
||||
*/
|
||||
public function set(Model $model, string $key, mixed $value, array $attributes): mixed
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
}
|
@@ -26,6 +26,8 @@ namespace FireflyIII\Console\Commands\Correction;
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Support\Models\AccountBalanceCalculator;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -38,14 +40,22 @@ class FixUnevenAmount extends Command
|
||||
|
||||
protected $description = 'Fix journals with uneven amounts.';
|
||||
protected $signature = 'firefly-iii:fix-uneven-amount';
|
||||
private int $count;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->count = 0;
|
||||
$this->convertOldStyleTransfers();
|
||||
$this->fixUnevenAmounts();
|
||||
$this->matchCurrencies();
|
||||
if (config('firefly.feature_flags.running_balance_column')) {
|
||||
$this->friendlyInfo('Will recalculate transaction running balance columns. This may take a LONG time. Please be patient.');
|
||||
AccountBalanceCalculator::recalculateAll(true);
|
||||
$this->friendlyInfo('Done recalculating transaction running balance columns.');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -71,6 +81,7 @@ class FixUnevenAmount extends Command
|
||||
);
|
||||
Transaction::where('transaction_journal_id', $journal->id ?? 0)->forceDelete();
|
||||
TransactionJournal::where('id', $journal->id ?? 0)->forceDelete();
|
||||
++$this->count;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -92,20 +103,33 @@ class FixUnevenAmount extends Command
|
||||
|
||||
Transaction::where('transaction_journal_id', $journal->id ?? 0)->forceDelete();
|
||||
TransactionJournal::where('id', $journal->id ?? 0)->forceDelete();
|
||||
++$this->count;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// may still be able to salvage this journal if it is a transfer with foreign currency info
|
||||
if ($this->isForeignCurrencyTransfer($journal)) {
|
||||
Log::debug(sprintf('Can skip foreign currency transfer #%d.', $journal->id));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$message = sprintf('Sum of journal #%d is not zero, journal is broken and now fixed.', $journal->id);
|
||||
|
||||
$this->friendlyWarning($message);
|
||||
app('log')->warning($message);
|
||||
|
||||
$destination->amount = $amount;
|
||||
$destination->save();
|
||||
|
||||
$message = sprintf('Corrected amount in transaction journal #%d', $param);
|
||||
$this->friendlyInfo($message);
|
||||
++$this->count;
|
||||
}
|
||||
|
||||
private function fixUnevenAmounts(): void
|
||||
{
|
||||
$count = 0;
|
||||
$journals = \DB::table('transactions')
|
||||
->groupBy('transaction_journal_id')
|
||||
->whereNull('deleted_at')
|
||||
@@ -126,7 +150,7 @@ class FixUnevenAmount extends Command
|
||||
);
|
||||
$this->friendlyWarning($message);
|
||||
app('log')->warning($message);
|
||||
++$count;
|
||||
++$this->count;
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -140,18 +164,10 @@ class FixUnevenAmount extends Command
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
if (0 !== $res) {
|
||||
$message = sprintf(
|
||||
'Sum of journal #%d is %s instead of zero.',
|
||||
$entry->transaction_journal_id,
|
||||
$entry->the_sum
|
||||
);
|
||||
$this->friendlyWarning($message);
|
||||
app('log')->warning($message);
|
||||
$this->fixJournal($entry->transaction_journal_id);
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
if (0 === $count) {
|
||||
if (0 === $this->count) {
|
||||
$this->friendlyPositive('Database amount integrity is OK');
|
||||
}
|
||||
}
|
||||
@@ -162,16 +178,119 @@ class FixUnevenAmount extends Command
|
||||
->where('transactions.transaction_currency_id', '!=', \DB::raw('transaction_journals.transaction_currency_id'))
|
||||
->get(['transaction_journals.*'])
|
||||
;
|
||||
if (0 === $journals->count()) {
|
||||
|
||||
$count = 0;
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($journals as $journal) {
|
||||
if (!$this->isForeignCurrencyTransfer($journal)) {
|
||||
Transaction::where('transaction_journal_id', $journal->id)->update(['transaction_currency_id' => $journal->transaction_currency_id]);
|
||||
++$count;
|
||||
|
||||
continue;
|
||||
}
|
||||
Log::debug(sprintf('Can skip foreign currency transfer #%d.', $journal->id));
|
||||
}
|
||||
if (0 === $count) {
|
||||
$this->friendlyPositive('Journal currency integrity is OK');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($journals as $journal) {
|
||||
Transaction::where('transaction_journal_id', $journal->id)->update(['transaction_currency_id' => $journal->transaction_currency_id]);
|
||||
}
|
||||
$this->friendlyPositive(sprintf('Fixed %d journal(s) with mismatched currencies.', $journals->count()));
|
||||
}
|
||||
|
||||
private function isForeignCurrencyTransfer(TransactionJournal $journal): bool
|
||||
{
|
||||
if (TransactionType::TRANSFER !== $journal->transactionType->type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var Transaction $destination */
|
||||
$destination = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
|
||||
/** @var Transaction $source */
|
||||
$source = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
|
||||
// safety catch on NULL should not be necessary, we just had that catch.
|
||||
// source amount = dest foreign amount
|
||||
// source currency = dest foreign currency
|
||||
// dest amount = source foreign currency
|
||||
// dest currency = source foreign currency
|
||||
|
||||
// Log::debug(sprintf('[a] %s', bccomp(app('steam')->positive($source->amount), app('steam')->positive($destination->foreign_amount))));
|
||||
// Log::debug(sprintf('[b] %s', bccomp(app('steam')->positive($destination->amount), app('steam')->positive($source->foreign_amount))));
|
||||
// Log::debug(sprintf('[c] %s', var_export($source->transaction_currency_id === $destination->foreign_currency_id,true)));
|
||||
// Log::debug(sprintf('[d] %s', var_export((int) $destination->transaction_currency_id ===(int) $source->foreign_currency_id, true)));
|
||||
|
||||
if (0 === bccomp(app('steam')->positive($source->amount), app('steam')->positive($destination->foreign_amount))
|
||||
&& $source->transaction_currency_id === $destination->foreign_currency_id
|
||||
&& 0 === bccomp(app('steam')->positive($destination->amount), app('steam')->positive($source->foreign_amount))
|
||||
&& (int) $destination->transaction_currency_id === (int) $source->foreign_currency_id
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function convertOldStyleTransfers(): void
|
||||
{
|
||||
Log::debug('convertOldStyleTransfers()');
|
||||
// select transactions with a foreign amount and a foreign currency. and it's a transfer. and they are different.
|
||||
$transactions = Transaction::distinct()
|
||||
->whereNotNull('foreign_currency_id')
|
||||
->whereNotNull('foreign_amount')->get(['transactions.transaction_journal_id'])
|
||||
;
|
||||
$count = 0;
|
||||
|
||||
Log::debug(sprintf('Found %d potential journal(s)', $transactions->count()));
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
/** @var null|TransactionJournal $journal */
|
||||
$journal = TransactionJournal::find($transaction->transaction_journal_id);
|
||||
if (null === $journal) {
|
||||
Log::debug('Found no journal, continue.');
|
||||
|
||||
continue;
|
||||
}
|
||||
// needs to be a transfer.
|
||||
if (TransactionType::TRANSFER !== $journal->transactionType->type) {
|
||||
Log::debug('Must be a transfer, continue.');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var null|Transaction $destination */
|
||||
$destination = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
|
||||
/** @var null|Transaction $source */
|
||||
$source = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
if (null === $destination || null === $source) {
|
||||
Log::debug('Source or destination transaction is NULL, continue.');
|
||||
|
||||
// will be picked up later.
|
||||
continue;
|
||||
}
|
||||
if ($source->transaction_currency_id === $destination->transaction_currency_id) {
|
||||
Log::debug('Ready to swap data between transactions.');
|
||||
$destination->foreign_currency_id = $source->transaction_currency_id;
|
||||
$destination->foreign_amount = app('steam')->positive($source->amount);
|
||||
$destination->transaction_currency_id = $source->foreign_currency_id;
|
||||
$destination->amount = app('steam')->positive($source->foreign_amount);
|
||||
$destination->balance_dirty = true;
|
||||
$source->balance_dirty = true;
|
||||
$destination->save();
|
||||
$source->save();
|
||||
$this->friendlyWarning(sprintf('Corrected foreign amounts of transfer #%d.', $journal->id));
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
if (0 === $count) {
|
||||
$this->friendlyPositive('No "old style" foreign currency transfers.');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
117
app/Console/Commands/Integrity/AddTimezonesToDates.php
Normal file
117
app/Console/Commands/Integrity/AddTimezonesToDates.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* AddTimezonesToDates.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Console\Commands\Integrity;
|
||||
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use FireflyIII\Models\AccountBalance;
|
||||
use FireflyIII\Models\AvailableBudget;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\CurrencyExchangeRate;
|
||||
use FireflyIII\Models\InvitedUser;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\PiggyBankRepetition;
|
||||
use FireflyIII\Models\Recurrence;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class AddTimezonesToDates extends Command
|
||||
{
|
||||
use ShowsFriendlyMessages;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly-iii:add-timezones-to-dates';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Make sure all dates have a timezone.';
|
||||
|
||||
public static array $models = [
|
||||
AccountBalance::class => ['date'], // done
|
||||
AvailableBudget::class => ['start_date', 'end_date'], // done
|
||||
Bill::class => ['date', 'end_date', 'extension_date'], // done
|
||||
BudgetLimit::class => ['start_date', 'end_date'], // done
|
||||
CurrencyExchangeRate::class => ['date'], // done
|
||||
InvitedUser::class => ['expires'],
|
||||
PiggyBankEvent::class => ['date'],
|
||||
PiggyBankRepetition::class => ['startdate', 'targetdate'],
|
||||
PiggyBank::class => ['startdate', 'targetdate'], // done
|
||||
Recurrence::class => ['first_date', 'repeat_until', 'latest_date'],
|
||||
Tag::class => ['date'],
|
||||
TransactionJournal::class => ['date'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
foreach (self::$models as $model => $fields) {
|
||||
$this->addTimezoneToModel($model, $fields);
|
||||
}
|
||||
// not yet in UTC mode
|
||||
FireflyConfig::set('utc', false);
|
||||
}
|
||||
|
||||
private function addTimezoneToModel(string $model, array $fields): void
|
||||
{
|
||||
foreach ($fields as $field) {
|
||||
$this->addTimezoneToModelField($model, $field);
|
||||
}
|
||||
}
|
||||
|
||||
private function addTimezoneToModelField(string $model, string $field): void
|
||||
{
|
||||
$shortModel = str_replace('FireflyIII\Models\\', '', $model);
|
||||
$timezoneField = sprintf('%s_tz', $field);
|
||||
$count = 0;
|
||||
|
||||
try {
|
||||
$count = $model::whereNull($timezoneField)->count();
|
||||
} catch (QueryException $e) {
|
||||
$this->friendlyError(sprintf('Cannot add timezone information to field "%s" of model "%s". Field does not exist.', $field, $shortModel));
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
if (0 === $count) {
|
||||
$this->friendlyPositive(sprintf('Timezone information is present in field "%s" of model "%s".', $field, $shortModel));
|
||||
|
||||
return;
|
||||
}
|
||||
$this->friendlyInfo(sprintf('Adding timezone information to field "%s" of model "%s".', $field, $shortModel));
|
||||
|
||||
$model::whereNull($timezoneField)->update([$timezoneField => config('app.timezone')]);
|
||||
}
|
||||
}
|
109
app/Console/Commands/Integrity/ConvertDatesToUTC.php
Normal file
109
app/Console/Commands/Integrity/ConvertDatesToUTC.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* ConvertDatesToUTC.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Console\Commands\Integrity;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ConvertDatesToUTC extends Command
|
||||
{
|
||||
use ShowsFriendlyMessages;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly-iii:migrate-to-utc';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Convert stored dates to UTC.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
/**
|
||||
* @var string $model
|
||||
* @var array $fields
|
||||
*/
|
||||
foreach (AddTimezonesToDates::$models as $model => $fields) {
|
||||
$this->ConvertModeltoUTC($model, $fields);
|
||||
}
|
||||
// tell the system we are now in UTC mode.
|
||||
FireflyConfig::set('utc', true);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function ConvertModeltoUTC(string $model, array $fields): void
|
||||
{
|
||||
/** @var string $field */
|
||||
foreach ($fields as $field) {
|
||||
$this->convertFieldtoUTC($model, $field);
|
||||
}
|
||||
}
|
||||
|
||||
private function convertFieldtoUTC(string $model, string $field): void
|
||||
{
|
||||
$this->info(sprintf('Converting %s.%s to UTC', $model, $field));
|
||||
$shortModel = str_replace('FireflyIII\Models\\', '', $model);
|
||||
$timezoneField = sprintf('%s_tz', $field);
|
||||
$items = new Collection();
|
||||
$timeZone = config('app.timezone');
|
||||
|
||||
try {
|
||||
$items = $model::where($timezoneField, $timeZone)->get();
|
||||
} catch (QueryException $e) {
|
||||
$this->friendlyError(sprintf('Cannot find timezone information to field "%s" of model "%s". Field does not exist.', $field, $shortModel));
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
if (0 === $items->count()) {
|
||||
$this->friendlyPositive(sprintf('All timezone information is UTC in field "%s" of model "%s".', $field, $shortModel));
|
||||
|
||||
return;
|
||||
}
|
||||
$this->friendlyInfo(sprintf('Converting field "%s" of model "%s" to UTC.', $field, $shortModel));
|
||||
$items->each(
|
||||
function ($item) use ($field, $timezoneField): void {
|
||||
/** @var Carbon $date */
|
||||
$date = Carbon::parse($item->{$field}, $item->{$timezoneField});
|
||||
$date->setTimezone('UTC');
|
||||
$item->{$field} = $date->format('Y-m-d H:i:s');
|
||||
$item->{$timezoneField} = 'UTC';
|
||||
$item->save();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@@ -47,6 +47,7 @@ class ReportIntegrity extends Command
|
||||
return 1;
|
||||
}
|
||||
$commands = [
|
||||
'firefly-iii:add-timezones-to-dates',
|
||||
'firefly-iii:create-group-memberships',
|
||||
'firefly-iii:report-empty-objects',
|
||||
'firefly-iii:report-sum',
|
||||
|
@@ -58,7 +58,7 @@ class ReportSum extends Command
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($userRepository->all() as $user) {
|
||||
$sum = (string)$user->transactions()->sum('amount');
|
||||
$sum = (string)$user->transactions()->selectRaw('SUM(amount) + SUM(foreign_amount) as total')->value('total');
|
||||
if (!is_numeric($sum)) {
|
||||
$message = sprintf('Error: Transactions for user #%d (%s) have an invalid sum ("%s").', $user->id, $user->email, $sum);
|
||||
$this->friendlyError($message);
|
||||
|
@@ -80,7 +80,7 @@ class ForceMigration extends Command
|
||||
sleep(2);
|
||||
Schema::dropIfExists('migrations');
|
||||
$this->friendlyLine('Re-run all migrations...');
|
||||
Artisan::call('migrate', ['--seed' => true]);
|
||||
Artisan::call('migrate', ['--seed' => true, '--force' => true]);
|
||||
sleep(2);
|
||||
$this->friendlyLine('');
|
||||
$this->friendlyWarning('There is a good chance you just saw a lot of error messages.');
|
||||
|
@@ -46,10 +46,15 @@ class Cron extends Command
|
||||
protected $signature = 'firefly-iii:cron
|
||||
{--F|force : Force the cron job(s) to execute.}
|
||||
{--date= : Set the date in YYYY-MM-DD to make Firefly III think that\'s the current date.}
|
||||
{--download-cer : Download exchange rates. Other tasks will be skipped unless also requested.}
|
||||
{--create-recurring : Create recurring transactions. Other tasks will be skipped unless also requested.}
|
||||
{--create-auto-budgets : Create auto budgets. Other tasks will be skipped unless also requested.}
|
||||
{--send-bill-warnings : Send bill warnings. Other tasks will be skipped unless also requested.}
|
||||
';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$doAll = !$this->option('download-cer') && !$this->option('create-recurring') && !$this->option('create-auto-budgets') && !$this->option('send-bill-warnings');
|
||||
$date = null;
|
||||
|
||||
try {
|
||||
@@ -60,7 +65,7 @@ class Cron extends Command
|
||||
$force = (bool)$this->option('force'); // @phpstan-ignore-line
|
||||
|
||||
// Fire exchange rates cron job.
|
||||
if (true === config('cer.download_enabled')) {
|
||||
if (true === config('cer.download_enabled') && ($doAll || $this->option('download-cer'))) {
|
||||
try {
|
||||
$this->exchangeRatesCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
@@ -71,30 +76,36 @@ class Cron extends Command
|
||||
}
|
||||
|
||||
// Fire recurring transaction cron job.
|
||||
try {
|
||||
$this->recurringCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
$this->friendlyError($e->getMessage());
|
||||
if ($doAll || $this->option('create-recurring')) {
|
||||
try {
|
||||
$this->recurringCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
$this->friendlyError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Fire auto-budget cron job:
|
||||
try {
|
||||
$this->autoBudgetCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
$this->friendlyError($e->getMessage());
|
||||
if ($doAll || $this->option('create-auto-budgets')) {
|
||||
try {
|
||||
$this->autoBudgetCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
$this->friendlyError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Fire bill warning cron job
|
||||
try {
|
||||
$this->billWarningCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
$this->friendlyError($e->getMessage());
|
||||
if ($doAll || $this->option('send-bill-warnings')) {
|
||||
try {
|
||||
$this->billWarningCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
$this->friendlyError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$this->friendlyInfo('More feedback on the cron jobs can be found in the log files.');
|
||||
|
@@ -33,6 +33,7 @@ use Illuminate\Console\Command;
|
||||
class CorrectAccountBalance extends Command
|
||||
{
|
||||
use ShowsFriendlyMessages;
|
||||
|
||||
public const string CONFIG_NAME = '610_correct_balances';
|
||||
protected $description = 'Recalculate all account balance amounts';
|
||||
protected $signature = 'firefly-iii:correct-account-balance {--F|force : Force the execution of this command.}';
|
||||
@@ -44,22 +45,30 @@ class CorrectAccountBalance extends Command
|
||||
|
||||
return 0;
|
||||
}
|
||||
$this->correctBalanceAmounts();
|
||||
$this->markAsExecuted();
|
||||
if (config('firefly.feature_flags.running_balance_column')) {
|
||||
$this->friendlyInfo('Will recalculate account balances. This may take a LONG time. Please be patient.');
|
||||
$this->markAsExecuted();
|
||||
$this->correctBalanceAmounts();
|
||||
$this->friendlyInfo('Done recalculating account balances.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
$this->friendlyWarning('This command has been disabled.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function correctBalanceAmounts(): void
|
||||
{
|
||||
AccountBalanceCalculator::recalculateAll();
|
||||
return;
|
||||
AccountBalanceCalculator::recalculateAll(true);
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool)$configVar?->data;
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
|
@@ -47,7 +47,7 @@ class FixPostgresSequences extends Command
|
||||
return 0;
|
||||
}
|
||||
$this->friendlyLine('Going to verify PostgreSQL table sequences.');
|
||||
$tablesToCheck = ['2fa_tokens', 'account_meta', 'account_types', 'accounts', 'attachments', 'auto_budgets', 'available_budgets', 'bills', 'budget_limits', 'budget_transaction', 'budget_transaction_journal', 'budgets', 'categories', 'category_transaction', 'category_transaction_journal', 'configuration', 'currency_exchange_rates', 'failed_jobs', 'group_journals', 'jobs', 'journal_links', 'journal_meta', 'limit_repetitions', 'link_types', 'locations', 'migrations', 'notes', 'oauth_clients', 'oauth_personal_access_clients', 'object_groups', 'permissions', 'piggy_bank_events', 'piggy_bank_repetitions', 'piggy_banks', 'preferences', 'recurrences', 'recurrences_meta', 'recurrences_repetitions', 'recurrences_transactions', 'roles', 'rt_meta', 'rule_actions', 'rule_groups', 'rule_triggers', 'rules', 'tag_transaction_journal', 'tags', 'transaction_currencies', 'transaction_groups', 'transaction_journals', 'transaction_types', 'transactions', 'users', 'webhook_attempts', 'webhook_messages', 'webhooks'];
|
||||
$tablesToCheck = ['2fa_tokens', 'account_meta', 'account_types', 'accounts', 'attachments', 'auto_budgets', 'available_budgets', 'bills', 'budget_limits', 'budget_transaction', 'budget_transaction_journal', 'budgets', 'categories', 'category_transaction', 'category_transaction_journal', 'configuration', 'currency_exchange_rates', 'failed_jobs', 'group_journals', 'jobs', 'journal_links', 'journal_meta', 'link_types', 'locations', 'migrations', 'notes', 'oauth_clients', 'oauth_personal_access_clients', 'object_groups', 'permissions', 'piggy_bank_events', 'piggy_bank_repetitions', 'piggy_banks', 'preferences', 'recurrences', 'recurrences_meta', 'recurrences_repetitions', 'recurrences_transactions', 'roles', 'rt_meta', 'rule_actions', 'rule_groups', 'rule_triggers', 'rules', 'tag_transaction_journal', 'tags', 'transaction_currencies', 'transaction_groups', 'transaction_journals', 'transaction_types', 'transactions', 'users', 'webhook_attempts', 'webhook_messages', 'webhooks'];
|
||||
|
||||
foreach ($tablesToCheck as $tableToCheck) {
|
||||
$this->friendlyLine(sprintf('Checking the next id sequence for table "%s".', $tableToCheck));
|
||||
|
@@ -54,6 +54,7 @@ class MigrateRuleActions extends Command
|
||||
}
|
||||
$this->replaceEqualSign();
|
||||
$this->replaceObsoleteActions();
|
||||
$this->markAsExecuted();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -179,4 +180,9 @@ class MigrateRuleActions extends Command
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
}
|
||||
}
|
||||
|
@@ -90,7 +90,7 @@ class OtherCurrenciesCorrections extends Command
|
||||
{
|
||||
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
|
||||
if (null !== $configVar) {
|
||||
return (bool)$configVar->data;
|
||||
return (bool) $configVar->data;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -120,7 +120,7 @@ class OtherCurrenciesCorrections extends Command
|
||||
$this->journalRepos->setUser($journal->user);
|
||||
$this->cliRepos->setUser($journal->user);
|
||||
|
||||
$leadTransaction = $this->getLeadTransaction($journal);
|
||||
$leadTransaction = $this->getLeadTransaction($journal);
|
||||
|
||||
if (null === $leadTransaction) {
|
||||
$this->friendlyError(sprintf('Could not reliably determine which transaction is in the lead for transaction journal #%d.', $journal->id));
|
||||
@@ -128,8 +128,9 @@ class OtherCurrenciesCorrections extends Command
|
||||
return;
|
||||
}
|
||||
|
||||
$account = $leadTransaction->account;
|
||||
$currency = $this->getCurrency($account);
|
||||
$account = $leadTransaction->account;
|
||||
$currency = $this->getCurrency($account);
|
||||
$isMultiCurrency = $this->isMultiCurrency($account);
|
||||
if (null === $currency) {
|
||||
$this->friendlyError(
|
||||
sprintf(
|
||||
@@ -145,14 +146,14 @@ class OtherCurrenciesCorrections extends Command
|
||||
}
|
||||
// fix each transaction:
|
||||
$journal->transactions->each(
|
||||
static function (Transaction $transaction) use ($currency): void {
|
||||
static function (Transaction $transaction) use ($currency, $isMultiCurrency): void {
|
||||
if (null === $transaction->transaction_currency_id) {
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
$transaction->save();
|
||||
}
|
||||
|
||||
// when mismatch in transaction:
|
||||
if ($transaction->transaction_currency_id !== $currency->id) {
|
||||
if ($transaction->transaction_currency_id !== $currency->id && !$isMultiCurrency) {
|
||||
$transaction->foreign_currency_id = $transaction->transaction_currency_id;
|
||||
$transaction->foreign_amount = $transaction->amount;
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
@@ -161,7 +162,9 @@ class OtherCurrenciesCorrections extends Command
|
||||
}
|
||||
);
|
||||
// also update the journal, of course:
|
||||
$journal->transaction_currency_id = $currency->id;
|
||||
if (!$isMultiCurrency) {
|
||||
$journal->transaction_currency_id = $currency->id;
|
||||
}
|
||||
++$this->count;
|
||||
$journal->save();
|
||||
}
|
||||
@@ -239,4 +242,14 @@ class OtherCurrenciesCorrections extends Command
|
||||
{
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
private function isMultiCurrency(Account $account): bool
|
||||
{
|
||||
$value = $this->accountRepos->getMetaValue($account, 'is_multi_currency', false);
|
||||
if (false === $value || null === $value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return '1' === $value;
|
||||
}
|
||||
}
|
||||
|
@@ -154,7 +154,7 @@ class TransactionIdentifier extends Command
|
||||
app('log')->error($e->getMessage());
|
||||
$this->friendlyError('Firefly III could not find the "identifier" field in the "transactions" table.');
|
||||
$this->friendlyError(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version')));
|
||||
$this->friendlyError('Please run "php artisan migrate" to add this field to the table.');
|
||||
$this->friendlyError('Please run "php artisan migrate --force" to add this field to the table.');
|
||||
$this->friendlyError('Then, run "php artisan firefly:upgrade-database" to try again.');
|
||||
|
||||
return null;
|
||||
|
@@ -67,6 +67,7 @@ class UpgradeDatabase extends Command
|
||||
'firefly-iii:restore-oauth-keys',
|
||||
'firefly-iii:correct-account-balance',
|
||||
// also just in case, some integrity commands:
|
||||
'firefly-iii:add-timezones-to-dates',
|
||||
'firefly-iii:create-group-memberships',
|
||||
'firefly-iii:upgrade-group-information',
|
||||
'firefly-iii:upgrade-currency-preferences',
|
||||
|
@@ -30,6 +30,7 @@ namespace FireflyIII\Enums;
|
||||
enum UserRoleEnum: string
|
||||
{
|
||||
// most basic rights, cannot see other members, can see everything else.
|
||||
// includes reading of metadata
|
||||
case READ_ONLY = 'ro';
|
||||
|
||||
// required to even USE the group properly (in this order)
|
||||
@@ -38,6 +39,15 @@ enum UserRoleEnum: string
|
||||
// required to edit, add or change categories/tags/object-groups
|
||||
case MANAGE_META = 'mng_meta';
|
||||
|
||||
// read other objects and things.
|
||||
case READ_BUDGETS = 'read_budgets';
|
||||
case READ_PIGGY_BANKS = 'read_piggies';
|
||||
case READ_SUBSCRIPTIONS = 'read_subscriptions';
|
||||
case READ_RULES = 'read_rules';
|
||||
case READ_RECURRING = 'read_recurring';
|
||||
case READ_WEBHOOKS = 'read_webhooks';
|
||||
case READ_CURRENCIES = 'read_currencies';
|
||||
|
||||
// manage other financial objects:
|
||||
case MANAGE_BUDGETS = 'mng_budgets';
|
||||
case MANAGE_PIGGY_BANKS = 'mng_piggies';
|
||||
|
43
app/Events/Security/DisabledMFA.php
Normal file
43
app/Events/Security/DisabledMFA.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/*
|
||||
* EnabledMFA.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Security;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class DisabledMFA extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public User $user;
|
||||
|
||||
public function __construct(null|Authenticatable|User $user)
|
||||
{
|
||||
if ($user instanceof User) {
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
}
|
43
app/Events/Security/EnabledMFA.php
Normal file
43
app/Events/Security/EnabledMFA.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/*
|
||||
* EnabledMFA.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Security;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class EnabledMFA extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public User $user;
|
||||
|
||||
public function __construct(null|Authenticatable|User $user)
|
||||
{
|
||||
if ($user instanceof User) {
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
}
|
45
app/Events/Security/MFABackupFewLeft.php
Normal file
45
app/Events/Security/MFABackupFewLeft.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/*
|
||||
* EnabledMFA.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Security;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MFABackupFewLeft extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public User $user;
|
||||
public int $count;
|
||||
|
||||
public function __construct(null|Authenticatable|User $user, int $count)
|
||||
{
|
||||
if ($user instanceof User) {
|
||||
$this->user = $user;
|
||||
}
|
||||
$this->count = $count;
|
||||
}
|
||||
}
|
43
app/Events/Security/MFABackupNoLeft.php
Normal file
43
app/Events/Security/MFABackupNoLeft.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/*
|
||||
* EnabledMFA.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Security;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MFABackupNoLeft extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public User $user;
|
||||
|
||||
public function __construct(null|Authenticatable|User $user)
|
||||
{
|
||||
if ($user instanceof User) {
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
}
|
45
app/Events/Security/MFAManyFailedAttempts.php
Normal file
45
app/Events/Security/MFAManyFailedAttempts.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/*
|
||||
* EnabledMFA.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Security;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MFAManyFailedAttempts extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public User $user;
|
||||
public int $count;
|
||||
|
||||
public function __construct(null|Authenticatable|User $user, int $count)
|
||||
{
|
||||
if ($user instanceof User) {
|
||||
$this->user = $user;
|
||||
}
|
||||
$this->count = $count;
|
||||
}
|
||||
}
|
43
app/Events/Security/MFANewBackupCodes.php
Normal file
43
app/Events/Security/MFANewBackupCodes.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/*
|
||||
* EnabledMFA.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Security;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MFANewBackupCodes extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public User $user;
|
||||
|
||||
public function __construct(null|Authenticatable|User $user)
|
||||
{
|
||||
if ($user instanceof User) {
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
}
|
43
app/Events/Security/MFAUsedBackupCode.php
Normal file
43
app/Events/Security/MFAUsedBackupCode.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/*
|
||||
* EnabledMFA.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Security;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MFAUsedBackupCode extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public User $user;
|
||||
|
||||
public function __construct(null|Authenticatable|User $user)
|
||||
{
|
||||
if ($user instanceof User) {
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
}
|
@@ -37,6 +37,7 @@ use Illuminate\Support\Arr;
|
||||
use Illuminate\Validation\ValidationException as LaravelValidationException;
|
||||
use Laravel\Passport\Exceptions\OAuthServerException as LaravelOAuthException;
|
||||
use LaravelJsonApi\Core\Exceptions\JsonApiException;
|
||||
use LaravelJsonApi\Exceptions\ExceptionParser;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -67,6 +68,16 @@ class Handler extends ExceptionHandler
|
||||
JsonApiException::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Register the exception handling callbacks for the application.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->renderable(
|
||||
ExceptionParser::make()->renderable()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an exception into an HTTP response. It's complex but lucky for us, we never use it because
|
||||
* Firefly III never crashes.
|
||||
@@ -81,15 +92,23 @@ class Handler extends ExceptionHandler
|
||||
public function render($request, \Throwable $e): Response
|
||||
{
|
||||
$expectsJson = $request->expectsJson();
|
||||
// if the user requests anything /api/, assume the user wants to see JSON.
|
||||
if (str_starts_with($request->getRequestUri(), '/api/')) {
|
||||
app('log')->debug('API endpoint, always assume user wants JSON.');
|
||||
$expectsJson = true;
|
||||
}
|
||||
|
||||
app('log')->debug('Now in Handler::render()');
|
||||
|
||||
if ($e instanceof JsonApiException) {
|
||||
// ignore it: controller will handle it.
|
||||
|
||||
app('log')->debug(sprintf(
|
||||
'Return to parent to handle JsonApiException(%d)',
|
||||
$e->getCode()
|
||||
));
|
||||
|
||||
return parent::render($request, $e);
|
||||
}
|
||||
|
||||
if ($e instanceof LaravelValidationException && $expectsJson) {
|
||||
// ignore it: controller will handle it.
|
||||
|
||||
app('log')->debug(sprintf('Return to parent to handle LaravelValidationException(%d)', $e->status));
|
||||
|
||||
return parent::render($request, $e);
|
||||
|
@@ -58,20 +58,23 @@ class BillFactory
|
||||
/** @var Bill $bill */
|
||||
$bill = Bill::create(
|
||||
[
|
||||
'name' => $data['name'],
|
||||
'match' => 'MIGRATED_TO_RULES',
|
||||
'amount_min' => $data['amount_min'],
|
||||
'user_id' => $this->user->id,
|
||||
'user_group_id' => $this->user->user_group_id,
|
||||
'transaction_currency_id' => $currency->id,
|
||||
'amount_max' => $data['amount_max'],
|
||||
'date' => $data['date'],
|
||||
'end_date' => $data['end_date'] ?? null,
|
||||
'extension_date' => $data['extension_date'] ?? null,
|
||||
'repeat_freq' => $data['repeat_freq'],
|
||||
'skip' => $skip,
|
||||
'automatch' => true,
|
||||
'active' => $active,
|
||||
'name' => $data['name'],
|
||||
'match' => 'MIGRATED_TO_RULES',
|
||||
'amount_min' => $data['amount_min'],
|
||||
'user_id' => $this->user->id,
|
||||
'user_group_id' => $this->user->user_group_id,
|
||||
'transaction_currency_id' => $currency->id,
|
||||
'amount_max' => $data['amount_max'],
|
||||
'date' => $data['date'],
|
||||
'date_tz' => $data['date']->format('e'),
|
||||
'end_date' => $data['end_date'] ?? null,
|
||||
'end_date_tz' => $data['end_date']?->format('e'),
|
||||
'extension_date' => $data['extension_date'] ?? null,
|
||||
'extension_date_tz' => $data['extension_date']?->format('e'),
|
||||
'repeat_freq' => $data['repeat_freq'],
|
||||
'skip' => $skip,
|
||||
'automatch' => true,
|
||||
'active' => $active,
|
||||
]
|
||||
);
|
||||
} catch (QueryException $e) {
|
||||
@@ -126,7 +129,7 @@ class BillFactory
|
||||
|
||||
public function findByName(string $name): ?Bill
|
||||
{
|
||||
return $this->user->bills()->where('name', 'LIKE', sprintf('%%%s%%', $name))->first();
|
||||
return $this->user->bills()->whereLike('name', sprintf('%%%s%%', $name))->first();
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
|
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Factory;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Exceptions\DuplicateTransactionException;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
@@ -43,10 +44,12 @@ use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface;
|
||||
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Services\Internal\Destroy\JournalDestroyService;
|
||||
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use FireflyIII\Support\NullArrayObject;
|
||||
use FireflyIII\User;
|
||||
use FireflyIII\Validation\AccountValidator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class TransactionJournalFactory
|
||||
@@ -156,7 +159,7 @@ class TransactionJournalFactory
|
||||
|
||||
$this->errorIfDuplicate($row['import_hash_v2']);
|
||||
|
||||
/** Some basic fields */
|
||||
// Some basic fields
|
||||
$type = $this->typeRepository->findTransactionType(null, $row['type']);
|
||||
$carbon = $row['date'] ?? today(config('app.timezone'));
|
||||
$order = $row['order'] ?? 0;
|
||||
@@ -169,6 +172,13 @@ class TransactionJournalFactory
|
||||
// Manipulate basic fields
|
||||
$carbon->setTimezone(config('app.timezone'));
|
||||
|
||||
// 2024-11-19, overrule timezone with UTC and store it as UTC.
|
||||
|
||||
if (FireflyConfig::get('utc', false)->data) {
|
||||
$carbon->setTimezone('UTC');
|
||||
}
|
||||
// $carbon->setTimezone('UTC');
|
||||
|
||||
try {
|
||||
// validate source and destination using a new Validator.
|
||||
$this->validateAccounts($row);
|
||||
@@ -204,7 +214,7 @@ class TransactionJournalFactory
|
||||
app('log')->debug('Done with getAccount(2x)');
|
||||
|
||||
// this is the moment for a reconciliation sanity check (again).
|
||||
if (TransactionType::RECONCILIATION === $type->type) {
|
||||
if (TransactionTypeEnum::RECONCILIATION->value === $type->type) {
|
||||
[$sourceAccount, $destinationAccount] = $this->reconciliationSanityCheck($sourceAccount, $destinationAccount);
|
||||
}
|
||||
|
||||
@@ -224,7 +234,8 @@ class TransactionJournalFactory
|
||||
'bill_id' => $billId,
|
||||
'transaction_currency_id' => $currency->id,
|
||||
'description' => substr($description, 0, 1000),
|
||||
'date' => $carbon->format('Y-m-d H:i:s'),
|
||||
'date' => $carbon,
|
||||
'date_tz' => $carbon->format('e'),
|
||||
'order' => $order,
|
||||
'tag_count' => 0,
|
||||
'completed' => 0,
|
||||
@@ -261,8 +272,24 @@ class TransactionJournalFactory
|
||||
$transactionFactory->setForeignCurrency($foreignCurrency);
|
||||
$transactionFactory->setReconciled($row['reconciled'] ?? false);
|
||||
|
||||
// if the foreign currency is set and is different, and the transaction type is a transfer,
|
||||
// Firefly III will save the foreign currency information in such a way that both
|
||||
// asset accounts can look at the "amount" and "transaction_currency_id" column and
|
||||
// see the currency they expect to see.
|
||||
$amount = (string)$row['amount'];
|
||||
$foreignAmount = (string)$row['foreign_amount'];
|
||||
if (null !== $foreignCurrency && $foreignCurrency->id !== $currency->id
|
||||
&& TransactionType::TRANSFER === $type->type
|
||||
) {
|
||||
$transactionFactory->setCurrency($foreignCurrency);
|
||||
$transactionFactory->setForeignCurrency($currency);
|
||||
$amount = (string)$row['foreign_amount'];
|
||||
$foreignAmount = (string)$row['amount'];
|
||||
Log::debug('Swap native/foreign amounts in transfer for new save method.');
|
||||
}
|
||||
|
||||
try {
|
||||
$transactionFactory->createPositive((string)$row['amount'], (string)$row['foreign_amount']);
|
||||
$transactionFactory->createPositive($amount, $foreignAmount);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error(sprintf('Exception creating positive transaction: %s', $e->getMessage()));
|
||||
$this->forceTrDelete($negative);
|
||||
|
@@ -131,12 +131,14 @@ class BudgetLimitHandler
|
||||
app('log')->debug(sprintf('Will create AB for period %s to %s', $current->format('Y-m-d'), $currentEnd->format('Y-m-d')));
|
||||
$availableBudget = new AvailableBudget(
|
||||
[
|
||||
'user_id' => $budgetLimit->budget->user->id,
|
||||
'user_group_id' => $budgetLimit->budget->user->user_group_id,
|
||||
'transaction_currency_id' => $budgetLimit->transaction_currency_id,
|
||||
'start_date' => $current,
|
||||
'end_date' => $currentEnd,
|
||||
'amount' => $amount,
|
||||
'user_id' => $budgetLimit->budget->user->id,
|
||||
'user_group_id' => $budgetLimit->budget->user->user_group_id,
|
||||
'transaction_currency_id' => $budgetLimit->transaction_currency_id,
|
||||
'start_date' => $current,
|
||||
'start_date_tz' => $current->format('e'),
|
||||
'end_date' => $currentEnd,
|
||||
'end_date_tz' => $currentEnd->format('e'),
|
||||
'amount' => $amount,
|
||||
]
|
||||
);
|
||||
$availableBudget->save();
|
||||
|
@@ -58,6 +58,7 @@ class PiggyBankEventHandler
|
||||
'piggy_bank_id' => $event->piggyBank->id,
|
||||
'transaction_journal_id' => $journal?->id,
|
||||
'date' => $date->format('Y-m-d'),
|
||||
'date_tz' => $date->format('e'),
|
||||
'amount' => $event->amount,
|
||||
]
|
||||
);
|
||||
|
220
app/Handlers/Events/Security/MFAHandler.php
Normal file
220
app/Handlers/Events/Security/MFAHandler.php
Normal file
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
/*
|
||||
* MFAHandler.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events\Security;
|
||||
|
||||
use FireflyIII\Events\Security\DisabledMFA;
|
||||
use FireflyIII\Events\Security\EnabledMFA;
|
||||
use FireflyIII\Events\Security\MFABackupFewLeft;
|
||||
use FireflyIII\Events\Security\MFABackupNoLeft;
|
||||
use FireflyIII\Events\Security\MFAManyFailedAttempts;
|
||||
use FireflyIII\Events\Security\MFANewBackupCodes;
|
||||
use FireflyIII\Events\Security\MFAUsedBackupCode;
|
||||
use FireflyIII\Notifications\Security\DisabledMFANotification;
|
||||
use FireflyIII\Notifications\Security\EnabledMFANotification;
|
||||
use FireflyIII\Notifications\Security\MFABackupFewLeftNotification;
|
||||
use FireflyIII\Notifications\Security\MFABackupNoLeftNotification;
|
||||
use FireflyIII\Notifications\Security\MFAManyFailedAttemptsNotification;
|
||||
use FireflyIII\Notifications\Security\MFAUsedBackupCodeNotification;
|
||||
use FireflyIII\Notifications\Security\NewBackupCodesNotification;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
class MFAHandler
|
||||
{
|
||||
public function sendMFAEnabledMail(EnabledMFA $event): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
$user = $event->user;
|
||||
|
||||
try {
|
||||
Notification::send($user, new EnabledMFANotification($user));
|
||||
} catch (\Exception $e) { // @phpstan-ignore-line
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
app('log')->warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
app('log')->warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
public function sendNewMFABackupCodesMail(MFANewBackupCodes $event): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
$user = $event->user;
|
||||
|
||||
try {
|
||||
Notification::send($user, new NewBackupCodesNotification($user));
|
||||
} catch (\Exception $e) { // @phpstan-ignore-line
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
app('log')->warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
app('log')->warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
public function sendBackupFewLeftMail(MFABackupFewLeft $event): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
$user = $event->user;
|
||||
$count = $event->count;
|
||||
|
||||
try {
|
||||
Notification::send($user, new MFABackupFewLeftNotification($user, $count));
|
||||
} catch (\Exception $e) { // @phpstan-ignore-line
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
app('log')->warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
app('log')->warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
public function sendMFAFailedAttemptsMail(MFAManyFailedAttempts $event): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
$user = $event->user;
|
||||
$count = $event->count;
|
||||
|
||||
try {
|
||||
Notification::send($user, new MFAManyFailedAttemptsNotification($user, $count));
|
||||
} catch (\Exception $e) { // @phpstan-ignore-line
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
app('log')->warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
app('log')->warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
public function sendBackupNoLeftMail(MFABackupNoLeft $event): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
$user = $event->user;
|
||||
|
||||
try {
|
||||
Notification::send($user, new MFABackupNoLeftNotification($user));
|
||||
} catch (\Exception $e) { // @phpstan-ignore-line
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
app('log')->warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
app('log')->warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
public function sendUsedBackupCodeMail(MFAUsedBackupCode $event): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
$user = $event->user;
|
||||
|
||||
try {
|
||||
Notification::send($user, new MFAUsedBackupCodeNotification($user));
|
||||
} catch (\Exception $e) { // @phpstan-ignore-line
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
app('log')->warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
app('log')->warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
public function sendMFADisabledMail(DisabledMFA $event): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
$user = $event->user;
|
||||
|
||||
try {
|
||||
Notification::send($user, new DisabledMFANotification($user));
|
||||
} catch (\Exception $e) { // @phpstan-ignore-line
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
app('log')->warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
app('log')->warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
}
|
@@ -37,7 +37,9 @@ class PiggyBankObserver
|
||||
$repetition = new PiggyBankRepetition();
|
||||
$repetition->piggyBank()->associate($piggyBank);
|
||||
$repetition->startdate = $piggyBank->startdate;
|
||||
$repetition->startdate_tz = $piggyBank->startdate->format('e');
|
||||
$repetition->targetdate = $piggyBank->targetdate;
|
||||
$repetition->targetdate_tz = $piggyBank->targetdate?->format('e');
|
||||
$repetition->currentamount = '0';
|
||||
$repetition->save();
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ namespace FireflyIII\Handlers\Observer;
|
||||
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Support\Models\AccountBalanceCalculator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class TransactionObserver
|
||||
@@ -39,13 +40,19 @@ class TransactionObserver
|
||||
|
||||
public function updated(Transaction $transaction): void
|
||||
{
|
||||
app('log')->debug('Observe "updated" of a transaction.');
|
||||
AccountBalanceCalculator::recalculateForJournal($transaction->transactionJournal);
|
||||
Log::debug('Observe "updated" of a transaction.');
|
||||
if (1 === bccomp($transaction->amount, '0')) {
|
||||
Log::debug('Trigger recalculateForJournal');
|
||||
AccountBalanceCalculator::recalculateForJournal($transaction->transactionJournal);
|
||||
}
|
||||
}
|
||||
|
||||
public function created(Transaction $transaction): void
|
||||
{
|
||||
app('log')->debug('Observe "created" of a transaction.');
|
||||
AccountBalanceCalculator::recalculateForJournal($transaction->transactionJournal);
|
||||
Log::debug('Observe "created" of a transaction.');
|
||||
if (1 === bccomp($transaction->amount, '0')) {
|
||||
Log::debug('Trigger recalculateForJournal');
|
||||
AccountBalanceCalculator::recalculateForJournal($transaction->transactionJournal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -93,7 +93,7 @@ trait AttachmentCollection
|
||||
->where(
|
||||
static function (EloquentBuilder $q1): void { // @phpstan-ignore-line
|
||||
$q1->where('attachments.attachable_type', TransactionJournal::class);
|
||||
$q1->where('attachments.uploaded', true);
|
||||
// $q1->where('attachments.uploaded', true);
|
||||
$q1->whereNull('attachments.deleted_at');
|
||||
$q1->orWhereNull('attachments.attachable_type');
|
||||
}
|
||||
|
@@ -200,7 +200,7 @@ trait MetaCollection
|
||||
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'internal_reference');
|
||||
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s%%', $internalReference));
|
||||
$this->query->whereNotLike('journal_meta.data', sprintf('%%%s%%', $internalReference));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -221,7 +221,7 @@ trait MetaCollection
|
||||
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'external_id');
|
||||
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $externalId));
|
||||
$this->query->whereLike('journal_meta.data', sprintf('%%%s%%', $externalId));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -233,7 +233,7 @@ trait MetaCollection
|
||||
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'external_id');
|
||||
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s%%', $externalId));
|
||||
$this->query->whereNotLike('journal_meta.data', sprintf('%%%s%%', $externalId));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -245,7 +245,7 @@ trait MetaCollection
|
||||
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'external_id');
|
||||
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s"', $externalId));
|
||||
$this->query->whereNotLike('journal_meta.data', sprintf('%%%s"', $externalId));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -257,7 +257,7 @@ trait MetaCollection
|
||||
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'external_id');
|
||||
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId));
|
||||
$this->query->whereLike('journal_meta.data', sprintf('"%s%%', $externalId));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -269,7 +269,7 @@ trait MetaCollection
|
||||
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'external_id');
|
||||
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s"', $externalId));
|
||||
$this->query->whereLike('journal_meta.data', sprintf('%%%s"', $externalId));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -281,7 +281,7 @@ trait MetaCollection
|
||||
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'external_id');
|
||||
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId));
|
||||
$this->query->whereLike('journal_meta.data', sprintf('"%s%%', $externalId));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -292,7 +292,7 @@ trait MetaCollection
|
||||
$url = (string)json_encode($url);
|
||||
$url = str_replace('\\', '\\\\', trim($url, '"'));
|
||||
$this->query->where('journal_meta.name', '=', 'external_url');
|
||||
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $url));
|
||||
$this->query->whereLike('journal_meta.data', sprintf('%%%s%%', $url));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -303,7 +303,7 @@ trait MetaCollection
|
||||
$url = (string)json_encode($url);
|
||||
$url = str_replace('\\', '\\\\', trim($url, '"'));
|
||||
$this->query->where('journal_meta.name', '=', 'external_url');
|
||||
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s%%', $url));
|
||||
$this->query->whereNotLike('journal_meta.data', sprintf('%%%s%%', $url));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -314,7 +314,7 @@ trait MetaCollection
|
||||
$url = (string)json_encode($url);
|
||||
$url = str_replace('\\', '\\\\', ltrim($url, '"'));
|
||||
$this->query->where('journal_meta.name', '=', 'external_url');
|
||||
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s', $url));
|
||||
$this->query->whereNotLike('journal_meta.data', sprintf('%%%s', $url));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -327,7 +327,7 @@ trait MetaCollection
|
||||
// var_dump($url);
|
||||
|
||||
$this->query->where('journal_meta.name', '=', 'external_url');
|
||||
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%s%%', $url));
|
||||
$this->query->whereNotLike('journal_meta.data', sprintf('%s%%', $url));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -338,7 +338,7 @@ trait MetaCollection
|
||||
$url = (string)json_encode($url);
|
||||
$url = str_replace('\\', '\\\\', ltrim($url, '"'));
|
||||
$this->query->where('journal_meta.name', '=', 'external_url');
|
||||
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s', $url));
|
||||
$this->query->whereLike('journal_meta.data', sprintf('%%%s', $url));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -351,7 +351,7 @@ trait MetaCollection
|
||||
// var_dump($url);
|
||||
|
||||
$this->query->where('journal_meta.name', '=', 'external_url');
|
||||
$this->query->where('journal_meta.data', 'LIKE', sprintf('%s%%', $url));
|
||||
$this->query->whereLike('journal_meta.data', sprintf('%s%%', $url));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -404,7 +404,7 @@ trait MetaCollection
|
||||
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'internal_reference');
|
||||
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $internalReference));
|
||||
$this->query->whereLike('journal_meta.data', sprintf('%%%s%%', $internalReference));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -416,7 +416,7 @@ trait MetaCollection
|
||||
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'internal_reference');
|
||||
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s%%', $internalReference));
|
||||
$this->query->whereNotLike('journal_meta.data', sprintf('%%%s%%', $internalReference));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -428,7 +428,7 @@ trait MetaCollection
|
||||
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'internal_reference');
|
||||
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s"', $internalReference));
|
||||
$this->query->whereNotLike('journal_meta.data', sprintf('%%%s"', $internalReference));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -440,7 +440,7 @@ trait MetaCollection
|
||||
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'internal_reference');
|
||||
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $internalReference));
|
||||
$this->query->whereLike('journal_meta.data', sprintf('"%s%%', $internalReference));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -452,7 +452,7 @@ trait MetaCollection
|
||||
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'internal_reference');
|
||||
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s"', $internalReference));
|
||||
$this->query->whereLike('journal_meta.data', sprintf('%%%s"', $internalReference));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -464,7 +464,7 @@ trait MetaCollection
|
||||
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'internal_reference');
|
||||
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $internalReference));
|
||||
$this->query->whereLike('journal_meta.data', sprintf('"%s%%', $internalReference));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -472,7 +472,7 @@ trait MetaCollection
|
||||
public function notesContain(string $value): GroupCollectorInterface
|
||||
{
|
||||
$this->withNotes();
|
||||
$this->query->where('notes.text', 'LIKE', sprintf('%%%s%%', $value));
|
||||
$this->query->whereLike('notes.text', sprintf('%%%s%%', $value));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -502,7 +502,7 @@ trait MetaCollection
|
||||
$this->withNotes();
|
||||
$this->query->where(static function (Builder $q) use ($value): void { // @phpstan-ignore-line
|
||||
$q->whereNull('notes.text');
|
||||
$q->orWhere('notes.text', 'NOT LIKE', sprintf('%%%s%%', $value));
|
||||
$q->orWhereNotLike('notes.text', sprintf('%%%s%%', $value));
|
||||
});
|
||||
|
||||
return $this;
|
||||
@@ -513,7 +513,7 @@ trait MetaCollection
|
||||
$this->withNotes();
|
||||
$this->query->where(static function (Builder $q) use ($value): void { // @phpstan-ignore-line
|
||||
$q->whereNull('notes.text');
|
||||
$q->orWhere('notes.text', 'NOT LIKE', sprintf('%%%s', $value));
|
||||
$q->orWhereNotLike('notes.text', sprintf('%%%s', $value));
|
||||
});
|
||||
|
||||
return $this;
|
||||
@@ -524,7 +524,7 @@ trait MetaCollection
|
||||
$this->withNotes();
|
||||
$this->query->where(static function (Builder $q) use ($value): void { // @phpstan-ignore-line
|
||||
$q->whereNull('notes.text');
|
||||
$q->orWhere('notes.text', 'NOT LIKE', sprintf('%s%%', $value));
|
||||
$q->orWhereNotLike('notes.text', sprintf('%s%%', $value));
|
||||
});
|
||||
|
||||
return $this;
|
||||
@@ -533,7 +533,7 @@ trait MetaCollection
|
||||
public function notesEndWith(string $value): GroupCollectorInterface
|
||||
{
|
||||
$this->withNotes();
|
||||
$this->query->where('notes.text', 'LIKE', sprintf('%%%s', $value));
|
||||
$this->query->whereLike('notes.text', sprintf('%%%s', $value));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -560,7 +560,7 @@ trait MetaCollection
|
||||
public function notesStartWith(string $value): GroupCollectorInterface
|
||||
{
|
||||
$this->withNotes();
|
||||
$this->query->where('notes.text', 'LIKE', sprintf('%s%%', $value));
|
||||
$this->query->whereLike('notes.text', sprintf('%s%%', $value));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -717,7 +717,7 @@ trait MetaCollection
|
||||
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'internal_reference');
|
||||
$this->query->where('journal_meta.data', '=', $internalReference);
|
||||
$this->query->where('journal_meta.data', '=', sprintf('%s', json_encode($internalReference)));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@@ -117,6 +117,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
'transaction_journals.transaction_type_id',
|
||||
'transaction_journals.description',
|
||||
'transaction_journals.date',
|
||||
'transaction_journals.date_tz',
|
||||
'transaction_journals.order',
|
||||
|
||||
// types
|
||||
@@ -156,7 +157,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
static function (EloquentBuilder $q1) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%%%s', $word);
|
||||
$q1->where('transaction_journals.description', 'NOT LIKE', $keyword);
|
||||
$q1->whereNotLike('transaction_journals.description', $keyword);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -164,7 +165,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
static function (EloquentBuilder $q2) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%%%s', $word);
|
||||
$q2->where('transaction_groups.title', 'NOT LIKE', $keyword);
|
||||
$q2->whereNotLike('transaction_groups.title', $keyword);
|
||||
$q2->orWhereNull('transaction_groups.title');
|
||||
}
|
||||
}
|
||||
@@ -183,7 +184,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
static function (EloquentBuilder $q1) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%s%%', $word);
|
||||
$q1->where('transaction_journals.description', 'NOT LIKE', $keyword);
|
||||
$q1->whereNotLike('transaction_journals.description', $keyword);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -191,7 +192,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
static function (EloquentBuilder $q2) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%s%%', $word);
|
||||
$q2->where('transaction_groups.title', 'NOT LIKE', $keyword);
|
||||
$q2->whereNotLike('transaction_groups.title', $keyword);
|
||||
$q2->orWhereNull('transaction_groups.title');
|
||||
}
|
||||
}
|
||||
@@ -210,7 +211,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
static function (EloquentBuilder $q1) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%%%s', $word);
|
||||
$q1->where('transaction_journals.description', 'LIKE', $keyword);
|
||||
$q1->whereLike('transaction_journals.description', $keyword);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -218,7 +219,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
static function (EloquentBuilder $q2) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%%%s', $word);
|
||||
$q2->where('transaction_groups.title', 'LIKE', $keyword);
|
||||
$q2->whereLike('transaction_groups.title', $keyword);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -265,7 +266,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
static function (EloquentBuilder $q1) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%s%%', $word);
|
||||
$q1->where('transaction_journals.description', 'LIKE', $keyword);
|
||||
$q1->whereLike('transaction_journals.description', $keyword);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -273,7 +274,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
static function (EloquentBuilder $q2) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%s%%', $word);
|
||||
$q2->where('transaction_groups.title', 'LIKE', $keyword);
|
||||
$q2->whereLike('transaction_groups.title', $keyword);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -379,7 +380,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
static function (EloquentBuilder $q1) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%%%s%%', $word);
|
||||
$q1->where('transaction_journals.description', 'NOT LIKE', $keyword);
|
||||
$q1->whereNotLike('transaction_journals.description', $keyword);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -387,7 +388,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
static function (EloquentBuilder $q2) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%%%s%%', $word);
|
||||
$q2->where('transaction_groups.title', 'NOT LIKE', $keyword);
|
||||
$q2->whereNotLike('transaction_groups.title', $keyword);
|
||||
$q2->orWhereNull('transaction_groups.title');
|
||||
}
|
||||
}
|
||||
@@ -805,7 +806,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
return 'zzz';
|
||||
}
|
||||
|
||||
exit('here we are');
|
||||
exit('here we are 2');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -944,7 +945,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
static function (EloquentBuilder $q1) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%%%s%%', $word);
|
||||
$q1->where('transaction_journals.description', 'LIKE', $keyword);
|
||||
$q1->whereLike('transaction_journals.description', $keyword);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -952,7 +953,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
static function (EloquentBuilder $q2) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%%%s%%', $word);
|
||||
$q2->where('transaction_groups.title', 'LIKE', $keyword);
|
||||
$q2->whereLike('transaction_groups.title', $keyword);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@@ -103,7 +103,8 @@ class PopupReport implements PopupReportInterface
|
||||
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setAccounts($attributes['accounts'])
|
||||
$collector
|
||||
->setAccounts($attributes['accounts'])
|
||||
->withAccountInformation()
|
||||
->withBudgetInformation()
|
||||
->withCategoryInformation()
|
||||
@@ -113,11 +114,10 @@ class PopupReport implements PopupReportInterface
|
||||
if (null !== $currency) {
|
||||
$collector->setCurrency($currency);
|
||||
}
|
||||
|
||||
if (null === $budget->id) {
|
||||
if (null === $budget->id || 0 === $budget->id) {
|
||||
$collector->setTypes([TransactionType::WITHDRAWAL])->withoutBudget();
|
||||
}
|
||||
if (null !== $budget->id) {
|
||||
if (null !== $budget->id && 0 !== $budget->id) {
|
||||
$collector->setBudget($budget);
|
||||
}
|
||||
|
||||
|
@@ -77,12 +77,16 @@ class LoginController extends Controller
|
||||
*/
|
||||
public function login(Request $request): JsonResponse|RedirectResponse
|
||||
{
|
||||
Log::channel('audit')->info(sprintf('User is trying to login using "%s"', $request->get($this->username())));
|
||||
$username = $request->get($this->username());
|
||||
Log::channel('audit')->info(sprintf('User is trying to login using "%s"', $username));
|
||||
app('log')->debug('User is trying to login.');
|
||||
|
||||
try {
|
||||
$this->validateLogin($request);
|
||||
} catch (ValidationException $e) {
|
||||
// basic validation exception.
|
||||
// report the failed login to the user if the count is 2 or 5.
|
||||
// TODO here be warning.
|
||||
return redirect(route('login'))
|
||||
->withErrors(
|
||||
[
|
||||
|
@@ -23,6 +23,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Auth;
|
||||
|
||||
use FireflyIII\Events\Security\MFABackupFewLeft;
|
||||
use FireflyIII\Events\Security\MFABackupNoLeft;
|
||||
use FireflyIII\Events\Security\MFAManyFailedAttempts;
|
||||
use FireflyIII\Events\Security\MFAUsedBackupCode;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
@@ -30,6 +34,7 @@ use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use PragmaRX\Google2FALaravel\Support\Authenticator;
|
||||
|
||||
/**
|
||||
@@ -47,7 +52,7 @@ class TwoFactorController extends Controller
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$siteOwner = config('firefly.site_owner');
|
||||
$title = (string)trans('firefly.two_factor_forgot_title');
|
||||
$title = (string) trans('firefly.two_factor_forgot_title');
|
||||
|
||||
return view('auth.lost-two-factor', compact('user', 'siteOwner', 'title'));
|
||||
}
|
||||
@@ -59,7 +64,7 @@ class TwoFactorController extends Controller
|
||||
{
|
||||
/** @var array $mfaHistory */
|
||||
$mfaHistory = app('preferences')->get('mfa_history', [])->data;
|
||||
$mfaCode = (string)$request->get('one_time_password');
|
||||
$mfaCode = (string) $request->get('one_time_password');
|
||||
|
||||
// is in history? then refuse to use it.
|
||||
if ($this->inMFAHistory($mfaCode, $mfaHistory)) {
|
||||
@@ -72,10 +77,26 @@ class TwoFactorController extends Controller
|
||||
/** @var Authenticator $authenticator */
|
||||
$authenticator = app(Authenticator::class)->boot($request);
|
||||
|
||||
// if not OK, save error.
|
||||
if (!$authenticator->isAuthenticated()) {
|
||||
$user = auth()->user();
|
||||
$this->addToMFAFailureCounter();
|
||||
$counter = $this->getMFAFailureCounter();
|
||||
if (3 === $counter || 10 === $counter) {
|
||||
// do not reset MFA failure counter, but DO send a warning to the user.
|
||||
Log::channel('audit')->info(sprintf('User "%s" has had %d failed MFA attempts.', $user->email, $counter));
|
||||
event(new MFAManyFailedAttempts($user, $counter));
|
||||
}
|
||||
unset($user);
|
||||
}
|
||||
|
||||
if ($authenticator->isAuthenticated()) {
|
||||
// save MFA in preferences
|
||||
$this->addToMFAHistory($mfaCode);
|
||||
|
||||
// reset failure count
|
||||
$this->resetMFAFailureCounter();
|
||||
|
||||
// otp auth success!
|
||||
return redirect(route('home'));
|
||||
}
|
||||
@@ -85,7 +106,14 @@ class TwoFactorController extends Controller
|
||||
$this->removeFromBackupCodes($mfaCode);
|
||||
$authenticator->login();
|
||||
|
||||
// reset failure count
|
||||
$this->resetMFAFailureCounter();
|
||||
|
||||
session()->flash('info', trans('firefly.mfa_backup_code'));
|
||||
// send user notification.
|
||||
$user = auth()->user();
|
||||
Log::channel('audit')->info(sprintf('User "%s" has used a backup code.', $user->email));
|
||||
event(new MFAUsedBackupCode($user));
|
||||
|
||||
return redirect(route('home'));
|
||||
}
|
||||
@@ -175,6 +203,42 @@ class TwoFactorController extends Controller
|
||||
$list = [];
|
||||
}
|
||||
$newList = array_values(array_diff($list, [$mfaCode]));
|
||||
|
||||
// if the list is 3 or less, send a notification.
|
||||
if (count($newList) <= 3 && count($newList) > 0) {
|
||||
$user = auth()->user();
|
||||
Log::channel('audit')->info(sprintf('User "%s" has used a backup code. They have %d backup codes left.', $user->email, count($newList)));
|
||||
event(new MFABackupFewLeft($user, count($newList)));
|
||||
}
|
||||
// if the list is empty, send notification
|
||||
if (0 === count($newList)) {
|
||||
$user = auth()->user();
|
||||
Log::channel('audit')->info(sprintf('User "%s" has used their last backup code.', $user->email));
|
||||
event(new MFABackupNoLeft($user));
|
||||
}
|
||||
|
||||
app('preferences')->set('mfa_recovery', $newList);
|
||||
}
|
||||
|
||||
private function addToMFAFailureCounter(): void
|
||||
{
|
||||
$preference = (int) app('preferences')->get('mfa_failure_count', 0)->data;
|
||||
++$preference;
|
||||
Log::channel('audit')->info(sprintf('MFA failure count is set to %d.', $preference));
|
||||
app('preferences')->set('mfa_failure_count', $preference);
|
||||
}
|
||||
|
||||
private function getMFAFailureCounter(): int
|
||||
{
|
||||
$value = (int) app('preferences')->get('mfa_failure_count', 0)->data;
|
||||
Log::channel('audit')->info(sprintf('MFA failure count is %d.', $value));
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function resetMFAFailureCounter(): void
|
||||
{
|
||||
app('preferences')->set('mfa_failure_count', 0);
|
||||
Log::channel('audit')->info('MFA failure count is set to zero.');
|
||||
}
|
||||
}
|
||||
|
@@ -40,6 +40,7 @@ use FireflyIII\Support\Http\Controllers\ChartGeneration;
|
||||
use FireflyIII\Support\Http\Controllers\DateCalculation;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class AccountController.
|
||||
@@ -300,13 +301,13 @@ class AccountController extends Controller
|
||||
$start = clone session('start', today(config('app.timezone'))->startOfMonth());
|
||||
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
|
||||
$defaultSet = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray();
|
||||
app('log')->debug('Default set is ', $defaultSet);
|
||||
Log::debug('Default set is ', $defaultSet);
|
||||
$frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
|
||||
$frontpageArray = !is_array($frontpage->data) ? [] : $frontpage->data;
|
||||
app('log')->debug('Frontpage preference set is ', $frontpageArray);
|
||||
Log::debug('Frontpage preference set is ', $frontpageArray);
|
||||
if (0 === count($frontpageArray)) {
|
||||
app('preferences')->set('frontpageAccounts', $defaultSet);
|
||||
app('log')->debug('frontpage set is empty!');
|
||||
Log::debug('frontpage set is empty!');
|
||||
}
|
||||
$accounts = $repository->getAccountsById($frontpageArray);
|
||||
|
||||
@@ -414,7 +415,7 @@ class AccountController extends Controller
|
||||
*/
|
||||
private function periodByCurrency(Carbon $start, Carbon $end, Account $account, TransactionCurrency $currency): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in periodByCurrency("%s", "%s", %s, "%s")', $start->format('Y-m-d'), $end->format('Y-m-d'), $account->id, $currency->code));
|
||||
Log::debug(sprintf('Now in periodByCurrency("%s", "%s", %s, "%s")', $start->format('Y-m-d'), $end->format('Y-m-d'), $account->id, $currency->code));
|
||||
$locale = app('steam')->getLocale();
|
||||
$step = $this->calculateStep($start, $end);
|
||||
$result = [
|
||||
@@ -424,13 +425,13 @@ class AccountController extends Controller
|
||||
];
|
||||
$entries = [];
|
||||
$current = clone $start;
|
||||
app('log')->debug(sprintf('Step is %s', $step));
|
||||
Log::debug(sprintf('Step is %s', $step));
|
||||
|
||||
// fix for issue https://github.com/firefly-iii/firefly-iii/issues/8041
|
||||
// have to make sure this chart is always based on the balance at the END of the period.
|
||||
// This period depends on the size of the chart
|
||||
$current = app('navigation')->endOfX($current, $step, null);
|
||||
app('log')->debug(sprintf('$current date is %s', $current->format('Y-m-d')));
|
||||
Log::debug(sprintf('$current date is %s', $current->format('Y-m-d')));
|
||||
if ('1D' === $step) {
|
||||
// per day the entire period, balance for every day.
|
||||
$format = (string)trans('config.month_and_day_js', [], $locale);
|
||||
@@ -447,7 +448,7 @@ class AccountController extends Controller
|
||||
}
|
||||
if ('1W' === $step || '1M' === $step || '1Y' === $step) {
|
||||
while ($end >= $current) {
|
||||
app('log')->debug(sprintf('Current is: %s', $current->format('Y-m-d')));
|
||||
Log::debug(sprintf('Current is: %s', $current->format('Y-m-d')));
|
||||
$balance = (float)app('steam')->balance($account, $current, $currency);
|
||||
$label = app('navigation')->periodShow($current, $step);
|
||||
$entries[$label] = $balance;
|
||||
|
@@ -151,6 +151,7 @@ class CategoryController extends Controller
|
||||
*/
|
||||
private function reportPeriodChart(Collection $accounts, Carbon $start, Carbon $end, ?Category $category): array
|
||||
{
|
||||
|
||||
$income = [];
|
||||
$expenses = [];
|
||||
$categoryId = 0;
|
||||
@@ -169,8 +170,8 @@ class CategoryController extends Controller
|
||||
$categoryId = $category->id;
|
||||
// this gives us all currencies
|
||||
$collection = new Collection([$category]);
|
||||
$expenses = $opsRepository->listExpenses($start, $end, null, $collection);
|
||||
$income = $opsRepository->listIncome($start, $end, null, $collection);
|
||||
$expenses = $opsRepository->listExpenses($start, $end, $accounts, $collection);
|
||||
$income = $opsRepository->listIncome($start, $end, $accounts, $collection);
|
||||
}
|
||||
$currencies = array_unique(array_merge(array_keys($income), array_keys($expenses)));
|
||||
$periods = app('navigation')->listOfPeriods($start, $end);
|
||||
|
@@ -122,6 +122,7 @@ class CategoryReportController extends Controller
|
||||
|
||||
public function categoryIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): JsonResponse
|
||||
{
|
||||
|
||||
$result = [];
|
||||
$earned = $this->opsRepository->listIncome($start, $end, $accounts, $categories);
|
||||
|
||||
|
@@ -24,18 +24,19 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Controllers\Chart;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Helpers\Report\NetWorthInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use FireflyIII\Support\Http\Controllers\BasicDataSupport;
|
||||
use FireflyIII\Support\Http\Controllers\ChartGeneration;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class ReportController.
|
||||
@@ -88,7 +89,7 @@ class ReportController extends Controller
|
||||
$includeNetWorth = $accountRepository->getMetaValue($account, 'include_net_worth');
|
||||
$result = null === $includeNetWorth ? true : '1' === $includeNetWorth;
|
||||
if (false === $result) {
|
||||
app('log')->debug(sprintf('Will not include "%s" in net worth charts.', $account->name));
|
||||
Log::debug(sprintf('Will not include "%s" in net worth charts.', $account->name));
|
||||
}
|
||||
|
||||
return $result;
|
||||
@@ -136,6 +137,7 @@ class ReportController extends Controller
|
||||
*/
|
||||
public function operations(Collection $accounts, Carbon $start, Carbon $end): JsonResponse
|
||||
{
|
||||
$end->endOfDay();
|
||||
// chart properties for cache:
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty('chart.report.operations');
|
||||
@@ -146,7 +148,8 @@ class ReportController extends Controller
|
||||
// return response()->json($cache->get());
|
||||
}
|
||||
|
||||
app('log')->debug('Going to do operations for accounts ', $accounts->pluck('id')->toArray());
|
||||
Log::debug('Going to do operations for accounts ', $accounts->pluck('id')->toArray());
|
||||
Log::debug(sprintf('Period: %s to %s', $start->toW3cString(), $end->toW3cString()));
|
||||
$format = app('navigation')->preferredCarbonFormat($start, $end);
|
||||
$titleFormat = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
|
||||
$preferredRange = app('navigation')->preferredRangeFormat($start, $end);
|
||||
@@ -158,7 +161,14 @@ class ReportController extends Controller
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setRange($start, $end)->withAccountInformation();
|
||||
$collector->setXorAccounts($accounts);
|
||||
$collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::RECONCILIATION, TransactionType::TRANSFER]);
|
||||
$collector->setTypes(
|
||||
[
|
||||
TransactionTypeEnum::WITHDRAWAL,
|
||||
TransactionTypeEnum::DEPOSIT,
|
||||
TransactionTypeEnum::RECONCILIATION,
|
||||
TransactionTypeEnum::TRANSFER,
|
||||
]
|
||||
);
|
||||
$journals = $collector->getExtractedJournals();
|
||||
|
||||
// loop. group by currency and by period.
|
||||
@@ -184,15 +194,25 @@ class ReportController extends Controller
|
||||
|
||||
// deposit = incoming
|
||||
// transfer or reconcile or opening balance, and these accounts are the destination.
|
||||
if (TransactionType::DEPOSIT === $journal['transaction_type_type'] || ((TransactionType::TRANSFER === $journal['transaction_type_type'] || TransactionType::RECONCILIATION === $journal['transaction_type_type'] || TransactionType::OPENING_BALANCE === $journal['transaction_type_type']) && in_array($journal['destination_account_id'], $ids, true))) {
|
||||
if (
|
||||
TransactionTypeEnum::DEPOSIT->value === $journal['transaction_type_type']
|
||||
|| ((
|
||||
TransactionTypeEnum::TRANSFER->value === $journal['transaction_type_type']
|
||||
|| TransactionTypeEnum::RECONCILIATION->value === $journal['transaction_type_type']
|
||||
|| TransactionTypeEnum::OPENING_BALANCE->value === $journal['transaction_type_type']
|
||||
)
|
||||
&& in_array($journal['destination_account_id'], $ids, true))) {
|
||||
$key = 'earned';
|
||||
}
|
||||
$data[$currencyId][$period][$key] = bcadd($data[$currencyId][$period][$key], $amount);
|
||||
}
|
||||
|
||||
// loop this data, make chart bars for each currency:
|
||||
Log::debug('Looping data');
|
||||
|
||||
/** @var array $currency */
|
||||
foreach ($data as $currency) {
|
||||
Log::debug(sprintf('Now processing currency "%s"', $currency['currency_name']));
|
||||
$income = [
|
||||
'label' => (string)trans('firefly.box_earned_in_currency', ['currency' => $currency['currency_name']]),
|
||||
'type' => 'bar',
|
||||
@@ -214,12 +234,15 @@ class ReportController extends Controller
|
||||
// loop all possible periods between $start and $end
|
||||
$currentStart = clone $start;
|
||||
$currentEnd = clone $end;
|
||||
Log::debug(sprintf('START current start and end: %s and %s', $currentStart->toW3cString(), $currentEnd->toW3cString()));
|
||||
|
||||
// #8374. Sloppy fix for yearly charts. Not really interested in a better fix with v2 layout and all.
|
||||
if ('1Y' === $preferredRange) {
|
||||
$currentEnd = app('navigation')->endOfPeriod($currentEnd, $preferredRange);
|
||||
}
|
||||
Log::debug('Start of sub-loop');
|
||||
while ($currentStart <= $currentEnd) {
|
||||
Log::debug(sprintf('Current start: %s', $currentStart->toW3cString()));
|
||||
$key = $currentStart->format($format);
|
||||
$title = $currentStart->isoFormat($titleFormat);
|
||||
// #8663 make sure the period exists in the data previously collected.
|
||||
@@ -227,12 +250,20 @@ class ReportController extends Controller
|
||||
$income['entries'][$title] = app('steam')->bcround($currency[$key]['earned'] ?? '0', $currency['currency_decimal_places']);
|
||||
$expense['entries'][$title] = app('steam')->bcround($currency[$key]['spent'] ?? '0', $currency['currency_decimal_places']);
|
||||
}
|
||||
// #9477 if the period is not in the data, add it with zero values.
|
||||
if (!array_key_exists($key, $currency)) {
|
||||
$income['entries'][$title] = '0';
|
||||
$expense['entries'][$title] = '0';
|
||||
|
||||
}
|
||||
$currentStart = app('navigation')->addPeriod($currentStart, $preferredRange, 0);
|
||||
}
|
||||
Log::debug('End of sub-loop');
|
||||
|
||||
$chartData[] = $income;
|
||||
$chartData[] = $expense;
|
||||
}
|
||||
Log::debug('End of loop');
|
||||
|
||||
$data = $this->generator->multiSet($chartData);
|
||||
$cache->store($data);
|
||||
|
@@ -30,6 +30,7 @@ use FireflyIII\Http\Middleware\IsDemoUser;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Support\Http\Controllers\GetConfigurationData;
|
||||
use FireflyIII\Support\Models\AccountBalanceCalculator;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
@@ -94,6 +95,7 @@ class DebugController extends Controller
|
||||
|
||||
// also do some recalculations.
|
||||
Artisan::call('firefly-iii:trigger-credit-recalculation');
|
||||
AccountBalanceCalculator::recalculateAll(true);
|
||||
|
||||
try {
|
||||
Artisan::call('twig:clean');
|
||||
|
@@ -107,7 +107,6 @@ class JavascriptController extends Controller
|
||||
$lang = $pref->data;
|
||||
$dateRange = $this->getDateRangeConfig();
|
||||
$uid = substr(hash('sha256', sprintf('%s-%s-%s', (string)config('app.key'), auth()->user()->id, auth()->user()->email)), 0, 12);
|
||||
|
||||
$data = [
|
||||
'currencyCode' => $currency->code,
|
||||
'currencySymbol' => $currency->symbol,
|
||||
|
@@ -65,6 +65,16 @@ class FrontpageController extends Controller
|
||||
$info[] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
// sort by current percentage (lowest at the top)
|
||||
uasort(
|
||||
$info,
|
||||
static function (array $a, array $b) {
|
||||
return $a['percentage'] <=> $b['percentage'];
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
$html = '';
|
||||
if (0 !== count($info)) {
|
||||
try {
|
||||
|
342
app/Http/Controllers/Profile/MfaController.php
Normal file
342
app/Http/Controllers/Profile/MfaController.php
Normal file
@@ -0,0 +1,342 @@
|
||||
<?php
|
||||
/*
|
||||
* MfaController.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Profile;
|
||||
|
||||
use FireflyIII\Events\Security\DisabledMFA;
|
||||
use FireflyIII\Events\Security\EnabledMFA;
|
||||
use FireflyIII\Events\Security\MFANewBackupCodes;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Middleware\IsDemoUser;
|
||||
use FireflyIII\Http\Requests\ExistingTokenFormRequest;
|
||||
use FireflyIII\Http\Requests\TokenFormRequest;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
use PragmaRX\Recovery\Recovery;
|
||||
|
||||
/**
|
||||
* Class MfaController
|
||||
*
|
||||
* Enable MFA Flow:
|
||||
*
|
||||
* Page 1 (GET): Show QR code and the manual code. Secret keeps rotating.
|
||||
* POST: store secret, store response, validate password.
|
||||
* ---
|
||||
* Page 3 (GET): Confirm 2FA status and show recovery codes.
|
||||
* Same page as page 1, but when secret is present.
|
||||
*/
|
||||
class MfaController extends Controller
|
||||
{
|
||||
protected bool $internalAuth;
|
||||
|
||||
/**
|
||||
* ProfileController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->middleware(
|
||||
static function ($request, $next) {
|
||||
app('view')->share('title', (string) trans('firefly.profile'));
|
||||
app('view')->share('mainTitleIcon', 'fa-user');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
$authGuard = config('firefly.authentication_guard');
|
||||
$this->internalAuth = 'web' === $authGuard;
|
||||
app('log')->debug(sprintf('ProfileController::__construct(). Authentication guard is "%s"', $authGuard));
|
||||
|
||||
$this->middleware(IsDemoUser::class)->except(['index']);
|
||||
|
||||
}
|
||||
|
||||
public function index(): Factory|RedirectResponse|View
|
||||
{
|
||||
if (!$this->internalAuth) {
|
||||
request()->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
$subTitle = (string)trans('firefly.mfa_index_title');
|
||||
$subTitleIcon = 'fa-calculator';
|
||||
$enabledMFA = null !== auth()->user()->mfa_secret;
|
||||
|
||||
return view('profile.mfa.index')->with(compact('subTitle', 'subTitleIcon', 'enabledMFA'));
|
||||
}
|
||||
|
||||
public function disableMFA(Request $request): Factory|RedirectResponse|View
|
||||
{
|
||||
if (!$this->internalAuth) {
|
||||
request()->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
$enabledMFA = null !== auth()->user()->mfa_secret;
|
||||
if (false === $enabledMFA) {
|
||||
request()->session()->flash('info', trans('firefly.mfa_already_disabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
$subTitle = (string)trans('firefly.mfa_index_title');
|
||||
$subTitleIcon = 'fa-calculator';
|
||||
|
||||
return view('profile.mfa.disable-mfa')->with(compact('subTitle', 'subTitleIcon', 'enabledMFA'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete 2FA routine.
|
||||
*/
|
||||
public function disableMFAPost(ExistingTokenFormRequest $request): Redirector|RedirectResponse
|
||||
{
|
||||
if (!$this->internalAuth) {
|
||||
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
/** @var UserRepositoryInterface $repository */
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
app('preferences')->delete('temp-mfa-secret');
|
||||
app('preferences')->delete('temp-mfa-codes');
|
||||
$repository->setMFACode($user, null);
|
||||
app('preferences')->mark();
|
||||
|
||||
session()->flash('success', (string) trans('firefly.pref_two_factor_auth_disabled'));
|
||||
session()->flash('info', (string) trans('firefly.pref_two_factor_auth_remove_it'));
|
||||
|
||||
// also logout current 2FA tokens.
|
||||
$cookieName = config('google2fa.cookie_name', 'google2fa_token');
|
||||
\Cookie::forget($cookieName);
|
||||
|
||||
// send user notification.
|
||||
Log::channel('audit')->info(sprintf('User "%s" has disabled MFA', $user->email));
|
||||
event(new DisabledMFA($user));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable 2FA screen.
|
||||
*/
|
||||
public function enableMFA(Request $request): Redirector|RedirectResponse|View
|
||||
{
|
||||
if (!$this->internalAuth) {
|
||||
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$enabledMFA = null !== $user->mfa_secret;
|
||||
|
||||
// If FF3 already has a secret, just set the two-factor auth enabled to 1,
|
||||
// and let the user continue with the existing secret.
|
||||
if ($enabledMFA) {
|
||||
session()->flash('info', (string) trans('firefly.2fa_already_enabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
$domain = $this->getDomain();
|
||||
$secret = \Google2FA::generateSecretKey();
|
||||
$image = \Google2FA::getQRCodeInline($domain, auth()->user()->email, (string) $secret);
|
||||
|
||||
app('preferences')->set('temp-mfa-secret', $secret);
|
||||
|
||||
|
||||
|
||||
return view('profile.mfa.enable-mfa', compact('image', 'secret'));
|
||||
|
||||
}
|
||||
|
||||
public function backupCodesPost(ExistingTokenFormRequest $request): Redirector|RedirectResponse|View
|
||||
{
|
||||
if (!$this->internalAuth) {
|
||||
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
$enabledMFA = null !== auth()->user()->mfa_secret;
|
||||
if (false === $enabledMFA) {
|
||||
request()->session()->flash('info', trans('firefly.mfa_not_enabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
// generate recovery codes:
|
||||
$recovery = app(Recovery::class);
|
||||
$recoveryCodes = $recovery->lowercase()
|
||||
->setCount(8) // Generate 8 codes
|
||||
->setBlocks(2) // Every code must have 2 blocks
|
||||
->setChars(6) // Each block must have 6 chars
|
||||
->toArray()
|
||||
;
|
||||
$codes = implode("\r\n", $recoveryCodes);
|
||||
|
||||
app('preferences')->set('mfa_recovery', $recoveryCodes);
|
||||
app('preferences')->mark();
|
||||
|
||||
// send user notification.
|
||||
$user = auth()->user();
|
||||
Log::channel('audit')->info(sprintf('User "%s" has generated new backup codes.', $user->email));
|
||||
event(new MFANewBackupCodes($user));
|
||||
|
||||
return view('profile.mfa.backup-codes-post')->with(compact('codes'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function backupCodes(Request $request): Factory|RedirectResponse|View
|
||||
{
|
||||
if (!$this->internalAuth) {
|
||||
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
$enabledMFA = null !== auth()->user()->mfa_secret;
|
||||
if (false === $enabledMFA) {
|
||||
request()->session()->flash('info', trans('firefly.mfa_not_enabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
return view('profile.mfa.backup-codes-intro');
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit 2FA for the first time.
|
||||
*
|
||||
* @return Redirector|RedirectResponse
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function enableMFAPost(TokenFormRequest $request)
|
||||
{
|
||||
if (!$this->internalAuth) {
|
||||
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
// verify password.
|
||||
$password = $request->get('password');
|
||||
if (!auth()->validate(['email' => $user->email, 'password' => $password])) {
|
||||
session()->flash('error', 'Bad user pw, no MFA for you!');
|
||||
|
||||
return redirect(route('profile.mfa.index'));
|
||||
}
|
||||
|
||||
/** @var UserRepositoryInterface $repository */
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
$secret = app('preferences')->get('temp-mfa-secret')?->data;
|
||||
if (is_array($secret)) {
|
||||
$secret = null;
|
||||
}
|
||||
$secret = (string) $secret;
|
||||
|
||||
$repository->setMFACode($user, $secret);
|
||||
|
||||
app('preferences')->delete('temp-mfa-secret');
|
||||
|
||||
session()->flash('success', (string) trans('firefly.saved_preferences'));
|
||||
app('preferences')->mark();
|
||||
|
||||
// also save the code so replay attack is prevented.
|
||||
$mfaCode = $request->get('code');
|
||||
$this->addToMFAHistory($mfaCode);
|
||||
|
||||
// make sure MFA is logged out.
|
||||
if ('testing' !== config('app.env')) {
|
||||
\Google2FA::logout();
|
||||
}
|
||||
|
||||
// drop all info from session:
|
||||
session()->forget(['temp-mfa-secret', 'two-factor-secret', 'two-factor-codes']);
|
||||
|
||||
// send user notification.
|
||||
Log::channel('audit')->info(sprintf('User "%s" has enabled MFA', $user->email));
|
||||
event(new EnabledMFA($user));
|
||||
|
||||
return redirect(route('profile.mfa.backup-codes'));
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO duplicate code.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function addToMFAHistory(string $mfaCode): void
|
||||
{
|
||||
/** @var array $mfaHistory */
|
||||
$mfaHistory = app('preferences')->get('mfa_history', [])->data;
|
||||
$entry = [
|
||||
'time' => time(),
|
||||
'code' => $mfaCode,
|
||||
];
|
||||
$mfaHistory[] = $entry;
|
||||
|
||||
app('preferences')->set('mfa_history', $mfaHistory);
|
||||
$this->filterMFAHistory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove old entries from the preferences array.
|
||||
*/
|
||||
private function filterMFAHistory(): void
|
||||
{
|
||||
/** @var array $mfaHistory */
|
||||
$mfaHistory = app('preferences')->get('mfa_history', [])->data;
|
||||
$newHistory = [];
|
||||
$now = time();
|
||||
foreach ($mfaHistory as $entry) {
|
||||
$time = $entry['time'];
|
||||
$code = $entry['code'];
|
||||
if ($now - $time <= 300) {
|
||||
$newHistory[] = [
|
||||
'time' => $time,
|
||||
'code' => $code,
|
||||
];
|
||||
}
|
||||
}
|
||||
app('preferences')->set('mfa_history', $newHistory);
|
||||
}
|
||||
}
|
@@ -30,7 +30,6 @@ use FireflyIII\Http\Middleware\IsDemoUser;
|
||||
use FireflyIII\Http\Requests\DeleteAccountFormRequest;
|
||||
use FireflyIII\Http\Requests\EmailFormRequest;
|
||||
use FireflyIII\Http\Requests\ProfileFormRequest;
|
||||
use FireflyIII\Http\Requests\TokenFormRequest;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Controllers\CreateStuff;
|
||||
@@ -45,10 +44,6 @@ use Illuminate\Routing\Redirector;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\View\View;
|
||||
use Laravel\Passport\ClientRepository;
|
||||
use PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException;
|
||||
use PragmaRX\Google2FA\Exceptions\InvalidCharactersException;
|
||||
use PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException;
|
||||
use PragmaRX\Recovery\Recovery;
|
||||
|
||||
/**
|
||||
* Class ProfileController.
|
||||
@@ -83,65 +78,6 @@ class ProfileController extends Controller
|
||||
$this->middleware(IsDemoUser::class)->except(['index']);
|
||||
}
|
||||
|
||||
/**
|
||||
* View that generates a 2FA code for the user.
|
||||
*
|
||||
* @throws IncompatibleWithGoogleAuthenticatorException
|
||||
* @throws InvalidCharactersException
|
||||
* @throws SecretKeyTooShortException
|
||||
*/
|
||||
public function code(Request $request): Factory|RedirectResponse|View
|
||||
{
|
||||
if (!$this->internalAuth) {
|
||||
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
$domain = $this->getDomain();
|
||||
$secretPreference = app('preferences')->get('temp-mfa-secret');
|
||||
$codesPreference = app('preferences')->get('temp-mfa-codes');
|
||||
|
||||
// generate secret if not in session
|
||||
if (null === $secretPreference) {
|
||||
// generate secret + store + flash
|
||||
$secret = \Google2FA::generateSecretKey();
|
||||
app('preferences')->set('temp-mfa-secret', $secret);
|
||||
}
|
||||
|
||||
// re-use secret if in session
|
||||
if (null !== $secretPreference) {
|
||||
// get secret from session and flash
|
||||
$secret = $secretPreference->data;
|
||||
}
|
||||
if (is_array($secret)) {
|
||||
$secret = '';
|
||||
}
|
||||
|
||||
// generate recovery codes if not in session:
|
||||
$recoveryCodes = '';
|
||||
|
||||
if (null === $codesPreference) {
|
||||
// generate codes + store + flash:
|
||||
$recovery = app(Recovery::class);
|
||||
$recoveryCodes = $recovery->lowercase()->setCount(8)->setBlocks(2)->setChars(6)->toArray();
|
||||
app('preferences')->set('temp-mfa-codes', $recoveryCodes);
|
||||
}
|
||||
|
||||
// get codes from session if present already:
|
||||
if (null !== $codesPreference) {
|
||||
$recoveryCodes = $codesPreference->data;
|
||||
}
|
||||
if (!is_array($recoveryCodes)) {
|
||||
$recoveryCodes = [];
|
||||
}
|
||||
|
||||
$codes = implode("\r\n", $recoveryCodes);
|
||||
|
||||
$image = \Google2FA::getQRCodeInline($domain, auth()->user()->email, (string)$secret);
|
||||
|
||||
return view('profile.code', compact('image', 'secret', 'codes'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Screen to confirm email change.
|
||||
*
|
||||
@@ -193,61 +129,6 @@ class ProfileController extends Controller
|
||||
return view('profile.delete-account', compact('title', 'subTitle', 'subTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete 2FA routine.
|
||||
*/
|
||||
public function deleteCode(Request $request): Redirector|RedirectResponse
|
||||
{
|
||||
if (!$this->internalAuth) {
|
||||
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
/** @var UserRepositoryInterface $repository */
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
app('preferences')->delete('temp-mfa-secret');
|
||||
app('preferences')->delete('temp-mfa-codes');
|
||||
$repository->setMFACode($user, null);
|
||||
app('preferences')->mark();
|
||||
|
||||
session()->flash('success', (string)trans('firefly.pref_two_factor_auth_disabled'));
|
||||
session()->flash('info', (string)trans('firefly.pref_two_factor_auth_remove_it'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable 2FA screen.
|
||||
*/
|
||||
public function enable2FA(Request $request): Redirector|RedirectResponse
|
||||
{
|
||||
if (!$this->internalAuth) {
|
||||
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$enabledMFA = null !== $user->mfa_secret;
|
||||
|
||||
// if we don't have a valid secret yet, redirect to the code page to get one.
|
||||
if (!$enabledMFA) {
|
||||
return redirect(route('profile.code'));
|
||||
}
|
||||
|
||||
// If FF3 already has a secret, just set the two factor auth enabled to 1,
|
||||
// and let the user continue with the existing secret.
|
||||
session()->flash('info', (string)trans('firefly.2fa_already_enabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Index for profile.
|
||||
*
|
||||
@@ -298,33 +179,6 @@ class ProfileController extends Controller
|
||||
return view('profile.logout-other-sessions');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function newBackupCodes(Request $request): Factory|RedirectResponse|View
|
||||
{
|
||||
if (!$this->internalAuth) {
|
||||
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
// generate recovery codes:
|
||||
$recovery = app(Recovery::class);
|
||||
$recoveryCodes = $recovery->lowercase()
|
||||
->setCount(8) // Generate 8 codes
|
||||
->setBlocks(2) // Every code must have 7 blocks
|
||||
->setChars(6) // Each block must have 16 chars
|
||||
->toArray()
|
||||
;
|
||||
$codes = implode("\r\n", $recoveryCodes);
|
||||
|
||||
app('preferences')->set('mfa_recovery', $recoveryCodes);
|
||||
app('preferences')->mark();
|
||||
|
||||
return view('profile.new-backup-codes', compact('codes'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the change email form.
|
||||
*/
|
||||
@@ -442,99 +296,6 @@ class ProfileController extends Controller
|
||||
return view('profile.change-password', compact('title', 'subTitle', 'subTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit 2FA for the first time.
|
||||
*
|
||||
* @return Redirector|RedirectResponse
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function postCode(TokenFormRequest $request)
|
||||
{
|
||||
if (!$this->internalAuth) {
|
||||
$request->session()->flash('error', trans('firefly.external_user_mgt_disabled'));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
/** @var UserRepositoryInterface $repository */
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
$secret = app('preferences')->get('temp-mfa-secret')?->data;
|
||||
if (is_array($secret)) {
|
||||
$secret = null;
|
||||
}
|
||||
$secret = (string)$secret;
|
||||
|
||||
$repository->setMFACode($user, $secret);
|
||||
|
||||
app('preferences')->delete('temp-mfa-secret');
|
||||
app('preferences')->delete('temp-mfa-codes');
|
||||
|
||||
session()->flash('success', (string)trans('firefly.saved_preferences'));
|
||||
app('preferences')->mark();
|
||||
|
||||
// also save the code so replay attack is prevented.
|
||||
$mfaCode = $request->get('code');
|
||||
$this->addToMFAHistory($mfaCode);
|
||||
|
||||
// save backup codes in preferences:
|
||||
app('preferences')->set('mfa_recovery', session()->get('temp-mfa-codes'));
|
||||
|
||||
// make sure MFA is logged out.
|
||||
if ('testing' !== config('app.env')) {
|
||||
\Google2FA::logout();
|
||||
}
|
||||
|
||||
// drop all info from session:
|
||||
session()->forget(['temp-mfa-secret', 'two-factor-secret', 'temp-mfa-codes', 'two-factor-codes']);
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO duplicate code.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function addToMFAHistory(string $mfaCode): void
|
||||
{
|
||||
/** @var array $mfaHistory */
|
||||
$mfaHistory = app('preferences')->get('mfa_history', [])->data;
|
||||
$entry = [
|
||||
'time' => time(),
|
||||
'code' => $mfaCode,
|
||||
];
|
||||
$mfaHistory[] = $entry;
|
||||
|
||||
app('preferences')->set('mfa_history', $mfaHistory);
|
||||
$this->filterMFAHistory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove old entries from the preferences array.
|
||||
*/
|
||||
private function filterMFAHistory(): void
|
||||
{
|
||||
/** @var array $mfaHistory */
|
||||
$mfaHistory = app('preferences')->get('mfa_history', [])->data;
|
||||
$newHistory = [];
|
||||
$now = time();
|
||||
foreach ($mfaHistory as $entry) {
|
||||
$time = $entry['time'];
|
||||
$code = $entry['code'];
|
||||
if ($now - $time <= 300) {
|
||||
$newHistory[] = [
|
||||
'time' => $time,
|
||||
'code' => $code,
|
||||
];
|
||||
}
|
||||
}
|
||||
app('preferences')->set('mfa_history', $newHistory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit delete account.
|
||||
*
|
||||
@@ -664,7 +425,7 @@ class ProfileController extends Controller
|
||||
$repository->changeEmail($user, $match);
|
||||
$repository->unblockUser($user);
|
||||
|
||||
// return to login.
|
||||
// return to login page.
|
||||
session()->flash('success', (string)trans('firefly.login_with_old_email'));
|
||||
|
||||
return redirect(route('login'));
|
||||
|
@@ -77,17 +77,18 @@ class ShowController extends Controller
|
||||
*/
|
||||
public function show(Recurrence $recurrence)
|
||||
{
|
||||
$repos = app(AttachmentRepositoryInterface::class);
|
||||
$repos = app(AttachmentRepositoryInterface::class);
|
||||
|
||||
/** @var RecurrenceTransformer $transformer */
|
||||
$transformer = app(RecurrenceTransformer::class);
|
||||
$transformer = app(RecurrenceTransformer::class);
|
||||
$transformer->setParameters(new ParameterBag());
|
||||
|
||||
$array = $transformer->transform($recurrence);
|
||||
$array = $transformer->transform($recurrence);
|
||||
|
||||
$groups = $this->recurring->getTransactions($recurrence);
|
||||
$today = today(config('app.timezone'));
|
||||
$array['repeat_until'] = null !== $array['repeat_until'] ? new Carbon($array['repeat_until']) : null;
|
||||
$groups = $this->recurring->getTransactions($recurrence);
|
||||
$today = today(config('app.timezone'));
|
||||
$array['repeat_until'] = null !== $array['repeat_until'] ? new Carbon($array['repeat_until']) : null;
|
||||
$array['journal_count'] = $this->recurring->getJournalCount($recurrence);
|
||||
|
||||
// transform dates back to Carbon objects and expand information
|
||||
foreach ($array['repetitions'] as $index => $repetition) {
|
||||
@@ -103,9 +104,9 @@ class ShowController extends Controller
|
||||
}
|
||||
|
||||
// add attachments to the recurrence object.
|
||||
$attachments = $recurrence->attachments()->get();
|
||||
$array['attachments'] = [];
|
||||
$attachmentTransformer = app(AttachmentTransformer::class);
|
||||
$attachments = $recurrence->attachments()->get();
|
||||
$array['attachments'] = [];
|
||||
$attachmentTransformer = app(AttachmentTransformer::class);
|
||||
|
||||
/** @var Attachment $attachment */
|
||||
foreach ($attachments as $attachment) {
|
||||
@@ -114,7 +115,16 @@ class ShowController extends Controller
|
||||
$array['attachments'][] = $item;
|
||||
}
|
||||
|
||||
$subTitle = (string)trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]);
|
||||
if (null !== $array['nr_of_repetitions']) {
|
||||
$left = $array['nr_of_repetitions'] - $array['journal_count'];
|
||||
$left = max(0, $left);
|
||||
// limit each repetition to X occurrences:
|
||||
foreach ($array['repetitions'] as $index => $repetition) {
|
||||
$array['repetitions'][$index]['occurrences'] = array_slice($repetition['occurrences'], 0, $left);
|
||||
}
|
||||
}
|
||||
|
||||
$subTitle = (string)trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]);
|
||||
|
||||
return view('recurring.show', compact('recurrence', 'subTitle', 'array', 'groups', 'today'));
|
||||
}
|
||||
|
@@ -40,24 +40,24 @@ class TriggerController extends Controller
|
||||
{
|
||||
public function trigger(Recurrence $recurrence, TriggerRecurrenceRequest $request): RedirectResponse
|
||||
{
|
||||
$all = $request->getAll();
|
||||
$date = $all['date'];
|
||||
$all = $request->getAll();
|
||||
$date = $all['date'];
|
||||
|
||||
// grab the date from the last time the recurrence fired:
|
||||
$backupDate = $recurrence->latest_date;
|
||||
$backupDate = $recurrence->latest_date;
|
||||
|
||||
// fire the recurring cron job on the given date, then post-date the created transaction.
|
||||
app('log')->info(sprintf('Trigger: will now fire recurring cron job task for date "%s".', $date->format('Y-m-d H:i:s')));
|
||||
|
||||
/** @var CreateRecurringTransactions $job */
|
||||
$job = app(CreateRecurringTransactions::class);
|
||||
$job = app(CreateRecurringTransactions::class);
|
||||
$job->setRecurrences(new Collection([$recurrence]));
|
||||
$job->setDate($date);
|
||||
$job->setForce(false);
|
||||
$job->handle();
|
||||
app('log')->debug('Done with recurrence.');
|
||||
|
||||
$groups = $job->getGroups();
|
||||
$groups = $job->getGroups();
|
||||
|
||||
/** @var TransactionGroup $group */
|
||||
foreach ($groups as $group) {
|
||||
@@ -68,7 +68,8 @@ class TriggerController extends Controller
|
||||
$journal->save();
|
||||
}
|
||||
}
|
||||
$recurrence->latest_date = $backupDate;
|
||||
$recurrence->latest_date = $backupDate;
|
||||
$recurrence->latest_date_tz = $backupDate?->format('e');
|
||||
$recurrence->save();
|
||||
app('preferences')->mark();
|
||||
|
||||
|
@@ -158,6 +158,11 @@ class Installer
|
||||
// version compare thing.
|
||||
$configVersion = (string)config('firefly.version');
|
||||
$dbVersion = (string)app('fireflyconfig')->getFresh('ff3_version', '1.0')->data;
|
||||
if (str_starts_with($configVersion, 'develop')) {
|
||||
Log::debug('Skipping version check for develop version.');
|
||||
|
||||
return false;
|
||||
}
|
||||
if (1 === version_compare($configVersion, $dbVersion)) {
|
||||
app('log')->warning(
|
||||
sprintf(
|
||||
|
56
app/Http/Requests/ExistingTokenFormRequest.php
Normal file
56
app/Http/Requests/ExistingTokenFormRequest.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* TokenFormRequest.php
|
||||
* Copyright (c) 2019 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Requests;
|
||||
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
/**
|
||||
* Class ExistingTokenFormRequest.
|
||||
*/
|
||||
class ExistingTokenFormRequest extends FormRequest
|
||||
{
|
||||
use ChecksLogin;
|
||||
|
||||
/**
|
||||
* Rules for this request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
// fixed
|
||||
return [
|
||||
'password' => 'required|currentPassword',
|
||||
'code' => 'required|existingMfaCode',
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
if ($validator->fails()) {
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
}
|
@@ -42,7 +42,8 @@ class TokenFormRequest extends FormRequest
|
||||
{
|
||||
// fixed
|
||||
return [
|
||||
'code' => 'required|2faCode',
|
||||
'password' => 'required|currentPassword',
|
||||
'code' => 'required|2faCode',
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -42,6 +42,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class CreateRecurringTransactions.
|
||||
@@ -88,7 +89,7 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
$this->recurrences = new Collection();
|
||||
$this->groups = new Collection();
|
||||
|
||||
app('log')->debug(sprintf('Created new CreateRecurringTransactions("%s")', $this->date->format('Y-m-d')));
|
||||
Log::debug(sprintf('Created new CreateRecurringTransactions("%s")', $this->date->format('Y-m-d')));
|
||||
}
|
||||
|
||||
public function getGroups(): Collection
|
||||
@@ -101,25 +102,25 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now at start of CreateRecurringTransactions() job for %s.', $this->date->format('D d M Y')));
|
||||
Log::debug(sprintf('Now at start of CreateRecurringTransactions() job for %s.', $this->date->format('D d M Y')));
|
||||
|
||||
// only use recurrences from database if there is no collection submitted.
|
||||
if (0 !== count($this->recurrences)) {
|
||||
app('log')->debug('Using predetermined set of recurrences.');
|
||||
Log::debug('Using predetermined set of recurrences.');
|
||||
}
|
||||
if (0 === count($this->recurrences)) {
|
||||
app('log')->debug('Grab all recurrences from the database.');
|
||||
Log::debug('Grab all recurrences from the database.');
|
||||
$this->recurrences = $this->repository->getAll();
|
||||
}
|
||||
|
||||
$result = [];
|
||||
$count = $this->recurrences->count();
|
||||
$this->submitted = $count;
|
||||
app('log')->debug(sprintf('Count of collection is %d', $count));
|
||||
Log::debug(sprintf('Count of collection is %d', $count));
|
||||
|
||||
// filter recurrences:
|
||||
$filtered = $this->filterRecurrences($this->recurrences);
|
||||
app('log')->debug(sprintf('Left after filtering is %d', $filtered->count()));
|
||||
Log::debug(sprintf('Left after filtering is %d', $filtered->count()));
|
||||
|
||||
/** @var Recurrence $recurrence */
|
||||
foreach ($filtered as $recurrence) {
|
||||
@@ -133,20 +134,20 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
// clear cache for user
|
||||
app('preferences')->setForUser($recurrence->user, 'lastActivity', microtime());
|
||||
|
||||
app('log')->debug(sprintf('Now at recurrence #%d of user #%d', $recurrence->id, $recurrence->user_id));
|
||||
Log::debug(sprintf('Now at recurrence #%d of user #%d', $recurrence->id, $recurrence->user_id));
|
||||
$createdReps = $this->handleRepetitions($recurrence);
|
||||
app('log')->debug(sprintf('Done with recurrence #%d', $recurrence->id));
|
||||
Log::debug(sprintf('Done with recurrence #%d', $recurrence->id));
|
||||
$result[$recurrence->user_id] = $result[$recurrence->user_id]->merge($createdReps);
|
||||
++$this->executed;
|
||||
}
|
||||
|
||||
app('log')->debug('Now running report thing.');
|
||||
Log::debug('Now running report thing.');
|
||||
// will now send email to users.
|
||||
foreach ($result as $userId => $journals) {
|
||||
event(new RequestedReportOnJournals($userId, $journals));
|
||||
}
|
||||
|
||||
app('log')->debug('Done with handle()');
|
||||
Log::debug('Done with handle()');
|
||||
|
||||
// clear cache:
|
||||
app('preferences')->mark();
|
||||
@@ -166,10 +167,10 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
*/
|
||||
private function validRecurrence(Recurrence $recurrence): bool
|
||||
{
|
||||
app('log')->debug(sprintf('Now filtering recurrence #%d, owned by user #%d', $recurrence->id, $recurrence->user_id));
|
||||
Log::debug(sprintf('Now filtering recurrence #%d, owned by user #%d', $recurrence->id, $recurrence->user_id));
|
||||
// is not active.
|
||||
if (!$this->active($recurrence)) {
|
||||
app('log')->info(sprintf('Recurrence #%d is not active. Skipped.', $recurrence->id));
|
||||
Log::info(sprintf('Recurrence #%d is not active. Skipped.', $recurrence->id));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -177,14 +178,15 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
// has repeated X times.
|
||||
$journalCount = $this->repository->getJournalCount($recurrence);
|
||||
if (0 !== $recurrence->repetitions && $journalCount >= $recurrence->repetitions && false === $this->force) {
|
||||
app('log')->info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $recurrence->repetitions));
|
||||
Log::info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $journalCount));
|
||||
|
||||
return false;
|
||||
}
|
||||
Log::debug(sprintf('Recurrence #%d has run %d times, max is %d times.', $recurrence->id, $journalCount, $recurrence->repetitions));
|
||||
|
||||
// is no longer running
|
||||
if ($this->repeatUntilHasPassed($recurrence)) {
|
||||
app('log')->info(
|
||||
Log::info(
|
||||
sprintf(
|
||||
'Recurrence #%d was set to run until %s, and today\'s date is %s. Skipped.',
|
||||
$recurrence->id,
|
||||
@@ -198,12 +200,12 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
|
||||
// first_date is in the future
|
||||
if ($this->hasNotStartedYet($recurrence)) {
|
||||
app('log')->info(
|
||||
Log::info(
|
||||
sprintf(
|
||||
'Recurrence #%d is set to run on %s, and today\'s date is %s. Skipped.',
|
||||
$recurrence->id,
|
||||
$recurrence->first_date->format('Y-m-d'),
|
||||
$this->date->format('Y-m-d')
|
||||
$recurrence->first_date->format('Y-m-d H:i:s'),
|
||||
$this->date->format('Y-m-d H:i:s')
|
||||
)
|
||||
);
|
||||
|
||||
@@ -212,11 +214,11 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
|
||||
// already fired today (with success):
|
||||
if (false === $this->force && $this->hasFiredToday($recurrence)) {
|
||||
app('log')->info(sprintf('Recurrence #%d has already fired today. Skipped.', $recurrence->id));
|
||||
Log::info(sprintf('Recurrence #%d has already fired today. Skipped.', $recurrence->id));
|
||||
|
||||
return false;
|
||||
}
|
||||
app('log')->debug('Will be included.');
|
||||
Log::debug('Will be included.');
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -244,7 +246,8 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
private function hasNotStartedYet(Recurrence $recurrence): bool
|
||||
{
|
||||
$startDate = $this->getStartDate($recurrence);
|
||||
app('log')->debug(sprintf('Start date is %s', $startDate->format('Y-m-d')));
|
||||
Log::debug(sprintf('Start date is %s', $startDate->toW3cString()));
|
||||
Log::debug(sprintf('Ask date is %s', $this->date->toW3cString()));
|
||||
|
||||
return $startDate->gt($this->date);
|
||||
}
|
||||
@@ -283,7 +286,7 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
|
||||
/** @var RecurrenceRepetition $repetition */
|
||||
foreach ($recurrence->recurrenceRepetitions as $repetition) {
|
||||
app('log')->debug(
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Now repeating %s with value "%s", skips every %d time(s)',
|
||||
$repetition->repetition_type,
|
||||
@@ -338,63 +341,62 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
if ($date->ne($this->date)) {
|
||||
return null;
|
||||
}
|
||||
app('log')->debug(sprintf('%s IS today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d')));
|
||||
Log::debug(sprintf('%s IS today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d')));
|
||||
|
||||
// count created journals on THIS day.
|
||||
$journalCount = $this->repository->getJournalCount($recurrence, $date, $date);
|
||||
$journalCount = $this->repository->getJournalCount($recurrence, $date, $date);
|
||||
if ($journalCount > 0 && false === $this->force) {
|
||||
app('log')->info(sprintf('Already created %d journal(s) for date %s', $journalCount, $date->format('Y-m-d')));
|
||||
Log::info(sprintf('Already created %d journal(s) for date %s', $journalCount, $date->format('Y-m-d')));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->repository->createdPreviously($recurrence, $date) && false === $this->force) {
|
||||
app('log')->info('There is a transaction already made for this date, so will not be created now');
|
||||
Log::info('There is a transaction already made for this date, so will not be created now');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($journalCount > 0 && true === $this->force) {
|
||||
app('log')->warning(sprintf('Already created %d groups for date %s but FORCED to continue.', $journalCount, $date->format('Y-m-d')));
|
||||
Log::warning(sprintf('Already created %d groups for date %s but FORCED to continue.', $journalCount, $date->format('Y-m-d')));
|
||||
}
|
||||
|
||||
// create transaction array and send to factory.
|
||||
$groupTitle = null;
|
||||
$count = $recurrence->recurrenceTransactions->count();
|
||||
$groupTitle = null;
|
||||
$count = $recurrence->recurrenceTransactions->count();
|
||||
// #8844, if there is one recurrence transaction, use the first title as the title.
|
||||
if (1 === $count) {
|
||||
/** @var RecurrenceTransaction $first */
|
||||
$first = $recurrence->recurrenceTransactions()->first();
|
||||
$groupTitle = $first->description;
|
||||
}
|
||||
// #9305, if there is one recurrence transaction, group title must be NULL.
|
||||
$groupTitle = null;
|
||||
|
||||
// #8844, if there are more, use the recurrence transaction itself.
|
||||
if ($count > 1) {
|
||||
$groupTitle = $recurrence->title;
|
||||
}
|
||||
|
||||
if (0 === $count) {
|
||||
app('log')->error('No transactions to be created in this recurrence. Cannot continue.');
|
||||
Log::error('No transactions to be created in this recurrence. Cannot continue.');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$array = [
|
||||
$array = [
|
||||
'user' => $recurrence->user_id,
|
||||
'group_title' => $groupTitle,
|
||||
'transactions' => $this->getTransactionData($recurrence, $repetition, $date),
|
||||
];
|
||||
|
||||
/** @var TransactionGroup $group */
|
||||
$group = $this->groupRepository->store($array);
|
||||
$group = $this->groupRepository->store($array);
|
||||
++$this->created;
|
||||
app('log')->info(sprintf('Created new transaction group #%d', $group->id));
|
||||
Log::info(sprintf('Created new transaction group #%d', $group->id));
|
||||
|
||||
// trigger event:
|
||||
event(new StoredTransactionGroup($group, $recurrence->apply_rules, true));
|
||||
$this->groups->push($group);
|
||||
|
||||
// update recurring thing:
|
||||
$recurrence->latest_date = $date;
|
||||
$recurrence->latest_date = $date;
|
||||
$recurrence->latest_date_tz = $date?->format('e');
|
||||
$recurrence->save();
|
||||
|
||||
return $group;
|
||||
@@ -459,6 +461,7 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
{
|
||||
$newDate = clone $date;
|
||||
$newDate->startOfDay();
|
||||
Log::debug(sprintf('Overruled date to "%s', $newDate->format('Y-m-d H:i:s')));
|
||||
$this->date = $newDate;
|
||||
}
|
||||
|
||||
|
@@ -5,7 +5,10 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\JsonApi\V2\Accounts;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Rules\Account\IsValidAccountType;
|
||||
use FireflyIII\Rules\IsAllowedGroupAction;
|
||||
use FireflyIII\Rules\IsDateOrTime;
|
||||
use FireflyIII\Rules\IsValidDateRange;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Laravel\Http\Requests\ResourceQuery;
|
||||
use LaravelJsonApi\Validation\Rule as JsonApiRule;
|
||||
@@ -18,6 +21,7 @@ class AccountCollectionQuery extends ResourceQuery
|
||||
public function rules(): array
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
$validFilters = config('api.valid_api_filters')[Account::class];
|
||||
|
||||
return [
|
||||
'fields' => [
|
||||
@@ -25,15 +29,28 @@ class AccountCollectionQuery extends ResourceQuery
|
||||
'array',
|
||||
JsonApiRule::fieldSets(),
|
||||
],
|
||||
'user_group_id' => [
|
||||
'userGroupId' => [
|
||||
'nullable',
|
||||
'integer',
|
||||
new IsAllowedGroupAction(Account::class, request()->method()),
|
||||
],
|
||||
'startPeriod' => [
|
||||
'nullable',
|
||||
'date',
|
||||
new IsDateOrTime(),
|
||||
new IsValidDateRange(),
|
||||
],
|
||||
'endPeriod' => [
|
||||
'nullable',
|
||||
'date',
|
||||
new IsDateOrTime(),
|
||||
new IsValidDateRange(),
|
||||
],
|
||||
'filter' => [
|
||||
'nullable',
|
||||
'array',
|
||||
JsonApiRule::filter(),
|
||||
JsonApiRule::filter($validFilters),
|
||||
new IsValidAccountType(),
|
||||
],
|
||||
'include' => [
|
||||
'nullable',
|
||||
|
@@ -27,6 +27,7 @@ use FireflyIII\Models\Account;
|
||||
use FireflyIII\Support\JsonApi\Concerns\UsergroupAware;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Contracts\Store\CreatesResources;
|
||||
use LaravelJsonApi\Contracts\Store\QueriesAll;
|
||||
use LaravelJsonApi\NonEloquent\AbstractRepository;
|
||||
use LaravelJsonApi\NonEloquent\Capabilities\CrudRelations;
|
||||
@@ -43,7 +44,7 @@ use LaravelJsonApi\NonEloquent\Concerns\HasRelationsCapability;
|
||||
* This is necessary because the user can't just query all accounts (it would return other user's data)
|
||||
* and because we also need to collect all kinds of metadata, like the currency and user info.
|
||||
*/
|
||||
class AccountRepository extends AbstractRepository implements QueriesAll
|
||||
class AccountRepository extends AbstractRepository implements QueriesAll, CreatesResources
|
||||
{
|
||||
use HasCrudCapability;
|
||||
use HasRelationsCapability;
|
||||
@@ -52,17 +53,22 @@ class AccountRepository extends AbstractRepository implements QueriesAll
|
||||
/**
|
||||
* SiteRepository constructor.
|
||||
*/
|
||||
public function __construct() {}
|
||||
public function __construct()
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
}
|
||||
|
||||
public function exists(string $resourceId): bool
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
$result = null !== Account::find((int) $resourceId);
|
||||
Log::debug(sprintf('%s: %s', __METHOD__, var_export($result, true)));
|
||||
|
||||
return null !== Account::find((int) $resourceId);
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function find(string $resourceId): ?object
|
||||
{
|
||||
exit(__METHOD__);
|
||||
Log::debug(__METHOD__);
|
||||
// throw new \RuntimeException('trace me');
|
||||
$account = Account::find((int) $resourceId);
|
||||
@@ -88,11 +94,19 @@ class AccountRepository extends AbstractRepository implements QueriesAll
|
||||
|
||||
protected function crud(): Capabilities\CrudAccount
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
|
||||
return Capabilities\CrudAccount::make();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO piggy banks
|
||||
* TODO transactions
|
||||
*/
|
||||
protected function relations(): CrudRelations
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
|
||||
return Capabilities\CrudAccountRelations::make();
|
||||
}
|
||||
}
|
||||
|
58
app/JsonApi/V2/Accounts/AccountRequest.php
Normal file
58
app/JsonApi/V2/Accounts/AccountRequest.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V2\Accounts;
|
||||
|
||||
use FireflyIII\Rules\Account\IsUniqueAccount;
|
||||
use FireflyIII\Rules\IsBoolean;
|
||||
use FireflyIII\Rules\IsValidPositiveAmount;
|
||||
use FireflyIII\Rules\UniqueAccountNumber;
|
||||
use FireflyIII\Rules\UniqueIban;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Laravel\Http\Requests\ResourceRequest;
|
||||
|
||||
class AccountRequest extends ResourceRequest
|
||||
{
|
||||
use ConvertsDataTypes;
|
||||
|
||||
/**
|
||||
* Get the validation rules for the resource.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
$accountRoles = implode(',', config('firefly.accountRoles'));
|
||||
$ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes')));
|
||||
$types = implode(',', array_keys(config('firefly.subTitlesByIdentifier')));
|
||||
$type = $this->convertString('type');
|
||||
// var_dump($types);exit;
|
||||
|
||||
return [
|
||||
'name' => ['required', 'max:1024', 'min:1'], // , new IsUniqueAccount()
|
||||
'account_type' => ['required', 'max:1024', 'min:1', sprintf('in:%s', $types)],
|
||||
// 'iban' => ['iban', 'nullable', new UniqueIban(null, $type)],
|
||||
// 'bic' => 'bic|nullable',
|
||||
// 'account_number' => ['min:1', 'max:255', 'nullable', new UniqueAccountNumber(null, $type)],
|
||||
// 'opening_balance' => 'numeric|required_with:opening_balance_date|nullable',
|
||||
// 'opening_balance_date' => 'date|required_with:opening_balance|nullable',
|
||||
// 'virtual_balance' => 'numeric|nullable',
|
||||
// 'order' => 'numeric|nullable',
|
||||
// 'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
// 'currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
|
||||
// 'active' => [new IsBoolean()],
|
||||
// 'include_net_worth' => [new IsBoolean()],
|
||||
// 'account_role' => sprintf('nullable|in:%s|required_if:type,asset', $accountRoles),
|
||||
// 'credit_card_type' => sprintf('nullable|in:%s|required_if:account_role,ccAsset', $ccPaymentTypes),
|
||||
// 'monthly_payment_date' => 'nullable|date|required_if:account_role,ccAsset|required_if:credit_card_type,monthlyFull',
|
||||
// 'liability_type' => 'nullable|required_if:type,liability|required_if:type,liabilities|in:loan,debt,mortgage',
|
||||
// 'liability_amount' => ['required_with:liability_start_date', new IsValidPositiveAmount()],
|
||||
// 'liability_start_date' => 'required_with:liability_amount|date',
|
||||
// 'liability_direction' => 'nullable|required_if:type,liability|required_if:type,liabilities|in:credit,debit',
|
||||
// 'interest' => 'min:0|max:100|numeric',
|
||||
// 'interest_period' => sprintf('nullable|in:%s', implode(',', config('firefly.interest_periods'))),
|
||||
// 'notes' => 'min:0|max:32768',
|
||||
];
|
||||
}
|
||||
}
|
@@ -6,7 +6,6 @@ namespace FireflyIII\JsonApi\V2\Accounts;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Core\Resources\JsonApiResource;
|
||||
|
||||
/**
|
||||
@@ -19,8 +18,6 @@ class AccountResource extends JsonApiResource
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
|
||||
return (string) $this->resource->id;
|
||||
}
|
||||
|
||||
@@ -31,7 +28,7 @@ class AccountResource extends JsonApiResource
|
||||
*/
|
||||
public function attributes($request): iterable
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
// Log::debug(__METHOD__);
|
||||
|
||||
return [
|
||||
'created_at' => $this->resource->created_at,
|
||||
@@ -39,24 +36,36 @@ class AccountResource extends JsonApiResource
|
||||
'name' => $this->resource->name,
|
||||
'active' => $this->resource->active,
|
||||
'order' => $this->resource->order,
|
||||
'type' => $this->resource->account_type_string,
|
||||
'iban' => $this->resource->iban,
|
||||
'account_type' => $this->resource->account_type_string,
|
||||
'account_role' => $this->resource->account_role,
|
||||
'account_number' => '' === $this->resource->account_number ? null : $this->resource->account_number,
|
||||
|
||||
// currency
|
||||
// currency (if the account has a currency setting, otherwise NULL).
|
||||
'currency_id' => $this->resource->currency_id,
|
||||
'currency_name' => $this->resource->currency_name,
|
||||
'currency_code' => $this->resource->currency_code,
|
||||
'currency_symbol' => $this->resource->currency_symbol,
|
||||
'currency_decimal_places' => $this->resource->currency_decimal_places,
|
||||
'is_multi_currency' => '1' === $this->resource->is_multi_currency,
|
||||
|
||||
// balances
|
||||
'balance' => $this->resource->balance,
|
||||
'native_balance' => $this->resource->native_balance,
|
||||
|
||||
// liability things
|
||||
'liability_direction' => $this->resource->liability_direction,
|
||||
'interest' => $this->resource->interest,
|
||||
'interest_period' => $this->resource->interest_period,
|
||||
'current_debt' => $this->resource->current_debt,
|
||||
'current_debt' => $this->resource->current_debt, // TODO may be removed in the future.
|
||||
|
||||
// other things
|
||||
'last_activity' => $this->resource->last_activity,
|
||||
|
||||
// object group
|
||||
'object_group_id' => $this->resource->object_group_id,
|
||||
'object_group_title' => $this->resource->object_group_title,
|
||||
'object_group_order' => $this->resource->object_group_order,
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -12,6 +12,7 @@ use LaravelJsonApi\Eloquent\Fields\Relations\HasOne;
|
||||
use LaravelJsonApi\NonEloquent\Fields\Attribute;
|
||||
use LaravelJsonApi\NonEloquent\Fields\ID;
|
||||
use LaravelJsonApi\NonEloquent\Filters\Filter;
|
||||
use LaravelJsonApi\NonEloquent\Pagination\EnumerablePagination;
|
||||
|
||||
class AccountSchema extends Schema
|
||||
{
|
||||
@@ -27,20 +28,19 @@ class AccountSchema extends Schema
|
||||
*/
|
||||
public function fields(): array
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
|
||||
return [
|
||||
ID::make(),
|
||||
Attribute::make('created_at'),
|
||||
Attribute::make('updated_at'),
|
||||
|
||||
// basic info and meta data
|
||||
Attribute::make('name'),
|
||||
Attribute::make('active'),
|
||||
Attribute::make('order'),
|
||||
Attribute::make('type'),
|
||||
Attribute::make('name')->sortable(),
|
||||
Attribute::make('active')->sortable(),
|
||||
Attribute::make('order')->sortable(),
|
||||
Attribute::make('iban')->sortable(),
|
||||
Attribute::make('account_type'),
|
||||
Attribute::make('account_role'),
|
||||
Attribute::make('account_number'),
|
||||
Attribute::make('account_number')->sortable(),
|
||||
|
||||
// currency
|
||||
Attribute::make('currency_id'),
|
||||
@@ -48,16 +48,30 @@ class AccountSchema extends Schema
|
||||
Attribute::make('currency_code'),
|
||||
Attribute::make('currency_symbol'),
|
||||
Attribute::make('currency_decimal_places'),
|
||||
Attribute::make('is_multi_currency'),
|
||||
|
||||
// balance
|
||||
Attribute::make('balance')->sortable(),
|
||||
Attribute::make('native_balance')->sortable(),
|
||||
|
||||
// liability things
|
||||
Attribute::make('liability_direction'),
|
||||
Attribute::make('interest'),
|
||||
Attribute::make('interest_period'),
|
||||
Attribute::make('current_debt'),
|
||||
// Attribute::make('current_debt')->sortable(),
|
||||
|
||||
// TODO credit card fields.
|
||||
|
||||
// dynamic data
|
||||
Attribute::make('last_activity'),
|
||||
Attribute::make('last_activity')->sortable(),
|
||||
Attribute::make('balance_difference')->sortable(), // only used for sort.
|
||||
|
||||
// group
|
||||
Attribute::make('object_group_id'),
|
||||
Attribute::make('object_group_title'),
|
||||
Attribute::make('object_group_order'),
|
||||
|
||||
// relations.
|
||||
HasOne::make('user')->readOnly(),
|
||||
];
|
||||
}
|
||||
@@ -68,10 +82,13 @@ class AccountSchema extends Schema
|
||||
public function filters(): array
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
$array = [];
|
||||
$config = config('api.valid_api_filters')[Account::class];
|
||||
foreach ($config as $entry) {
|
||||
$array[] = Filter::make($entry);
|
||||
}
|
||||
|
||||
return [
|
||||
Filter::make('id'),
|
||||
];
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function repository(): AccountRepository
|
||||
@@ -85,4 +102,11 @@ class AccountSchema extends Schema
|
||||
->withUserGroup($this->userGroup)
|
||||
;
|
||||
}
|
||||
|
||||
public function pagination(): EnumerablePagination
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
|
||||
return EnumerablePagination::make();
|
||||
}
|
||||
}
|
||||
|
@@ -23,66 +23,119 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V2\Accounts\Capabilities;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\Support\JsonApi\CollectsCustomParameters;
|
||||
use FireflyIII\Support\JsonApi\Concerns\UsergroupAware;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
use FireflyIII\Support\JsonApi\ExpandsQuery;
|
||||
use FireflyIII\Support\JsonApi\FiltersPagination;
|
||||
use FireflyIII\Support\JsonApi\SortsCollection;
|
||||
use FireflyIII\Support\JsonApi\SortsQueryResults;
|
||||
use FireflyIII\Support\JsonApi\ValidateSortParameters;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Contracts\Pagination\Page;
|
||||
use LaravelJsonApi\Contracts\Store\HasPagination;
|
||||
use LaravelJsonApi\NonEloquent\Capabilities\QueryAll;
|
||||
use LaravelJsonApi\NonEloquent\Concerns\PaginatesEnumerables;
|
||||
|
||||
class AccountQuery extends QueryAll implements HasPagination
|
||||
{
|
||||
use AccountFilter;
|
||||
use CollectsCustomParameters;
|
||||
use ExpandsQuery;
|
||||
use FiltersPagination;
|
||||
use PaginatesEnumerables;
|
||||
use SortsCollection;
|
||||
use SortsQueryResults;
|
||||
use UsergroupAware;
|
||||
use ValidateSortParameters;
|
||||
|
||||
// use PaginatesEnumerables;
|
||||
|
||||
#[\Override]
|
||||
/**
|
||||
* This method returns all accounts, given a bunch of filters and sort fields, together with pagination.
|
||||
*
|
||||
* It is only used on the index, and nowhere else.
|
||||
*/
|
||||
public function get(): iterable
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
// collect filters
|
||||
$filters = $this->queryParameters->filter();
|
||||
// collect sort options
|
||||
$sort = $this->queryParameters->sortFields();
|
||||
$sort = $this->queryParameters->sortFields();
|
||||
|
||||
// collect pagination based on the page
|
||||
$pagination = $this->filtersPagination($this->queryParameters->page());
|
||||
$pagination = $this->filtersPagination($this->queryParameters->page());
|
||||
|
||||
// check if we need all accounts, regardless of pagination
|
||||
// This is necessary when the user wants to sort on specific params.
|
||||
$needsAll = $this->needsFullDataset('account', $sort);
|
||||
$needsAll = $this->needsFullDataset(Account::class, $sort);
|
||||
|
||||
// params that were not recognised, may be my own custom stuff.
|
||||
$otherParams = $this->getOtherParams($this->queryParameters->unrecognisedParameters());
|
||||
|
||||
// start the query
|
||||
$query = $this->userGroup->accounts();
|
||||
|
||||
// add pagination to the query, limiting the results.
|
||||
if (!$needsAll) {
|
||||
$query = $this->addPagination($query, $pagination);
|
||||
}
|
||||
$query = $this->userGroup->accounts();
|
||||
|
||||
// add sort and filter parameters to the query.
|
||||
$query = $this->addSortParams($query, $sort);
|
||||
$query = $this->addFilterParams('account', $query, $filters);
|
||||
$query = $this->addSortParams(Account::class, $query, $sort);
|
||||
$query = $this->addFilterParams(Account::class, $query, $this->queryParameters->filter());
|
||||
|
||||
// collect the result.
|
||||
$collection = $query->get(['accounts.*']);
|
||||
|
||||
// enrich the collected data
|
||||
$enrichment = new AccountEnrichment();
|
||||
$collection = $enrichment->enrich($collection);
|
||||
|
||||
// TODO add filters after the query, if there are filters that cannot be applied to the database but only
|
||||
// to the enriched results.
|
||||
|
||||
$collection = $query->get(['accounts.*']);
|
||||
// sort the data after the query, and return it right away.
|
||||
return $this->sortCollection($collection, $sort);
|
||||
$collection = $this->sortCollection(Account::class, $collection, $sort);
|
||||
|
||||
// if the entire collection needs to be enriched and sorted, do so now:
|
||||
$totalCount = $collection->count();
|
||||
Log::debug(sprintf('Total is %d', $totalCount));
|
||||
if ($needsAll) {
|
||||
Log::debug('Needs the entire collection');
|
||||
// enrich the entire collection
|
||||
$enrichment = new AccountEnrichment();
|
||||
$enrichment->setStart($otherParams['start'] ?? null);
|
||||
$enrichment->setEnd($otherParams['end'] ?? null);
|
||||
$collection = $enrichment->enrich($collection);
|
||||
|
||||
// TODO sort the set based on post-query sort options:
|
||||
$collection = $this->postQuerySort(Account::class, $collection, $sort);
|
||||
|
||||
// take the current page from the enriched set.
|
||||
$currentPage = $collection->skip(($pagination['number'] - 1) * $pagination['size'])->take($pagination['size']);
|
||||
}
|
||||
if (!$needsAll) {
|
||||
Log::debug('Needs only partial collection');
|
||||
// take from the collection the filtered page + page number:
|
||||
$currentPage = $collection->skip(($pagination['number'] - 1) * $pagination['size'])->take($pagination['size']);
|
||||
|
||||
// enrich only the current page.
|
||||
$enrichment = new AccountEnrichment();
|
||||
$enrichment->setStart($otherParams['start'] ?? null);
|
||||
$enrichment->setEnd($otherParams['end'] ?? null);
|
||||
$currentPage = $enrichment->enrich($currentPage);
|
||||
}
|
||||
// get current page?
|
||||
Log::debug(sprintf('Skip %d, take %d', ($pagination['number'] - 1) * $pagination['size'], $pagination['size']));
|
||||
// $currentPage = $collection->skip(($pagination['number'] - 1) * $pagination['size'])->take($pagination['size']);
|
||||
Log::debug(sprintf('New collection size: %d', $currentPage->count()));
|
||||
|
||||
// TODO add filters after the query, if there are filters that cannot be applied to the database
|
||||
// TODO same for sort things.
|
||||
|
||||
return new LengthAwarePaginator($currentPage, $totalCount, $pagination['size'], $pagination['number']);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function paginate(array $page): Page
|
||||
{
|
||||
exit('here weare');
|
||||
// TODO: Implement paginate() method.
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getOrPaginate(?array $page): iterable
|
||||
{
|
||||
exit('here weare');
|
||||
// TODO: Implement getOrPaginate() method.
|
||||
}
|
||||
}
|
||||
|
@@ -24,19 +24,37 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\JsonApi\V2\Accounts\Capabilities;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Support\JsonApi\CollectsCustomParameters;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\NonEloquent\Capabilities\CrudResource;
|
||||
|
||||
class CrudAccount extends CrudResource
|
||||
{
|
||||
use CollectsCustomParameters;
|
||||
|
||||
/**
|
||||
* Read the supplied site.
|
||||
*/
|
||||
public function read(Account $account): ?Account
|
||||
{
|
||||
$otherParams = $this->getOtherParams($this->request->query->all());
|
||||
|
||||
Log::debug(__METHOD__);
|
||||
// enrich the collected data
|
||||
$enrichment = new AccountEnrichment();
|
||||
$enrichment = new AccountEnrichment();
|
||||
|
||||
// set start and date, if present.
|
||||
$enrichment->setStart($otherParams['start'] ?? null);
|
||||
$enrichment->setEnd($otherParams['end'] ?? null);
|
||||
|
||||
return $enrichment->enrichSingle($account);
|
||||
}
|
||||
|
||||
public function create(array $validatedData): Account
|
||||
{
|
||||
var_dump($validatedData);
|
||||
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
@@ -42,7 +42,7 @@ class Server extends BaseServer
|
||||
*/
|
||||
protected function allSchemas(): array
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
// Log::debug(__METHOD__);
|
||||
|
||||
return [
|
||||
AccountSchema::class,
|
||||
|
@@ -23,15 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
|
||||
use FireflyIII\User;
|
||||
use GeneaLabs\LaravelModelCaching\Traits\Cachable;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@@ -39,83 +36,10 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Class Account
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property null|Carbon $deleted_at
|
||||
* @property int $user_id
|
||||
* @property int $account_type_id
|
||||
* @property string $account_type_string
|
||||
* @property string $name
|
||||
* @property string $virtual_balance
|
||||
* @property null|string $iban
|
||||
* @property bool $active
|
||||
* @property bool $encrypted
|
||||
* @property int $order
|
||||
* @property AccountMeta[]|Collection $accountMeta
|
||||
* @property null|int $account_meta_count
|
||||
* @property AccountType $accountType
|
||||
* @property Attachment[]|Collection $attachments
|
||||
* @property null|int $attachments_count
|
||||
* @property string $account_number
|
||||
* @property string $edit_name
|
||||
* @property Collection|Location[] $locations
|
||||
* @property null|int $locations_count
|
||||
* @property Collection|Note[] $notes
|
||||
* @property null|int $notes_count
|
||||
* @property Collection|ObjectGroup[] $objectGroups
|
||||
* @property null|int $object_groups_count
|
||||
* @property Collection|PiggyBank[] $piggyBanks
|
||||
* @property null|int $piggy_banks_count
|
||||
* @property Collection|Transaction[] $transactions
|
||||
* @property null|int $transactions_count
|
||||
* @property User $user
|
||||
* @property string $last_activity
|
||||
*
|
||||
* @method static EloquentBuilder|Account accountTypeIn($types)
|
||||
* @method static EloquentBuilder|Account newModelQuery()
|
||||
* @method static EloquentBuilder|Account newQuery()
|
||||
* @method static Builder|Account onlyTrashed()
|
||||
* @method static EloquentBuilder|Account query()
|
||||
* @method static EloquentBuilder|Account whereAccountTypeId($value)
|
||||
* @method static EloquentBuilder|Account whereActive($value)
|
||||
* @method static EloquentBuilder|Account whereCreatedAt($value)
|
||||
* @method static EloquentBuilder|Account whereDeletedAt($value)
|
||||
* @method static EloquentBuilder|Account whereEncrypted($value)
|
||||
* @method static EloquentBuilder|Account whereIban($value)
|
||||
* @method static EloquentBuilder|Account whereId($value)
|
||||
* @method static EloquentBuilder|Account whereName($value)
|
||||
* @method static EloquentBuilder|Account whereOrder($value)
|
||||
* @method static EloquentBuilder|Account whereUpdatedAt($value)
|
||||
* @method static EloquentBuilder|Account whereUserId($value)
|
||||
* @method static EloquentBuilder|Account whereVirtualBalance($value)
|
||||
* @method static Builder|Account withTrashed()
|
||||
* @method static Builder|Account withoutTrashed()
|
||||
*
|
||||
* @property Carbon $lastActivityDate
|
||||
* @property string $startBalance
|
||||
* @property string $endBalance
|
||||
* @property string $difference
|
||||
* @property string $interest
|
||||
* @property string $interestPeriod
|
||||
* @property string $accountTypeString
|
||||
* @property Location $location
|
||||
* @property string $liability_direction
|
||||
* @property string $current_debt
|
||||
* @property int $user_group_id
|
||||
*
|
||||
* @method static EloquentBuilder|Account whereUserGroupId($value)
|
||||
*
|
||||
* @property null|UserGroup $userGroup
|
||||
* @property mixed $account_id
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperAccount
|
||||
*/
|
||||
class Account extends Model
|
||||
{
|
||||
@@ -127,12 +51,13 @@ class Account extends Model
|
||||
|
||||
protected $casts
|
||||
= [
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'user_id' => 'integer',
|
||||
'deleted_at' => 'datetime',
|
||||
'active' => 'boolean',
|
||||
'encrypted' => 'boolean',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'user_id' => 'integer',
|
||||
'deleted_at' => 'datetime',
|
||||
'active' => 'boolean',
|
||||
'encrypted' => 'boolean',
|
||||
'virtual_balance' => 'string',
|
||||
];
|
||||
|
||||
protected $fillable = ['user_id', 'user_group_id', 'account_type_id', 'name', 'active', 'virtual_balance', 'iban'];
|
||||
|
@@ -4,14 +4,26 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use FireflyIII\Casts\SeparateTimezoneCaster;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @mixin IdeHelperAccountBalance
|
||||
*/
|
||||
class AccountBalance extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
protected $fillable = ['account_id', 'title', 'transaction_currency_id', 'balance'];
|
||||
protected $fillable = ['account_id', 'title', 'transaction_currency_id', 'balance', 'date', 'date_tz'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'date' => SeparateTimezoneCaster::class,
|
||||
'balance' => 'string',
|
||||
];
|
||||
}
|
||||
|
||||
public function account(): BelongsTo
|
||||
{
|
||||
|
@@ -23,35 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* Class AccountMeta
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property int $account_id
|
||||
* @property string $name
|
||||
* @property mixed $data
|
||||
* @property Account $account
|
||||
*
|
||||
* @method static Builder|AccountMeta newModelQuery()
|
||||
* @method static Builder|AccountMeta newQuery()
|
||||
* @method static Builder|AccountMeta query()
|
||||
* @method static Builder|AccountMeta whereAccountId($value)
|
||||
* @method static Builder|AccountMeta whereCreatedAt($value)
|
||||
* @method static Builder|AccountMeta whereData($value)
|
||||
* @method static Builder|AccountMeta whereId($value)
|
||||
* @method static Builder|AccountMeta whereName($value)
|
||||
* @method static Builder|AccountMeta whereUpdatedAt($value)
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperAccountMeta
|
||||
*/
|
||||
class AccountMeta extends Model
|
||||
{
|
||||
|
@@ -23,51 +23,58 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* FireflyIII\Models\AccountType
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property string $type
|
||||
* @property Account[]|Collection $accounts
|
||||
* @property null|int $accounts_count
|
||||
*
|
||||
* @method static Builder|AccountType newModelQuery()
|
||||
* @method static Builder|AccountType newQuery()
|
||||
* @method static Builder|AccountType query()
|
||||
* @method static Builder|AccountType whereCreatedAt($value)
|
||||
* @method static Builder|AccountType whereId($value)
|
||||
* @method static Builder|AccountType whereType($value)
|
||||
* @method static Builder|AccountType whereUpdatedAt($value)
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperAccountType
|
||||
*/
|
||||
class AccountType extends Model
|
||||
{
|
||||
use ReturnsIntegerIdTrait;
|
||||
|
||||
/** @deprecated */
|
||||
public const string ASSET = 'Asset account';
|
||||
|
||||
/** @deprecated */
|
||||
public const string BENEFICIARY = 'Beneficiary account';
|
||||
|
||||
/** @deprecated */
|
||||
public const string CASH = 'Cash account';
|
||||
|
||||
/** @deprecated */
|
||||
public const string CREDITCARD = 'Credit card';
|
||||
|
||||
/** @deprecated */
|
||||
public const string DEBT = 'Debt';
|
||||
|
||||
/** @deprecated */
|
||||
public const string DEFAULT = 'Default account';
|
||||
|
||||
/** @deprecated */
|
||||
public const string EXPENSE = 'Expense account';
|
||||
|
||||
/** @deprecated */
|
||||
public const string IMPORT = 'Import account';
|
||||
|
||||
/** @deprecated */
|
||||
public const string INITIAL_BALANCE = 'Initial balance account';
|
||||
|
||||
/** @deprecated */
|
||||
public const string LIABILITY_CREDIT = 'Liability credit account';
|
||||
|
||||
/** @deprecated */
|
||||
public const string LOAN = 'Loan';
|
||||
|
||||
/** @deprecated */
|
||||
public const string MORTGAGE = 'Mortgage';
|
||||
|
||||
/** @deprecated */
|
||||
public const string RECONCILIATION = 'Reconciliation account';
|
||||
|
||||
/** @deprecated */
|
||||
public const string REVENUE = 'Revenue account';
|
||||
|
||||
protected $casts
|
||||
@@ -82,4 +89,11 @@ class AccountType extends Model
|
||||
{
|
||||
return $this->hasMany(Account::class);
|
||||
}
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
// 'type' => AccountTypeEnum::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -23,71 +23,19 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* FireflyIII\Models\Attachment
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property null|Carbon $deleted_at
|
||||
* @property int $user_id
|
||||
* @property int $attachable_id
|
||||
* @property string $attachable_type
|
||||
* @property bool $file_exists
|
||||
* @property string $md5
|
||||
* @property string $filename
|
||||
* @property null|string $title
|
||||
* @property null|string $description
|
||||
* @property string $mime
|
||||
* @property int|string $size
|
||||
* @property bool $uploaded
|
||||
* @property string $notes_text
|
||||
* @property \Eloquent|Model $attachable
|
||||
* @property Collection|Note[] $notes
|
||||
* @property null|int $notes_count
|
||||
* @property User $user
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment newQuery()
|
||||
* @method static Builder|Attachment onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment whereAttachableId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment whereAttachableType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment whereDescription($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment whereFilename($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment whereMd5($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment whereMime($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment whereSize($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment whereTitle($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment whereUploaded($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment whereUserId($value)
|
||||
* @method static Builder|Attachment withTrashed()
|
||||
* @method static Builder|Attachment withoutTrashed()
|
||||
*
|
||||
* @property int $user_group_id
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Attachment whereUserGroupId($value)
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperAttachment
|
||||
*/
|
||||
class Attachment extends Model
|
||||
{
|
||||
|
@@ -24,53 +24,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* Class AuditLogEntry
|
||||
*
|
||||
* @property \Eloquent|Model $auditable
|
||||
* @property \Eloquent|Model $changer
|
||||
*
|
||||
* @method static Builder|AuditLogEntry newModelQuery()
|
||||
* @method static Builder|AuditLogEntry newQuery()
|
||||
* @method static \Illuminate\Database\Query\Builder|AuditLogEntry onlyTrashed()
|
||||
* @method static Builder|AuditLogEntry query()
|
||||
* @method static \Illuminate\Database\Query\Builder|AuditLogEntry withTrashed()
|
||||
* @method static \Illuminate\Database\Query\Builder|AuditLogEntry withoutTrashed()
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property null|Carbon $deleted_at
|
||||
* @property int $auditable_id
|
||||
* @property string $auditable_type
|
||||
* @property int $changer_id
|
||||
* @property string $changer_type
|
||||
* @property string $action
|
||||
* @property null|array $before
|
||||
* @property null|array $after
|
||||
*
|
||||
* @method static Builder|AuditLogEntry whereAction($value)
|
||||
* @method static Builder|AuditLogEntry whereAfter($value)
|
||||
* @method static Builder|AuditLogEntry whereAuditableId($value)
|
||||
* @method static Builder|AuditLogEntry whereAuditableType($value)
|
||||
* @method static Builder|AuditLogEntry whereBefore($value)
|
||||
* @method static Builder|AuditLogEntry whereChangerId($value)
|
||||
* @method static Builder|AuditLogEntry whereChangerType($value)
|
||||
* @method static Builder|AuditLogEntry whereCreatedAt($value)
|
||||
* @method static Builder|AuditLogEntry whereDeletedAt($value)
|
||||
* @method static Builder|AuditLogEntry whereId($value)
|
||||
* @method static Builder|AuditLogEntry whereUpdatedAt($value)
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperAuditLogEntry
|
||||
*/
|
||||
class AuditLogEntry extends Model
|
||||
{
|
||||
|
@@ -24,58 +24,35 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Enums\AutoBudgetType;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
|
||||
/**
|
||||
* FireflyIII\Models\AutoBudget
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property null|Carbon $deleted_at
|
||||
* @property int $budget_id
|
||||
* @property int $transaction_currency_id
|
||||
* @property int|string $auto_budget_type
|
||||
* @property string $amount
|
||||
* @property string $period
|
||||
* @property Budget $budget
|
||||
* @property TransactionCurrency $transactionCurrency
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AutoBudget newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AutoBudget newQuery()
|
||||
* @method static Builder|AutoBudget onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AutoBudget query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AutoBudget whereAmount($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AutoBudget whereAutoBudgetType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AutoBudget whereBudgetId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AutoBudget whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AutoBudget whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AutoBudget whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AutoBudget wherePeriod($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AutoBudget whereTransactionCurrencyId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AutoBudget whereUpdatedAt($value)
|
||||
* @method static Builder|AutoBudget withTrashed()
|
||||
* @method static Builder|AutoBudget withoutTrashed()
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperAutoBudget
|
||||
*/
|
||||
class AutoBudget extends Model
|
||||
{
|
||||
use ReturnsIntegerIdTrait;
|
||||
use SoftDeletes;
|
||||
|
||||
/** @deprecated */
|
||||
public const int AUTO_BUDGET_ADJUSTED = 3;
|
||||
|
||||
/** @deprecated */
|
||||
public const int AUTO_BUDGET_RESET = 1;
|
||||
|
||||
/** @deprecated */
|
||||
public const int AUTO_BUDGET_ROLLOVER = 2;
|
||||
protected $fillable = ['budget_id', 'amount', 'period'];
|
||||
|
||||
protected $casts = [
|
||||
'amount' => 'string',
|
||||
];
|
||||
|
||||
public function budget(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Budget::class);
|
||||
@@ -86,6 +63,13 @@ class AutoBudget extends Model
|
||||
return $this->belongsTo(TransactionCurrency::class);
|
||||
}
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
// 'auto_budget_type' => AutoBudgetType::class,
|
||||
];
|
||||
}
|
||||
|
||||
protected function amount(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
@@ -23,8 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
|
||||
use FireflyIII\User;
|
||||
@@ -32,45 +30,10 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* FireflyIII\Models\AvailableBudget
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property null|Carbon $deleted_at
|
||||
* @property int $user_id
|
||||
* @property int $transaction_currency_id
|
||||
* @property string $amount
|
||||
* @property Carbon $start_date
|
||||
* @property Carbon $end_date
|
||||
* @property TransactionCurrency $transactionCurrency
|
||||
* @property User $user
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AvailableBudget newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AvailableBudget newQuery()
|
||||
* @method static Builder|AvailableBudget onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AvailableBudget query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AvailableBudget whereAmount($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AvailableBudget whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AvailableBudget whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AvailableBudget whereEndDate($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AvailableBudget whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AvailableBudget whereStartDate($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AvailableBudget whereTransactionCurrencyId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AvailableBudget whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AvailableBudget whereUserId($value)
|
||||
* @method static Builder|AvailableBudget withTrashed()
|
||||
* @method static Builder|AvailableBudget withoutTrashed()
|
||||
*
|
||||
* @property int $user_group_id
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AvailableBudget whereUserGroupId($value)
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperAvailableBudget
|
||||
*/
|
||||
class AvailableBudget extends Model
|
||||
{
|
||||
@@ -86,9 +49,10 @@ class AvailableBudget extends Model
|
||||
'start_date' => 'date',
|
||||
'end_date' => 'date',
|
||||
'transaction_currency_id' => 'int',
|
||||
'amount' => 'string',
|
||||
];
|
||||
|
||||
protected $fillable = ['user_id', 'user_group_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date'];
|
||||
protected $fillable = ['user_id', 'user_group_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz'];
|
||||
|
||||
/**
|
||||
* Route binder. Converts the key in the URL to the specified object (or throw 404).
|
||||
|
@@ -23,88 +23,21 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Casts\SeparateTimezoneCaster;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* FireflyIII\Models\Bill
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property null|Carbon $deleted_at
|
||||
* @property int $user_id
|
||||
* @property int $transaction_currency_id
|
||||
* @property string $name
|
||||
* @property string $match
|
||||
* @property string $amount_min
|
||||
* @property string $amount_max
|
||||
* @property Carbon $date
|
||||
* @property null|Carbon $end_date
|
||||
* @property null|Carbon $extension_date
|
||||
* @property string $repeat_freq
|
||||
* @property int $skip
|
||||
* @property bool $automatch
|
||||
* @property bool $active
|
||||
* @property bool $name_encrypted
|
||||
* @property bool $match_encrypted
|
||||
* @property int $order
|
||||
* @property Attachment[]|Collection $attachments
|
||||
* @property null|int $attachments_count
|
||||
* @property Collection|Note[] $notes
|
||||
* @property null|int $notes_count
|
||||
* @property Collection|ObjectGroup[] $objectGroups
|
||||
* @property null|int $object_groups_count
|
||||
* @property null|TransactionCurrency $transactionCurrency
|
||||
* @property Collection|TransactionJournal[] $transactionJournals
|
||||
* @property null|int $transaction_journals_count
|
||||
* @property User $user
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill newQuery()
|
||||
* @method static Builder|Bill onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereActive($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereAmountMax($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereAmountMin($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereAutomatch($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereDate($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereEndDate($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereExtensionDate($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereMatch($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereMatchEncrypted($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereNameEncrypted($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereOrder($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereRepeatFreq($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereSkip($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereTransactionCurrencyId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereUserId($value)
|
||||
* @method static Builder|Bill withTrashed()
|
||||
* @method static Builder|Bill withoutTrashed()
|
||||
*
|
||||
* @property int $user_group_id
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Bill whereUserGroupId($value)
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperBill
|
||||
*/
|
||||
class Bill extends Model
|
||||
{
|
||||
@@ -117,14 +50,16 @@ class Bill extends Model
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
'date' => 'date',
|
||||
'end_date' => 'date',
|
||||
'extension_date' => 'date',
|
||||
'date' => SeparateTimezoneCaster::class,
|
||||
'end_date' => SeparateTimezoneCaster::class,
|
||||
'extension_date' => SeparateTimezoneCaster::class,
|
||||
'skip' => 'int',
|
||||
'automatch' => 'boolean',
|
||||
'active' => 'boolean',
|
||||
'name_encrypted' => 'boolean',
|
||||
'match_encrypted' => 'boolean',
|
||||
'amount_min' => 'string',
|
||||
'amount_max' => 'string',
|
||||
];
|
||||
|
||||
protected $fillable
|
||||
@@ -136,6 +71,7 @@ class Bill extends Model
|
||||
'user_group_id',
|
||||
'amount_max',
|
||||
'date',
|
||||
'date_tz',
|
||||
'repeat_freq',
|
||||
'skip',
|
||||
'automatch',
|
||||
@@ -143,6 +79,8 @@ class Bill extends Model
|
||||
'transaction_currency_id',
|
||||
'end_date',
|
||||
'extension_date',
|
||||
'end_date_tz',
|
||||
'extension_date_tz',
|
||||
];
|
||||
|
||||
protected $hidden = ['amount_min_encrypted', 'amount_max_encrypted', 'name_encrypted', 'match_encrypted'];
|
||||
|
@@ -23,71 +23,20 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* FireflyIII\Models\Budget
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property null|Carbon $deleted_at
|
||||
* @property int $user_id
|
||||
* @property string $name
|
||||
* @property bool $active
|
||||
* @property bool $encrypted
|
||||
* @property int $order
|
||||
* @property Attachment[]|Collection $attachments
|
||||
* @property null|int $attachments_count
|
||||
* @property AutoBudget[]|Collection $autoBudgets
|
||||
* @property null|int $auto_budgets_count
|
||||
* @property BudgetLimit[]|Collection $budgetlimits
|
||||
* @property null|int $budgetlimits_count
|
||||
* @property Collection|TransactionJournal[] $transactionJournals
|
||||
* @property null|int $transaction_journals_count
|
||||
* @property Collection|Transaction[] $transactions
|
||||
* @property null|int $transactions_count
|
||||
* @property User $user
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Budget newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Budget newQuery()
|
||||
* @method static Builder|Budget onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Budget query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Budget whereActive($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Budget whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Budget whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Budget whereEncrypted($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Budget whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Budget whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Budget whereOrder($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Budget whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Budget whereUserId($value)
|
||||
* @method static Builder|Budget withTrashed()
|
||||
* @method static Builder|Budget withoutTrashed()
|
||||
*
|
||||
* @property string $email
|
||||
* @property int $user_group_id
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Budget whereUserGroupId($value)
|
||||
*
|
||||
* @property Collection|Note[] $notes
|
||||
* @property null|int $notes_count
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperBudget
|
||||
*/
|
||||
class Budget extends Model
|
||||
{
|
||||
|
@@ -23,50 +23,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Casts\SeparateTimezoneCaster;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Created;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Deleted;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Updated;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* FireflyIII\Models\BudgetLimit
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property int $budget_id
|
||||
* @property int $transaction_currency_id
|
||||
* @property Carbon $start_date
|
||||
* @property null|Carbon $end_date
|
||||
* @property string $amount
|
||||
* @property string $spent
|
||||
* @property null|string $period
|
||||
* @property int|string $generated
|
||||
* @property Budget $budget
|
||||
* @property null|TransactionCurrency $transactionCurrency
|
||||
*
|
||||
* @method static Builder|BudgetLimit newModelQuery()
|
||||
* @method static Builder|BudgetLimit newQuery()
|
||||
* @method static Builder|BudgetLimit query()
|
||||
* @method static Builder|BudgetLimit whereAmount($value)
|
||||
* @method static Builder|BudgetLimit whereBudgetId($value)
|
||||
* @method static Builder|BudgetLimit whereCreatedAt($value)
|
||||
* @method static Builder|BudgetLimit whereEndDate($value)
|
||||
* @method static Builder|BudgetLimit whereGenerated($value)
|
||||
* @method static Builder|BudgetLimit whereId($value)
|
||||
* @method static Builder|BudgetLimit wherePeriod($value)
|
||||
* @method static Builder|BudgetLimit whereStartDate($value)
|
||||
* @method static Builder|BudgetLimit whereTransactionCurrencyId($value)
|
||||
* @method static Builder|BudgetLimit whereUpdatedAt($value)
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperBudgetLimit
|
||||
*/
|
||||
class BudgetLimit extends Model
|
||||
{
|
||||
@@ -76,9 +44,10 @@ class BudgetLimit extends Model
|
||||
= [
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'start_date' => 'date',
|
||||
'end_date' => 'date',
|
||||
'start_date' => SeparateTimezoneCaster::class,
|
||||
'end_date' => SeparateTimezoneCaster::class,
|
||||
'auto_budget' => 'boolean',
|
||||
'amount' => 'string',
|
||||
];
|
||||
protected $dispatchesEvents
|
||||
= [
|
||||
@@ -87,7 +56,7 @@ class BudgetLimit extends Model
|
||||
'deleted' => Deleted::class,
|
||||
];
|
||||
|
||||
protected $fillable = ['budget_id', 'start_date', 'end_date', 'amount', 'transaction_currency_id'];
|
||||
protected $fillable = ['budget_id', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz', 'amount', 'transaction_currency_id'];
|
||||
|
||||
/**
|
||||
* Route binder. Converts the key in the URL to the specified object (or throw 404).
|
||||
|
@@ -23,60 +23,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* FireflyIII\Models\Category
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property null|Carbon $deleted_at
|
||||
* @property int $user_id
|
||||
* @property string $name
|
||||
* @property Carbon $lastActivity
|
||||
* @property bool $encrypted
|
||||
* @property Attachment[]|Collection $attachments
|
||||
* @property null|int $attachments_count
|
||||
* @property Collection|Note[] $notes
|
||||
* @property null|int $notes_count
|
||||
* @property Collection|TransactionJournal[] $transactionJournals
|
||||
* @property null|int $transaction_journals_count
|
||||
* @property Collection|Transaction[] $transactions
|
||||
* @property null|int $transactions_count
|
||||
* @property User $user
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Category newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Category newQuery()
|
||||
* @method static Builder|Category onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Category query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Category whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Category whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Category whereEncrypted($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Category whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Category whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Category whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Category whereUserId($value)
|
||||
* @method static Builder|Category withTrashed()
|
||||
* @method static Builder|Category withoutTrashed()
|
||||
*
|
||||
* @property int $user_group_id
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Category whereUserGroupId($value)
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperCategory
|
||||
*/
|
||||
class Category extends Model
|
||||
{
|
||||
|
@@ -23,37 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
|
||||
/**
|
||||
* FireflyIII\Models\Configuration
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property null|Carbon $deleted_at
|
||||
* @property string $name
|
||||
* @property mixed $data
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Configuration newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Configuration newQuery()
|
||||
* @method static Builder|Configuration onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Configuration query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Configuration whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Configuration whereData($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Configuration whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Configuration whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Configuration whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Configuration whereUpdatedAt($value)
|
||||
* @method static Builder|Configuration withTrashed()
|
||||
* @method static Builder|Configuration withoutTrashed()
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperConfiguration
|
||||
*/
|
||||
class Configuration extends Model
|
||||
{
|
||||
|
@@ -23,56 +23,17 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Casts\SeparateTimezoneCaster;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* Class CurrencyExchangeRate
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property null|string $deleted_at
|
||||
* @property int $user_id
|
||||
* @property int $from_currency_id
|
||||
* @property int $to_currency_id
|
||||
* @property Carbon $date
|
||||
* @property string $rate
|
||||
* @property string $user_rate
|
||||
* @property TransactionCurrency $fromCurrency
|
||||
* @property TransactionCurrency $toCurrency
|
||||
* @property User $user
|
||||
*
|
||||
* @method static Builder|CurrencyExchangeRate newModelQuery()
|
||||
* @method static Builder|CurrencyExchangeRate newQuery()
|
||||
* @method static Builder|CurrencyExchangeRate query()
|
||||
* @method static Builder|CurrencyExchangeRate whereCreatedAt($value)
|
||||
* @method static Builder|CurrencyExchangeRate whereDate($value)
|
||||
* @method static Builder|CurrencyExchangeRate whereDeletedAt($value)
|
||||
* @method static Builder|CurrencyExchangeRate whereFromCurrencyId($value)
|
||||
* @method static Builder|CurrencyExchangeRate whereId($value)
|
||||
* @method static Builder|CurrencyExchangeRate whereRate($value)
|
||||
* @method static Builder|CurrencyExchangeRate whereToCurrencyId($value)
|
||||
* @method static Builder|CurrencyExchangeRate whereUpdatedAt($value)
|
||||
* @method static Builder|CurrencyExchangeRate whereUserId($value)
|
||||
* @method static Builder|CurrencyExchangeRate whereUserRate($value)
|
||||
*
|
||||
* @property int $user_group_id
|
||||
*
|
||||
* @method static Builder|CurrencyExchangeRate whereUserGroupId($value)
|
||||
* @method static Builder|CurrencyExchangeRate onlyTrashed()
|
||||
* @method static Builder|CurrencyExchangeRate withTrashed()
|
||||
* @method static Builder|CurrencyExchangeRate withoutTrashed()
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperCurrencyExchangeRate
|
||||
*/
|
||||
class CurrencyExchangeRate extends Model
|
||||
{
|
||||
@@ -87,9 +48,11 @@ class CurrencyExchangeRate extends Model
|
||||
'user_id' => 'int',
|
||||
'from_currency_id' => 'int',
|
||||
'to_currency_id' => 'int',
|
||||
'date' => 'datetime',
|
||||
'date' => SeparateTimezoneCaster::class,
|
||||
'rate' => 'string',
|
||||
'user_rate' => 'string',
|
||||
];
|
||||
protected $fillable = ['user_id', 'from_currency_id', 'to_currency_id', 'date', 'rate'];
|
||||
protected $fillable = ['user_id', 'from_currency_id', 'to_currency_id', 'date', 'date_tz', 'rate'];
|
||||
|
||||
public function fromCurrency(): BelongsTo
|
||||
{
|
||||
|
@@ -24,42 +24,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* Class GroupMembership
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property null|string $deleted_at
|
||||
* @property int $user_id
|
||||
* @property int $user_group_id
|
||||
* @property int $user_role_id
|
||||
* @property User $user
|
||||
* @property UserGroup $userGroup
|
||||
* @property UserRole $userRole
|
||||
*
|
||||
* @method static Builder|GroupMembership newModelQuery()
|
||||
* @method static Builder|GroupMembership newQuery()
|
||||
* @method static Builder|GroupMembership query()
|
||||
* @method static Builder|GroupMembership whereCreatedAt($value)
|
||||
* @method static Builder|GroupMembership whereDeletedAt($value)
|
||||
* @method static Builder|GroupMembership whereId($value)
|
||||
* @method static Builder|GroupMembership whereUpdatedAt($value)
|
||||
* @method static Builder|GroupMembership whereUserGroupId($value)
|
||||
* @method static Builder|GroupMembership whereUserId($value)
|
||||
* @method static Builder|GroupMembership whereUserRoleId($value)
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperGroupMembership
|
||||
*/
|
||||
class GroupMembership extends Model
|
||||
{
|
||||
|
@@ -24,46 +24,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Casts\SeparateTimezoneCaster;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Class InvitedUser
|
||||
*
|
||||
* @property User $user
|
||||
*
|
||||
* @method static Builder|InvitedUser newModelQuery()
|
||||
* @method static Builder|InvitedUser newQuery()
|
||||
* @method static Builder|InvitedUser query()
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property int $user_id
|
||||
* @property string $email
|
||||
* @property string $invite_code
|
||||
* @property Carbon $expires
|
||||
* @property bool $redeemed
|
||||
*
|
||||
* @method static Builder|InvitedUser whereCreatedAt($value)
|
||||
* @method static Builder|InvitedUser whereEmail($value)
|
||||
* @method static Builder|InvitedUser whereExpires($value)
|
||||
* @method static Builder|InvitedUser whereId($value)
|
||||
* @method static Builder|InvitedUser whereInviteCode($value)
|
||||
* @method static Builder|InvitedUser whereRedeemed($value)
|
||||
* @method static Builder|InvitedUser whereUpdatedAt($value)
|
||||
* @method static Builder|InvitedUser whereUserId($value)
|
||||
*
|
||||
* @property mixed $user_group_id
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperInvitedUser
|
||||
*/
|
||||
class InvitedUser extends Model
|
||||
{
|
||||
@@ -72,10 +42,10 @@ class InvitedUser extends Model
|
||||
|
||||
protected $casts
|
||||
= [
|
||||
'expires' => 'datetime',
|
||||
'expires' => SeparateTimezoneCaster::class,
|
||||
'redeemed' => 'boolean',
|
||||
];
|
||||
protected $fillable = ['user_id', 'email', 'invite_code', 'expires', 'redeemed'];
|
||||
protected $fillable = ['user_id', 'email', 'invite_code', 'expires', 'expires_tz', 'redeemed'];
|
||||
|
||||
/**
|
||||
* Route binder. Converts the key in the URL to the specified object (or throw 404).
|
||||
|
@@ -23,47 +23,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* FireflyIII\Models\LinkType
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property null|Carbon $deleted_at
|
||||
* @property string $name
|
||||
* @property string $outward
|
||||
* @property string $inward
|
||||
* @property int $journalCount
|
||||
* @property bool $editable
|
||||
* @property Collection|TransactionJournalLink[] $transactionJournalLinks
|
||||
* @property null|int $transaction_journal_links_count
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|LinkType newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|LinkType newQuery()
|
||||
* @method static Builder|LinkType onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|LinkType query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|LinkType whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|LinkType whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|LinkType whereEditable($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|LinkType whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|LinkType whereInward($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|LinkType whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|LinkType whereOutward($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|LinkType whereUpdatedAt($value)
|
||||
* @method static Builder|LinkType withTrashed()
|
||||
* @method static Builder|LinkType withoutTrashed()
|
||||
*
|
||||
* @mixin Eloquent
|
||||
* @mixin IdeHelperLinkType
|
||||
*/
|
||||
class LinkType extends Model
|
||||
{
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user