mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2026-01-15 02:14:12 +00:00
Compare commits
131 Commits
develop-20
...
develop-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2e562623c | ||
|
|
c8d5e8a9dc | ||
|
|
963f017be3 | ||
|
|
0e0eeb736f | ||
|
|
e8d9b8fa49 | ||
|
|
c166b9242e | ||
|
|
8ff8efced2 | ||
|
|
0b4fb9a806 | ||
|
|
ba9fef9410 | ||
|
|
f7d94d17cd | ||
|
|
1fea9c6817 | ||
|
|
a88c8bedbe | ||
|
|
fbf3468053 | ||
|
|
2a3ba9799e | ||
|
|
d121aad28f | ||
|
|
dc808fa807 | ||
|
|
a1be6ff62b | ||
|
|
20dc5b0256 | ||
|
|
edd54e23c5 | ||
|
|
1238df8784 | ||
|
|
b8c62652b0 | ||
|
|
54b2d02f63 | ||
|
|
47faf89a5c | ||
|
|
b7fb5a3854 | ||
|
|
a384b4202a | ||
|
|
99ecac0ce4 | ||
|
|
6102982456 | ||
|
|
b88e981b4b | ||
|
|
827263b03e | ||
|
|
d44e74d334 | ||
|
|
911f46c590 | ||
|
|
7d42c4ee5d | ||
|
|
ea89f6177f | ||
|
|
74291b3870 | ||
|
|
2c4f2082fe | ||
|
|
d8d58cc29b | ||
|
|
85b17e4035 | ||
|
|
83de5667b3 | ||
|
|
5fffe873c6 | ||
|
|
78c09c82d6 | ||
|
|
704abc315d | ||
|
|
86b4965458 | ||
|
|
d2dc0c2bf0 | ||
|
|
9f4894bbb5 | ||
|
|
76a8675a34 | ||
|
|
6988301da1 | ||
|
|
109cd37211 | ||
|
|
284ff4d1b0 | ||
|
|
bc0ab7af99 | ||
|
|
a17bc7258f | ||
|
|
87911c2438 | ||
|
|
746f1fd300 | ||
|
|
9e5faf919f | ||
|
|
cc6cbe6605 | ||
|
|
dc6d708897 | ||
|
|
6189d24b98 | ||
|
|
f6e28dc88f | ||
|
|
75ea035630 | ||
|
|
4cdb14301d | ||
|
|
9f95221ba3 | ||
|
|
e3a67be412 | ||
|
|
5749b642ce | ||
|
|
baff7c67f9 | ||
|
|
ccc005942f | ||
|
|
5b83c33039 | ||
|
|
cc32578c5f | ||
|
|
80f410835b | ||
|
|
b537a3145d | ||
|
|
bfa1fcbaf8 | ||
|
|
56243907c4 | ||
|
|
5928dd72e6 | ||
|
|
c6bf0ff1cd | ||
|
|
19d1cf192b | ||
|
|
37d7dc7e3e | ||
|
|
95a3a194b8 | ||
|
|
3542387188 | ||
|
|
da1b002a64 | ||
|
|
46daee28e7 | ||
|
|
9ade5635d4 | ||
|
|
e14e80f33c | ||
|
|
0c824e21c8 | ||
|
|
fab1c68569 | ||
|
|
c1534657f2 | ||
|
|
39841de680 | ||
|
|
43a720b62b | ||
|
|
5ec54de29e | ||
|
|
397e37f344 | ||
|
|
b6f84c2b99 | ||
|
|
843f86fc66 | ||
|
|
0e8e364074 | ||
|
|
bbccbef578 | ||
|
|
ee11a8e3a0 | ||
|
|
e8618047bd | ||
|
|
f104b76f73 | ||
|
|
cb701d8506 | ||
|
|
70a334c56e | ||
|
|
e6b2db1e29 | ||
|
|
e8dffa0052 | ||
|
|
c4f0512f39 | ||
|
|
3268019d0c | ||
|
|
a0ef6a1fc8 | ||
|
|
99d0098b20 | ||
|
|
a7a54c042c | ||
|
|
c44e48a793 | ||
|
|
53b501ca73 | ||
|
|
322f70bcca | ||
|
|
35559c077b | ||
|
|
590ffe7c76 | ||
|
|
8a2d8f148e | ||
|
|
4f0e15e07d | ||
|
|
7463861e0c | ||
|
|
1e70fa28be | ||
|
|
26c6ca470b | ||
|
|
5e54034e0e | ||
|
|
25873ef734 | ||
|
|
1092b04b22 | ||
|
|
01ce74dd72 | ||
|
|
41430d8386 | ||
|
|
01eb19169c | ||
|
|
cfaa7d7c68 | ||
|
|
14d3312a10 | ||
|
|
87be478dd8 | ||
|
|
0b6877a20e | ||
|
|
7186f0ef60 | ||
|
|
538933691e | ||
|
|
46c49ddbd8 | ||
|
|
bcfb134b6e | ||
|
|
57981f1cf9 | ||
|
|
0310186fb7 | ||
|
|
4dcb38290e | ||
|
|
2f5c37048b |
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"require": {
|
||||
"friendsofphp/php-cs-fixer": "^3.12"
|
||||
}
|
||||
"require": {
|
||||
"friendsofphp/php-cs-fixer": "^3.12"
|
||||
}
|
||||
}
|
||||
|
||||
77
.ci/php-cs-fixer/composer.lock
generated
77
.ci/php-cs-fixer/composer.lock
generated
@@ -160,16 +160,16 @@
|
||||
},
|
||||
{
|
||||
"name": "composer/xdebug-handler",
|
||||
"version": "3.0.3",
|
||||
"version": "3.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/xdebug-handler.git",
|
||||
"reference": "ced299686f41dce890debac69273b47ffe98a40c"
|
||||
"reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c",
|
||||
"reference": "ced299686f41dce890debac69273b47ffe98a40c",
|
||||
"url": "https://api.github.com/repos/composer/xdebug-handler/zipball/4f988f8fdf580d53bdb2d1278fe93d1ed5462255",
|
||||
"reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -180,7 +180,7 @@
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.1",
|
||||
"symfony/phpunit-bridge": "^6.0"
|
||||
"phpunit/phpunit": "^8.5 || ^9.6 || ^10.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -204,9 +204,9 @@
|
||||
"performance"
|
||||
],
|
||||
"support": {
|
||||
"irc": "irc://irc.freenode.org/composer",
|
||||
"irc": "ircs://irc.libera.chat:6697/composer",
|
||||
"issues": "https://github.com/composer/xdebug-handler/issues",
|
||||
"source": "https://github.com/composer/xdebug-handler/tree/3.0.3"
|
||||
"source": "https://github.com/composer/xdebug-handler/tree/3.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -222,20 +222,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-02-25T21:32:43+00:00"
|
||||
"time": "2024-03-26T18:29:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.52.1",
|
||||
"version": "v3.53.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "6e77207f0d851862ceeb6da63e6e22c01b1587bc"
|
||||
"reference": "69a19093a9ded8d1baac62ed6c009b8bc148d008"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/6e77207f0d851862ceeb6da63e6e22c01b1587bc",
|
||||
"reference": "6e77207f0d851862ceeb6da63e6e22c01b1587bc",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/69a19093a9ded8d1baac62ed6c009b8bc148d008",
|
||||
"reference": "69a19093a9ded8d1baac62ed6c009b8bc148d008",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -259,6 +259,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"facile-it/paraunit": "^1.3 || ^2.0",
|
||||
"infection/infection": "^0.27.11",
|
||||
"justinrainbow/json-schema": "^5.2",
|
||||
"keradus/cli-executor": "^2.1",
|
||||
"mikey179/vfsstream": "^1.6.11",
|
||||
@@ -306,7 +307,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.52.1"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.53.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -314,7 +315,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-03-19T21:02:43+00:00"
|
||||
"time": "2024-04-08T15:03:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
@@ -538,16 +539,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v7.0.4",
|
||||
"version": "v7.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "6b099f3306f7c9c2d2786ed736d0026b2903205f"
|
||||
"reference": "fde915cd8e7eb99b3d531d3d5c09531429c3f9e5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/6b099f3306f7c9c2d2786ed736d0026b2903205f",
|
||||
"reference": "6b099f3306f7c9c2d2786ed736d0026b2903205f",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/fde915cd8e7eb99b3d531d3d5c09531429c3f9e5",
|
||||
"reference": "fde915cd8e7eb99b3d531d3d5c09531429c3f9e5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -611,7 +612,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v7.0.4"
|
||||
"source": "https://github.com/symfony/console/tree/v7.0.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -627,7 +628,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-02-22T20:27:20+00:00"
|
||||
"time": "2024-04-01T11:04:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@@ -778,16 +779,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher-contracts",
|
||||
"version": "v3.4.0",
|
||||
"version": "v3.4.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
|
||||
"reference": "a76aed96a42d2b521153fb382d418e30d18b59df"
|
||||
"reference": "4e64b49bf370ade88e567de29465762e316e4224"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df",
|
||||
"reference": "a76aed96a42d2b521153fb382d418e30d18b59df",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/4e64b49bf370ade88e567de29465762e316e4224",
|
||||
"reference": "4e64b49bf370ade88e567de29465762e316e4224",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -834,7 +835,7 @@
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.0"
|
||||
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -850,20 +851,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-05-23T14:45:45+00:00"
|
||||
"time": "2024-01-23T14:51:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v7.0.3",
|
||||
"version": "v7.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
"reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12"
|
||||
"reference": "408105dff4c104454100730bdfd1a9cdd993f04d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/2890e3a825bc0c0558526c04499c13f83e1b6b12",
|
||||
"reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/408105dff4c104454100730bdfd1a9cdd993f04d",
|
||||
"reference": "408105dff4c104454100730bdfd1a9cdd993f04d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -897,7 +898,7 @@
|
||||
"description": "Provides basic utilities for the filesystem",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/filesystem/tree/v7.0.3"
|
||||
"source": "https://github.com/symfony/filesystem/tree/v7.0.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -913,7 +914,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-23T15:02:46+00:00"
|
||||
"time": "2024-03-21T19:37:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
@@ -1583,16 +1584,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
"version": "v3.4.1",
|
||||
"version": "v3.4.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/service-contracts.git",
|
||||
"reference": "fe07cbc8d837f60caf7018068e350cc5163681a0"
|
||||
"reference": "11bbf19a0fb7b36345861e85c5768844c552906e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0",
|
||||
"reference": "fe07cbc8d837f60caf7018068e350cc5163681a0",
|
||||
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/11bbf19a0fb7b36345861e85c5768844c552906e",
|
||||
"reference": "11bbf19a0fb7b36345861e85c5768844c552906e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1645,7 +1646,7 @@
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/service-contracts/tree/v3.4.1"
|
||||
"source": "https://github.com/symfony/service-contracts/tree/v3.4.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1661,7 +1662,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-12-26T14:02:43+00:00"
|
||||
"time": "2023-12-19T21:51:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/stopwatch",
|
||||
|
||||
28
.ci/phpmd/composer.lock
generated
28
.ci/phpmd/composer.lock
generated
@@ -9,16 +9,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "composer/pcre",
|
||||
"version": "3.1.2",
|
||||
"version": "3.1.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/pcre.git",
|
||||
"reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace"
|
||||
"reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/4775f35b2d70865807c89d32c8e7385b86eb0ace",
|
||||
"reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/5b16e25a5355f1f3afdfc2f954a0a80aec4826a8",
|
||||
"reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -60,7 +60,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/composer/pcre/issues",
|
||||
"source": "https://github.com/composer/pcre/tree/3.1.2"
|
||||
"source": "https://github.com/composer/pcre/tree/3.1.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -76,20 +76,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-03-07T15:38:35+00:00"
|
||||
"time": "2024-03-19T10:26:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/xdebug-handler",
|
||||
"version": "3.0.3",
|
||||
"version": "3.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/xdebug-handler.git",
|
||||
"reference": "ced299686f41dce890debac69273b47ffe98a40c"
|
||||
"reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c",
|
||||
"reference": "ced299686f41dce890debac69273b47ffe98a40c",
|
||||
"url": "https://api.github.com/repos/composer/xdebug-handler/zipball/4f988f8fdf580d53bdb2d1278fe93d1ed5462255",
|
||||
"reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -100,7 +100,7 @@
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.1",
|
||||
"symfony/phpunit-bridge": "^6.0"
|
||||
"phpunit/phpunit": "^8.5 || ^9.6 || ^10.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -124,9 +124,9 @@
|
||||
"performance"
|
||||
],
|
||||
"support": {
|
||||
"irc": "irc://irc.freenode.org/composer",
|
||||
"irc": "ircs://irc.libera.chat:6697/composer",
|
||||
"issues": "https://github.com/composer/xdebug-handler/issues",
|
||||
"source": "https://github.com/composer/xdebug-handler/tree/3.0.3"
|
||||
"source": "https://github.com/composer/xdebug-handler/tree/3.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -142,7 +142,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-02-25T21:32:43+00:00"
|
||||
"time": "2024-03-26T18:29:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pdepend/pdepend",
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<ruleset name="pcsg-generated-ruleset"
|
||||
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
name="pcsg-generated-ruleset"
|
||||
xmlns="http://pmd.sf.net/ruleset/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
|
||||
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
|
||||
<description>Firefly III ruleset.</description>
|
||||
|
||||
@@ -111,7 +111,10 @@ PGSQL_SSL_CERT=null
|
||||
PGSQL_SSL_KEY=null
|
||||
PGSQL_SSL_CRL_FILE=null
|
||||
|
||||
# more PostgreSQL settings
|
||||
# For postgresql 15 and up, setting this to public will no longer work as expected, becasuse the
|
||||
# 'public' schema is without grants. This can be worked around by having a super user grant those
|
||||
# necessary privileges, but in security conscious setups that's not viable.
|
||||
# You will need to set this to the schema you want to use.
|
||||
PGSQL_SCHEMA=public
|
||||
|
||||
# If you're looking for performance improvements, you could install memcached or redis
|
||||
|
||||
18
.github/pull_request_template.md
vendored
18
.github/pull_request_template.md
vendored
@@ -1,13 +1,19 @@
|
||||
<!--
|
||||
Before you create a new PR, please consider:
|
||||
Thank you for submitting new code to Firefly III, or any of the related projects. Please read the following rules carefully.
|
||||
|
||||
1) Pull requests for the MAIN branch will be closed.
|
||||
2) DO NOT include translations in your PR. Only English US sentences.
|
||||
- Do not submit solutions for problems that are not already reported in an issue
|
||||
- Firefly III can't be your learning experience. If you're new to all of this, please go be new somewhere else
|
||||
- Do not open PRs to "discuss" possible solutions or to "get feedback" on your code. I don't have time for that.
|
||||
- Pull requests for the MAIN branch will be closed.
|
||||
- DO NOT include translated strings in your PR.
|
||||
|
||||
Perhaps open an issue first, before you open a PR?
|
||||
|
||||
See also: https://docs.firefly-iii.org/explanation/support/#contributing-code
|
||||
|
||||
Thanks.
|
||||
-->
|
||||
|
||||
Fixes issue # (if relevant)
|
||||
|
||||
This PR fixes issue # (if relevant).
|
||||
|
||||
Changes in this pull request:
|
||||
|
||||
|
||||
58
.github/stale.yml
vendored
58
.github/stale.yml
vendored
@@ -1,58 +0,0 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 14
|
||||
|
||||
# Number of days of inactivity before a stale Issue or Pull Request is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
daysUntilClose: 14
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
# - "[Status] Maybe Later"
|
||||
exemptLabels:
|
||||
- enhancement
|
||||
- feature
|
||||
- bug
|
||||
- announcement
|
||||
- "layout-v3"
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: false
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
|
||||
# Comment to post when removing the stale label.
|
||||
# unmarkComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Comment to post when closing a stale Issue or Pull Request.
|
||||
# closeComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 30
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
|
||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
||||
# pulls:
|
||||
# daysUntilStale: 30
|
||||
# markComment: >
|
||||
# This pull request has been automatically marked as stale because it has not had
|
||||
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||
# for your contributions.
|
||||
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - confirmed
|
||||
4
.github/workflows/close-duplicates.yml
vendored
4
.github/workflows/close-duplicates.yml
vendored
@@ -3,7 +3,7 @@ name: "Issues - Command to close duplicate issues"
|
||||
# the workflow to execute on is comments that are newly created
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
types: [ created ]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
close_duplicates:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: github/command@v1.1.0
|
||||
- uses: github/command@v1.1.1
|
||||
id: command
|
||||
with:
|
||||
allowed_contexts: "issue"
|
||||
|
||||
4
.github/workflows/debug-info-actions.yml
vendored
4
.github/workflows/debug-info-actions.yml
vendored
@@ -3,9 +3,9 @@ name: 'Issues - Respond to hidden commands'
|
||||
# the workflow to execute on is comments that are newly created
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
types: [ opened, edited ]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
types: [ created ]
|
||||
|
||||
# permissions needed for reacting to IssueOps commands on issues and PRs
|
||||
permissions:
|
||||
|
||||
6
.github/workflows/label-actions.yml
vendored
6
.github/workflows/label-actions.yml
vendored
@@ -2,11 +2,11 @@ name: 'Issues - Reply to specific labels'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled, unlabeled]
|
||||
types: [ labeled, unlabeled ]
|
||||
pull_request_target:
|
||||
types: [labeled, unlabeled]
|
||||
types: [ labeled, unlabeled ]
|
||||
discussion:
|
||||
types: [labeled, unlabeled]
|
||||
types: [ labeled, unlabeled ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
130
.github/workflows/release.yml
vendored
130
.github/workflows/release.yml
vendored
@@ -4,11 +4,11 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to release'
|
||||
description: 'Release "v1.2.3" or "develop"'
|
||||
required: true
|
||||
default: 'develop'
|
||||
schedule:
|
||||
- cron: '0 3 * * MON,THU'
|
||||
- cron: '0 3 * * MON,THU'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
CROWDIN_TOKEN: ${{ secrets.CROWDIN_TOKEN }}
|
||||
- name: Cleanup translations
|
||||
id: cleanup-transactions
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
uses: JC5/firefly-iii-dev@v38
|
||||
with:
|
||||
action: 'ff3:crowdin-warning'
|
||||
output: ''
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
GH_TOKEN: ''
|
||||
- name: Cleanup changelog
|
||||
id: cleanup-changelog
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
uses: JC5/firefly-iii-dev@v38
|
||||
with:
|
||||
action: 'ff3:changelog'
|
||||
output: ''
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.CHANGELOG_TOKEN }}
|
||||
- name: Extract changelog
|
||||
id: extract-changelog
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
uses: JC5/firefly-iii-dev@v38
|
||||
with:
|
||||
action: 'ff3:extract-changelog'
|
||||
output: 'output'
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
GH_TOKEN: ""
|
||||
- name: Replace version
|
||||
id: replace-version
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
uses: JC5/firefly-iii-dev@v38
|
||||
with:
|
||||
action: 'ff3:version'
|
||||
output: ''
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
FF_III_VERSION: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
- name: Generate JSON v1
|
||||
id: json-v1
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
uses: JC5/firefly-iii-dev@v38
|
||||
with:
|
||||
action: 'ff3:json-translations v1'
|
||||
output: ''
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
GH_TOKEN: ''
|
||||
- name: Generate JSON v2
|
||||
id: json-v2
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
uses: JC5/firefly-iii-dev@v38
|
||||
with:
|
||||
action: 'ff3:json-translations v2'
|
||||
output: ''
|
||||
@@ -106,27 +106,19 @@ jobs:
|
||||
GH_TOKEN: ''
|
||||
- name: Code cleanup
|
||||
id: code-cleanup
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
uses: JC5/firefly-iii-dev@v38
|
||||
with:
|
||||
action: 'ff3:code'
|
||||
output: ''
|
||||
env:
|
||||
FIREFLY_III_ROOT: /github/workspace
|
||||
GH_TOKEN: ''
|
||||
- name: Build new JS
|
||||
- name: Build JS
|
||||
run: |
|
||||
pwd
|
||||
npm install
|
||||
npm run build
|
||||
- name: Build old JS
|
||||
id: old-js
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
with:
|
||||
action: 'ff3:old-js'
|
||||
output: ''
|
||||
env:
|
||||
FIREFLY_III_ROOT: /github/workspace
|
||||
GH_TOKEN: ''
|
||||
npm update
|
||||
npm run prod --workspace=v1
|
||||
npm run build --workspace=v2
|
||||
- name: Run CI
|
||||
run: |
|
||||
rm -rf vendor composer.lock
|
||||
@@ -136,20 +128,51 @@ jobs:
|
||||
.ci/phpcs.sh
|
||||
- name: Release
|
||||
run: |
|
||||
# do some configuration
|
||||
sudo timedatectl set-timezone Europe/Amsterdam
|
||||
git config user.name github-actions
|
||||
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
git config advice.addIgnoredFile false
|
||||
|
||||
# set some variables
|
||||
releaseName=$version
|
||||
originalName=$version
|
||||
zipName=FireflyIII-$version.zip
|
||||
tarName=FireflyIII-$version.tar.gz
|
||||
|
||||
# update composer (again)
|
||||
composer validate --strict
|
||||
composer update --no-dev --no-scripts --no-plugins
|
||||
composer dump-autoload
|
||||
|
||||
# if this is a develop build, slightly different variable names.
|
||||
if [[ "develop" == "$version" ]]; then
|
||||
[[ -z $(git status --untracked-files=normal --porcelain) ]] && echo "this branch is clean, no need to push..." && exit 0;
|
||||
releaseName=$version-$(date +'%Y%m%d')
|
||||
originalName=$releaseName
|
||||
zipName=FireflyIII-develop.zip
|
||||
tarName=FireflyIII-develop.tar.gz
|
||||
fi
|
||||
|
||||
# in both cases, if the release or tag already exists, add ".1" until it no longer exists.
|
||||
tagFound=true
|
||||
tagCount=1
|
||||
while [ "$tagFound" = true ]
|
||||
do
|
||||
if [ $(git tag -l "$releaseName") ]; then
|
||||
echo "Tag $releaseName exists already."
|
||||
releaseName="$originalName"."$tagCount"
|
||||
echo "Tag for release is now $releaseName"
|
||||
tagCount=$((tagCount+1))
|
||||
else
|
||||
echo "Tag $releaseName does not exist, can continue"
|
||||
tagFound=false
|
||||
fi
|
||||
done
|
||||
echo "Will use tag and release name $releaseName."
|
||||
|
||||
# add all content, except output.txt (this contains the changelog and/or the download instructions)
|
||||
echo 'Add all and reset output.txt'
|
||||
git add -A
|
||||
if test -f "output.txt"; then
|
||||
git reset output.txt
|
||||
@@ -157,29 +180,88 @@ jobs:
|
||||
git commit -m "Auto commit for release '$version' on $(date +'%Y-%m-%d')" || true
|
||||
git push
|
||||
|
||||
# zip everything
|
||||
# zip and tar everything
|
||||
echo 'Zip and tar...'
|
||||
zip -rq $zipName . -x "*.git*" "*.ci*" "*.github*" "*node_modules*" "*output.txt*"
|
||||
touch $tarName
|
||||
tar --exclude=$tarName --exclude=$zipName --exclude='./.git' --exclude='./.ci' --exclude='./.github' --exclude='./node_modules' --exclude='./output.txt' -czf $tarName .
|
||||
|
||||
# add sha256 sum
|
||||
echo 'Sha sum ...'
|
||||
sha256sum -b $zipName > $zipName.sha256
|
||||
sha256sum -b $tarName > $tarName.sha256
|
||||
|
||||
# create a development (nightly) release:
|
||||
if [[ "develop" == "$version" ]]; then
|
||||
echo 'Develop release.'
|
||||
# add text to output.txt (instructions)
|
||||
rm output.txt
|
||||
echo "Bi-weekly development release of Firefly III with the latest fixes, translations and features. Docker users can find this release under the \`develop\` tag." >> output.txt
|
||||
echo "" >> output.txt
|
||||
echo "This release was created on **$(date +'%Y-%m-%d')** and may contain unexpected bugs. Data loss is rare but is not impossible." >> output.txt
|
||||
echo "" >> output.txt
|
||||
echo "* Please read the installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt
|
||||
echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
|
||||
echo "" >> output.txt
|
||||
echo ":warning: Please be careful with this pre-release, as it may not work as expected." >> output.txt
|
||||
|
||||
# create the release:
|
||||
echo "Create nightly release."
|
||||
git tag -a $releaseName -m "Nightly development release '$version' on $(date +'%Y-%m-%d')"
|
||||
git push origin $releaseName
|
||||
gh release create $releaseName -p --verify-tag \
|
||||
-t "Development release for $(date +'%Y-%m-%d')" \
|
||||
-n "Bi-weekly development release of Firefly III with the latest fixes, translations and features. This release was created on **$(date +'%Y-%m-%d')** and may contain bugs. Use at your own risk. Docker users can find this release under the \`develop\` tag."
|
||||
-F output.txt
|
||||
|
||||
# add zip file to release.
|
||||
gh release upload $releaseName $zipName
|
||||
gh release upload $releaseName $tarName
|
||||
|
||||
# add sha256 sum to release
|
||||
gh release upload $releaseName $zipName.sha256
|
||||
gh release upload $releaseName $tarName.sha256
|
||||
|
||||
# get current HEAD and add as file to the release
|
||||
HEAD=$(git rev-parse HEAD)
|
||||
echo $HEAD > HEAD.txt
|
||||
gh release upload $releaseName HEAD.txt
|
||||
else
|
||||
echo 'MAIN (real) release'
|
||||
sudo chown -R runner:docker output.txt
|
||||
# add text to output.txt (more instructions)
|
||||
echo '' >> output.txt
|
||||
echo '### Instructions' >> output.txt
|
||||
echo '' >> output.txt
|
||||
echo "* Installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt
|
||||
echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
|
||||
|
||||
echo "Create default release."
|
||||
git tag -a $releaseName -m "Here be changelog"
|
||||
git push origin $releaseName
|
||||
gh release create $releaseName -F output.txt -t "$releaseName" --verify-tag
|
||||
# add zip file to release.
|
||||
|
||||
# add archive files to release
|
||||
gh release upload $releaseName $zipName
|
||||
gh release upload $releaseName $tarName
|
||||
|
||||
# add sha256 sums to release
|
||||
gh release upload $releaseName $zipName.sha256
|
||||
gh release upload $releaseName $tarName.sha256
|
||||
|
||||
# get current HEAD and add as file to the release
|
||||
HEAD=$(git rev-parse HEAD)
|
||||
echo $HEAD > HEAD.txt
|
||||
gh release upload $releaseName HEAD.txt
|
||||
|
||||
# remove all temporary files
|
||||
rm output.txt
|
||||
rm HEAD.txt
|
||||
rm $zipName
|
||||
rm $zipName.sha256
|
||||
rm $tarName
|
||||
rm $tarName.sha256
|
||||
|
||||
# merge main back into develop
|
||||
git checkout develop
|
||||
git merge main
|
||||
git push
|
||||
|
||||
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: >
|
||||
stale-issue-message: |
|
||||
Hi there!
|
||||
|
||||
This is an automatic reply. `Share and enjoy`
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
|
||||
|
||||
Thank you for your contributions.
|
||||
stale-pr-message: >
|
||||
stale-pr-message: |
|
||||
Hi there!
|
||||
|
||||
This is an automatic reply. `Share and enjoy`
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -10,3 +10,11 @@ coverage.xml
|
||||
|
||||
# ignore generated files.
|
||||
public/build
|
||||
|
||||
# ignore v1 build files
|
||||
resources/assets/v1/node_modules
|
||||
resources/assets/v1/build
|
||||
|
||||
# ignore v2 build files
|
||||
resources/assets/v2/node_modules
|
||||
resources/assets/v2/build
|
||||
|
||||
@@ -83,17 +83,17 @@ class AccountController extends Controller
|
||||
// user's preferences
|
||||
$defaultSet = $this->repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray();
|
||||
|
||||
/** @var Preference $frontPage */
|
||||
$frontPage = app('preferences')->get('frontPageAccounts', $defaultSet);
|
||||
/** @var Preference $frontpage */
|
||||
$frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
|
||||
$default = app('amount')->getDefaultCurrency();
|
||||
|
||||
if (!(is_array($frontPage->data) && count($frontPage->data) > 0)) {
|
||||
$frontPage->data = $defaultSet;
|
||||
$frontPage->save();
|
||||
if (!(is_array($frontpage->data) && count($frontpage->data) > 0)) {
|
||||
$frontpage->data = $defaultSet;
|
||||
$frontpage->save();
|
||||
}
|
||||
|
||||
// get accounts:
|
||||
$accounts = $this->repository->getAccountsById($frontPage->data);
|
||||
$accounts = $this->repository->getAccountsById($frontpage->data);
|
||||
$chartData = [];
|
||||
|
||||
/** @var Account $account */
|
||||
|
||||
@@ -72,6 +72,11 @@ class UpdateController extends Controller
|
||||
app('log')->debug('Now in update routine for transaction group!');
|
||||
$data = $request->getAll();
|
||||
|
||||
// Fixes 8750.
|
||||
foreach ($data['transactions'] as $index => $info) {
|
||||
unset($data['transactions'][$index]['type']);
|
||||
}
|
||||
|
||||
$transactionGroup = $this->groupRepository->update($transactionGroup, $data);
|
||||
$manager = $this->getManager();
|
||||
|
||||
|
||||
@@ -54,17 +54,13 @@ class TestRequest extends FormRequest
|
||||
|
||||
private function getDate(string $field): ?Carbon
|
||||
{
|
||||
$value = $this->query($field);
|
||||
$value = $this->query($field);
|
||||
if (is_array($value)) {
|
||||
return null;
|
||||
}
|
||||
$value = (string)$value;
|
||||
$result = null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
|
||||
if (false === $result) {
|
||||
return null;
|
||||
}
|
||||
$value = (string)$value;
|
||||
|
||||
return $result;
|
||||
return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
|
||||
}
|
||||
|
||||
private function getAccounts(): array
|
||||
|
||||
@@ -48,17 +48,13 @@ class TriggerRequest extends FormRequest
|
||||
|
||||
private function getDate(string $field): ?Carbon
|
||||
{
|
||||
$value = $this->query($field);
|
||||
$value = $this->query($field);
|
||||
if (is_array($value)) {
|
||||
return null;
|
||||
}
|
||||
$value = (string)$value;
|
||||
$result = null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
|
||||
if (false === $result) {
|
||||
return null;
|
||||
}
|
||||
$value = (string)$value;
|
||||
|
||||
return $result;
|
||||
return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
|
||||
}
|
||||
|
||||
private function getAccounts(): array
|
||||
|
||||
@@ -48,17 +48,13 @@ class TestRequest extends FormRequest
|
||||
|
||||
private function getDate(string $field): ?Carbon
|
||||
{
|
||||
$value = $this->query($field);
|
||||
$value = $this->query($field);
|
||||
if (is_array($value)) {
|
||||
return null;
|
||||
}
|
||||
$value = (string)$value;
|
||||
$result = null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
|
||||
if (false === $result) {
|
||||
return null;
|
||||
}
|
||||
$value = (string)$value;
|
||||
|
||||
return $result;
|
||||
return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
|
||||
}
|
||||
|
||||
private function getAccounts(): array
|
||||
|
||||
@@ -48,17 +48,13 @@ class TriggerRequest extends FormRequest
|
||||
|
||||
private function getDate(string $field): ?Carbon
|
||||
{
|
||||
$value = $this->query($field);
|
||||
$value = $this->query($field);
|
||||
if (is_array($value)) {
|
||||
return null;
|
||||
}
|
||||
$value = (string)$value;
|
||||
$result = null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
|
||||
if (false === $result) {
|
||||
return null;
|
||||
}
|
||||
$value = (string)$value;
|
||||
|
||||
return $result;
|
||||
return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
|
||||
}
|
||||
|
||||
private function getAccounts(): array
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace FireflyIII\Api\V2\Controllers\Chart;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Chart\DashboardChartRequest;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
@@ -46,6 +47,7 @@ class AccountController extends Controller
|
||||
use ValidatesUserGroupTrait;
|
||||
|
||||
private AccountRepositoryInterface $repository;
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -54,9 +56,7 @@ class AccountController extends Controller
|
||||
function ($request, $next) {
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -98,14 +98,14 @@ class AccountController extends Controller
|
||||
// user's preferences
|
||||
if (0 === $accounts->count()) {
|
||||
$defaultSet = $this->repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT])->pluck('id')->toArray();
|
||||
$frontPage = app('preferences')->get('frontPageAccounts', $defaultSet);
|
||||
$frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
|
||||
|
||||
if (!(is_array($frontPage->data) && count($frontPage->data) > 0)) {
|
||||
$frontPage->data = $defaultSet;
|
||||
$frontPage->save();
|
||||
if (!(is_array($frontpage->data) && count($frontpage->data) > 0)) {
|
||||
$frontpage->data = $defaultSet;
|
||||
$frontpage->save();
|
||||
}
|
||||
|
||||
$accounts = $this->repository->getAccountsById($frontPage->data);
|
||||
$accounts = $this->repository->getAccountsById($frontpage->data);
|
||||
}
|
||||
|
||||
// both options are overruled by "preselected"
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace FireflyIII\Api\V2\Controllers\Chart;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Chart\BalanceChartRequest;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
@@ -42,6 +43,7 @@ use Illuminate\Support\Collection;
|
||||
class BalanceController extends Controller
|
||||
{
|
||||
use CleansChartData;
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||
|
||||
/**
|
||||
* The code is practically a duplicate of ReportController::operations.
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace FireflyIII\Api\V2\Controllers;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\Exceptions\InvalidDateException;
|
||||
use Carbon\Exceptions\InvalidFormatException;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Transformers\V2\AbstractTransformer;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -55,6 +56,7 @@ class Controller extends BaseController
|
||||
|
||||
protected const string CONTENT_TYPE = 'application/vnd.api+json';
|
||||
protected ParameterBag $parameters;
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace FireflyIII\Api\V2\Controllers\Model\Account;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Model\Account\IndexRequest;
|
||||
use FireflyIII\Api\V2\Request\Model\Transaction\InfiniteListRequest;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\AccountTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -33,9 +34,10 @@ use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
class IndexController extends Controller
|
||||
{
|
||||
public const string RESOURCE_KEY = 'accounts';
|
||||
public const string RESOURCE_KEY = 'accounts';
|
||||
|
||||
private AccountRepositoryInterface $repository;
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY, UserRoleEnum::MANAGE_TRANSACTIONS];
|
||||
|
||||
/**
|
||||
* AccountController constructor.
|
||||
@@ -48,9 +50,7 @@ class IndexController extends Controller
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
// new way of user group validation
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V2\Controllers\Model\Account;
|
||||
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\AccountTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
@@ -36,6 +38,29 @@ use Illuminate\Http\JsonResponse;
|
||||
*/
|
||||
class ShowController extends Controller
|
||||
{
|
||||
public const string RESOURCE_KEY = 'accounts';
|
||||
|
||||
private AccountRepositoryInterface $repository;
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY, UserRoleEnum::MANAGE_TRANSACTIONS];
|
||||
|
||||
/**
|
||||
* AccountController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
// new way of user group validation
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO this endpoint is not yet reachable.
|
||||
*/
|
||||
|
||||
@@ -77,13 +77,11 @@ class BasicController extends Controller
|
||||
$this->opsRepository = app(OperationsRepositoryInterface::class);
|
||||
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->abRepository->setUserGroup($userGroup);
|
||||
$this->accountRepository->setUserGroup($userGroup);
|
||||
$this->billRepository->setUserGroup($userGroup);
|
||||
$this->budgetRepository->setUserGroup($userGroup);
|
||||
$this->opsRepository->setUserGroup($userGroup);
|
||||
}
|
||||
$this->abRepository->setUserGroup($userGroup);
|
||||
$this->accountRepository->setUserGroup($userGroup);
|
||||
$this->billRepository->setUserGroup($userGroup);
|
||||
$this->budgetRepository->setUserGroup($userGroup);
|
||||
$this->opsRepository->setUserGroup($userGroup);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -298,7 +296,7 @@ class BasicController extends Controller
|
||||
app('log')->debug(sprintf('Amount left is %s', $left));
|
||||
|
||||
// how much left per day?
|
||||
$days = (int) $today->diffInDays($end, true) + 1;
|
||||
$days = (int)$today->diffInDays($end, true) + 1;
|
||||
$perDay = '0';
|
||||
$perDayNative = '0';
|
||||
if (0 !== $days && bccomp($left, '0') > -1) {
|
||||
|
||||
73
app/Api/V2/Controllers/UserGroup/IndexController.php
Normal file
73
app/Api/V2/Controllers/UserGroup/IndexController.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/*
|
||||
* IndexController.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\Api\V2\Controllers\UserGroup;
|
||||
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Model\Account\IndexRequest;
|
||||
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\UserGroupTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
class IndexController extends Controller
|
||||
{
|
||||
public const string RESOURCE_KEY = 'user_groups';
|
||||
|
||||
private UserGroupRepositoryInterface $repository;
|
||||
|
||||
/**
|
||||
* AccountController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(UserGroupRepositoryInterface::class);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO see autocomplete/accountcontroller for list.
|
||||
*/
|
||||
public function index(IndexRequest $request): JsonResponse
|
||||
{
|
||||
$administrations = $this->repository->get();
|
||||
$pageSize = $this->parameters->get('limit');
|
||||
$count = $administrations->count();
|
||||
$administrations = $administrations->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
$paginator = new LengthAwarePaginator($administrations, $count, $pageSize, $this->parameters->get('page'));
|
||||
$transformer = new UserGroupTransformer();
|
||||
|
||||
$transformer->setParameters($this->parameters); // give params to transformer
|
||||
|
||||
return response()
|
||||
->json($this->jsonApiList(self::RESOURCE_KEY, $paginator, $transformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -79,4 +79,12 @@ class UpdateController extends Controller
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
|
||||
public function useUserGroup(UserGroup $userGroup): JsonResponse
|
||||
{
|
||||
// group validation is already in place, so can just update the user.
|
||||
$this->repository->useUserGroup($userGroup);
|
||||
|
||||
return response()->json([], 204);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V2\Request\Chart;
|
||||
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
@@ -39,6 +40,8 @@ class DashboardChartRequest extends FormRequest
|
||||
use ConvertsDataTypes;
|
||||
use ValidatesUserGroupTrait;
|
||||
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||
|
||||
/**
|
||||
* Get all data from the request.
|
||||
*/
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace FireflyIII\Api\V2\Request\UserGroup;
|
||||
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Rules\IsDefaultUserGroupName;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
@@ -53,7 +54,7 @@ class UpdateRequest extends FormRequest
|
||||
$userGroup = $this->route()->parameter('userGroup');
|
||||
|
||||
return [
|
||||
'title' => sprintf('required|min:1|max:255|unique:user_groups,title,%d', $userGroup->id),
|
||||
'title' => ['required', 'min:1', 'max:255', sprintf('unique:user_groups,title,%d', $userGroup->id), new IsDefaultUserGroupName($userGroup)],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ class CorrectDatabase extends Command
|
||||
// new!
|
||||
'firefly-iii:unify-group-accounts',
|
||||
'firefly-iii:trigger-credit-recalculation',
|
||||
'firefly-iii:migrate-preferences',
|
||||
];
|
||||
foreach ($commands as $command) {
|
||||
$this->friendlyLine(sprintf('Now executing command "%s"', $command));
|
||||
|
||||
@@ -50,7 +50,7 @@ class FixFrontpageAccounts extends Command
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$preference = app('preferences')->getForUser($user, 'frontPageAccounts');
|
||||
$preference = app('preferences')->getForUser($user, 'frontpageAccounts');
|
||||
if (null !== $preference) {
|
||||
$this->fixPreference($preference);
|
||||
}
|
||||
@@ -83,6 +83,6 @@ class FixFrontpageAccounts extends Command
|
||||
}
|
||||
}
|
||||
}
|
||||
app('preferences')->setForUser($preference->user, 'frontPageAccounts', $fixed);
|
||||
app('preferences')->setForUser($preference->user, 'frontpageAccounts', $fixed);
|
||||
}
|
||||
}
|
||||
|
||||
66
app/Console/Commands/Correction/MigratePreferences.php
Normal file
66
app/Console/Commands/Correction/MigratePreferences.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* MigratePreferences.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\Correction;
|
||||
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Command\Command as CommandAlias;
|
||||
|
||||
class MigratePreferences extends Command
|
||||
{
|
||||
protected $description = 'Give Firefly III preferences a user group ID so they can be made administration specific.';
|
||||
|
||||
protected $signature = 'firefly-iii:migrate-preferences';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$items = config('firefly.admin_specific_prefs');
|
||||
$users = User::get();
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$count = 0;
|
||||
foreach ($items as $item) {
|
||||
$preference = Preference::where('name', $item)->where('user_id', $user->id)->first();
|
||||
if (null === $preference) {
|
||||
continue;
|
||||
}
|
||||
if (null !== $preference->user_group_id) {
|
||||
$preference->user_group_id = $user->user_group_id;
|
||||
$preference->save();
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
if ($count > 0) {
|
||||
$this->info(sprintf('Migrated %d preference(s) for user #%d ("%s").', $count, $user->id, $user->email));
|
||||
}
|
||||
}
|
||||
|
||||
return CommandAlias::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -191,7 +191,7 @@ class ExportData extends Command
|
||||
$this->friendlyError(sprintf('%s date "%s" must be formatted YYYY-MM-DD. Field will be ignored.', $field, $this->option('start')));
|
||||
$error = true;
|
||||
}
|
||||
if (false === $date) {
|
||||
if (null === $date) {
|
||||
$this->friendlyError(sprintf('%s date "%s" must be formatted YYYY-MM-DD.', $field, $this->option('start')));
|
||||
|
||||
throw new FireflyException(sprintf('%s date "%s" must be formatted YYYY-MM-DD.', $field, $this->option('start')));
|
||||
|
||||
@@ -80,7 +80,7 @@ class CreateGroupMemberships extends Command
|
||||
// check if membership exists
|
||||
$userGroup = UserGroup::where('title', $user->email)->first();
|
||||
if (null === $userGroup) {
|
||||
$userGroup = UserGroup::create(['title' => $user->email]);
|
||||
$userGroup = UserGroup::create(['title' => $user->email, 'default_administration' => true]);
|
||||
}
|
||||
|
||||
$userRole = UserRole::where('title', UserRoleEnum::OWNER->value)->first();
|
||||
|
||||
@@ -32,24 +32,13 @@ class LaravelPassportKeys extends Command
|
||||
{
|
||||
use ShowsFriendlyMessages;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly-iii:laravel-passport-keys';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Calls the Laravel "passport:keys" but doesn\'t exit 1.';
|
||||
protected $signature = 'firefly-iii:laravel-passport-keys';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
Artisan::call('passport:keys --no-interaction', []);
|
||||
$result = Artisan::output();
|
||||
|
||||
@@ -285,7 +285,7 @@ class ApplyRules extends Command
|
||||
if (null !== $endString && '' !== $endString) {
|
||||
$inputEnd = Carbon::createFromFormat('Y-m-d', $endString);
|
||||
}
|
||||
if (false === $inputEnd || false === $inputStart) {
|
||||
if (null === $inputEnd || null === $inputStart) {
|
||||
Log::error('Could not parse start or end date in verifyInputDate().');
|
||||
|
||||
return;
|
||||
|
||||
@@ -69,6 +69,7 @@ class UpgradeDatabase extends Command
|
||||
'firefly-iii:create-group-memberships',
|
||||
'firefly-iii:upgrade-group-information',
|
||||
'firefly-iii:upgrade-currency-preferences',
|
||||
'firefly-iii:correct-database',
|
||||
];
|
||||
$args = [];
|
||||
if ($this->option('force')) {
|
||||
|
||||
@@ -41,7 +41,6 @@ enum UserRoleEnum: string
|
||||
// manage other financial objects:
|
||||
case MANAGE_BUDGETS = 'mng_budgets';
|
||||
case MANAGE_PIGGY_BANKS = 'mng_piggies';
|
||||
case MANAGE_REPETITIONS = 'mng_reps';
|
||||
case MANAGE_SUBSCRIPTIONS = 'mng_subscriptions';
|
||||
case MANAGE_RULES = 'mng_rules';
|
||||
case MANAGE_RECURRING = 'mng_recurring';
|
||||
@@ -51,7 +50,7 @@ enum UserRoleEnum: string
|
||||
// view and generate reports
|
||||
case VIEW_REPORTS = 'view_reports';
|
||||
|
||||
// view memberships. needs FULL to manage them.
|
||||
// view memberships AND roles. needs FULL to manage them.
|
||||
case VIEW_MEMBERSHIPS = 'view_memberships';
|
||||
|
||||
// everything the creator can, except remove/change original creator and delete group
|
||||
|
||||
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Exceptions;
|
||||
|
||||
use FireflyIII\Jobs\MailError;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
@@ -98,6 +99,13 @@ class Handler extends ExceptionHandler
|
||||
return response()->json(['message' => 'Resource not found', 'exception' => 'NotFoundHttpException'], 404);
|
||||
}
|
||||
|
||||
if ($e instanceof AuthorizationException && $expectsJson) {
|
||||
// somehow Laravel handler does not catch this:
|
||||
app('log')->debug('Return JSON unauthorized error.');
|
||||
|
||||
return response()->json(['message' => $e->getMessage(), 'exception' => 'AuthorizationException'], 401);
|
||||
}
|
||||
|
||||
if ($e instanceof AuthenticationException && $expectsJson) {
|
||||
// somehow Laravel handler does not catch this:
|
||||
app('log')->debug('Return JSON unauthenticated error.');
|
||||
|
||||
@@ -405,7 +405,7 @@ class UserEventHandler
|
||||
}
|
||||
// clean up old entries (6 months)
|
||||
$carbon = Carbon::createFromFormat('Y-m-d H:i:s', $preference[$index]['time']);
|
||||
if (false !== $carbon && $carbon->diffInMonths(today(), true) > 6) {
|
||||
if (null !== $carbon && $carbon->diffInMonths(today(), true) > 6) {
|
||||
app('log')->debug(sprintf('Entry for %s is very old, remove it.', $row['ip']));
|
||||
unset($preference[$index]);
|
||||
}
|
||||
|
||||
@@ -145,13 +145,13 @@ class CreateController extends Controller
|
||||
Log::channel('audit')->info('Stored new account.', $data);
|
||||
|
||||
// update preferences if necessary:
|
||||
$frontPage = app('preferences')->get('frontPageAccounts', [])->data;
|
||||
if (!is_array($frontPage)) {
|
||||
$frontPage = [];
|
||||
$frontpage = app('preferences')->get('frontpageAccounts', [])->data;
|
||||
if (!is_array($frontpage)) {
|
||||
$frontpage = [];
|
||||
}
|
||||
if (AccountType::ASSET === $account->accountType->type) {
|
||||
$frontPage[] = $account->id;
|
||||
app('preferences')->set('frontPageAccounts', $frontPage);
|
||||
$frontpage[] = $account->id;
|
||||
app('preferences')->set('frontpageAccounts', $frontpage);
|
||||
}
|
||||
|
||||
// store attachment(s):
|
||||
|
||||
@@ -106,7 +106,7 @@ class ShowController extends Controller
|
||||
$periods = $this->getAccountPeriodOverview($account, $firstTransaction, $end);
|
||||
|
||||
// if layout = v2, overrule the page title.
|
||||
if ('v1' !== config('firefly.layout')) {
|
||||
if ('v1' !== config('view.layout')) {
|
||||
$subTitle = (string)trans('firefly.all_journals_for_account', ['name' => $account->name]);
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ class BudgetLimitController extends Controller
|
||||
$start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
|
||||
$end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
|
||||
|
||||
if (false === $start || false === $end) {
|
||||
if (null === $start || null === $end) {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
@@ -235,12 +235,12 @@ class BudgetLimitController extends Controller
|
||||
new Collection([$budgetLimit->budget]),
|
||||
$budgetLimit->transactionCurrency
|
||||
);
|
||||
$daysLeft = $this->activeDaysLeft($limit->start_date, $limit->end_date);
|
||||
$array['spent'] = $spentArr[$budgetLimit->transactionCurrency->id]['sum'] ?? '0';
|
||||
$array['left_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, bcadd($array['spent'], $array['amount']));
|
||||
$array['amount_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, $limit['amount']);
|
||||
$array['days_left'] = (string)$this->activeDaysLeft($limit->start_date, $limit->end_date);
|
||||
// left per day:
|
||||
$array['left_per_day'] = bcdiv(bcadd($array['spent'], $array['amount']), $array['days_left']);
|
||||
$array['days_left'] = (string)$daysLeft;
|
||||
$array['left_per_day'] = 0 === $daysLeft ? bcadd($array['spent'], $array['amount']) : bcdiv(bcadd($array['spent'], $array['amount']), $array['days_left']);
|
||||
|
||||
// left per day formatted.
|
||||
$array['amount'] = app('steam')->bcround($limit['amount'], $limit->transactionCurrency->decimal_places);
|
||||
|
||||
@@ -301,14 +301,14 @@ class AccountController extends Controller
|
||||
$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);
|
||||
$frontPage = app('preferences')->get('frontPageAccounts', $defaultSet);
|
||||
$frontPageArray = !is_array($frontPage->data) ? [] : $frontPage->data;
|
||||
app('log')->debug('Frontpage preference set is ', $frontPageArray);
|
||||
if (0 === count($frontPageArray)) {
|
||||
app('preferences')->set('frontPageAccounts', $defaultSet);
|
||||
$frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
|
||||
$frontpageArray = !is_array($frontpage->data) ? [] : $frontpage->data;
|
||||
app('log')->debug('Frontpage preference set is ', $frontpageArray);
|
||||
if (0 === count($frontpageArray)) {
|
||||
app('preferences')->set('frontpageAccounts', $defaultSet);
|
||||
app('log')->debug('frontpage set is empty!');
|
||||
}
|
||||
$accounts = $repository->getAccountsById($frontPageArray);
|
||||
$accounts = $repository->getAccountsById($frontpageArray);
|
||||
|
||||
return response()->json($this->accountBalanceChart($accounts, $start, $end));
|
||||
}
|
||||
|
||||
@@ -116,8 +116,8 @@ class CategoryController extends Controller
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
|
||||
$frontPageGenerator = new FrontpageChartGenerator($start, $end);
|
||||
$chartData = $frontPageGenerator->generate();
|
||||
$frontpageGenerator = new FrontpageChartGenerator($start, $end);
|
||||
$chartData = $frontpageGenerator->generate();
|
||||
$data = $this->generator->multiSet($chartData);
|
||||
$cache->store($data);
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ abstract class Controller extends BaseController
|
||||
$logoutUrl = config('firefly.custom_logout_url');
|
||||
|
||||
// overrule v2 layout back to v1.
|
||||
if ('true' === request()->get('force_default_layout') && 'v2' === config('firefly.layout')) {
|
||||
if ('true' === request()->get('force_default_layout') && 'v2' === config('view.layout')) {
|
||||
app('view')->getFinder()->setPaths([realpath(base_path('resources/views'))]); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
|
||||
@@ -79,17 +79,17 @@ class HomeController extends Controller
|
||||
app('log')->error(sprintf('End could not parse date string "%s" so ignore it.', $stringEnd));
|
||||
$end = Carbon::now()->endOfMonth();
|
||||
}
|
||||
if (false === $start) {
|
||||
if (null === $start) {
|
||||
$start = Carbon::now()->startOfMonth();
|
||||
}
|
||||
if (false === $end) {
|
||||
if (null === $end) {
|
||||
$end = Carbon::now()->endOfMonth();
|
||||
}
|
||||
|
||||
$label = $request->get('label');
|
||||
$isCustomRange = false;
|
||||
|
||||
app('log')->debug('Received dateRange', ['start' => $stringStart, 'end' => $stringEnd, 'label' => $request->get('label')]);
|
||||
app('log')->debug('dateRange: Received dateRange', ['start' => $stringStart, 'end' => $stringEnd, 'label' => $request->get('label')]);
|
||||
// check if the label is "everything" or "Custom range" which will betray
|
||||
// a possible problem with the budgets.
|
||||
if ($label === (string)trans('firefly.everything') || $label === (string)trans('firefly.customRange')) {
|
||||
@@ -99,7 +99,7 @@ class HomeController extends Controller
|
||||
|
||||
$diff = $start->diffInDays($end, true) + 1;
|
||||
|
||||
if ($diff > 50) {
|
||||
if ($diff > 366) {
|
||||
$request->session()->flash('warning', (string)trans('firefly.warning_much_data', ['days' => (int)$diff]));
|
||||
}
|
||||
|
||||
@@ -120,33 +120,45 @@ class HomeController extends Controller
|
||||
*/
|
||||
public function index(AccountRepositoryInterface $repository): mixed
|
||||
{
|
||||
$types = config('firefly.accountTypesByIdentifier.asset');
|
||||
$count = $repository->count($types);
|
||||
$types = config('firefly.accountTypesByIdentifier.asset');
|
||||
$count = $repository->count($types);
|
||||
Log::channel('audit')->info('User visits homepage.');
|
||||
|
||||
if (0 === $count) {
|
||||
return redirect(route('new-user.index'));
|
||||
}
|
||||
|
||||
if ('v1' === (string)config('view.layout')) {
|
||||
return $this->indexV1($repository);
|
||||
}
|
||||
if ('v2' === (string)config('view.layout')) {
|
||||
return $this->indexV2();
|
||||
}
|
||||
|
||||
throw new FireflyException('Invalid layout configuration');
|
||||
}
|
||||
|
||||
private function indexV1(AccountRepositoryInterface $repository): mixed
|
||||
{
|
||||
$types = config('firefly.accountTypesByIdentifier.asset');
|
||||
$count = $repository->count($types);
|
||||
$subTitle = (string)trans('firefly.welcome_back');
|
||||
$transactions = [];
|
||||
$frontPage = app('preferences')->getFresh('frontPageAccounts', $repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray());
|
||||
$frontPageArray = $frontPage->data;
|
||||
if (!is_array($frontPageArray)) {
|
||||
$frontPageArray = [];
|
||||
$frontpage = app('preferences')->getFresh('frontpageAccounts', $repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray());
|
||||
$frontpageArray = $frontpage->data;
|
||||
if (!is_array($frontpageArray)) {
|
||||
$frontpageArray = [];
|
||||
}
|
||||
|
||||
/** @var Carbon $start */
|
||||
$start = session('start', today(config('app.timezone'))->startOfMonth());
|
||||
|
||||
/** @var Carbon $end */
|
||||
$start = session('start', today(config('app.timezone'))->startOfMonth());
|
||||
$end = session('end', today(config('app.timezone'))->endOfMonth());
|
||||
$accounts = $repository->getAccountsById($frontPageArray);
|
||||
$accounts = $repository->getAccountsById($frontpageArray);
|
||||
$today = today(config('app.timezone'));
|
||||
$accounts = $accounts->sortBy('order'); // sort frontpage accounts by order
|
||||
|
||||
// sort frontpage accounts by order
|
||||
$accounts = $accounts->sortBy('order');
|
||||
|
||||
app('log')->debug('Frontpage accounts are ', $frontPageArray);
|
||||
app('log')->debug('Frontpage accounts are ', $frontpageArray);
|
||||
|
||||
/** @var BillRepositoryInterface $billRepository */
|
||||
$billRepository = app(BillRepositoryInterface::class);
|
||||
@@ -166,4 +178,18 @@ class HomeController extends Controller
|
||||
|
||||
return view('index', compact('count', 'subTitle', 'transactions', 'billCount', 'start', 'end', 'today'));
|
||||
}
|
||||
|
||||
private function indexV2(): mixed
|
||||
{
|
||||
$subTitle = (string)trans('firefly.welcome_back');
|
||||
|
||||
$start = session('start', today(config('app.timezone'))->startOfMonth());
|
||||
$end = session('end', today(config('app.timezone'))->endOfMonth());
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
event(new RequestedVersionCheckStatus($user));
|
||||
|
||||
return view('index', compact('subTitle', 'start', 'end'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ class BoxController extends Controller
|
||||
$boxTitle = (string)trans('firefly.left_to_spend');
|
||||
$activeDaysLeft = $this->activeDaysLeft($start, $end); // see method description.
|
||||
$display = 1; // not overspent
|
||||
$leftPerDayAmount = bcdiv($leftToSpendAmount, (string)$activeDaysLeft);
|
||||
$leftPerDayAmount = 0 === $activeDaysLeft ? $leftToSpendAmount : bcdiv($leftToSpendAmount, (string)$activeDaysLeft);
|
||||
app('log')->debug(sprintf('Left to spend per day is %s', $leftPerDayAmount));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ class RecurrenceController extends Controller
|
||||
$skip = $skip < 0 || $skip > 31 ? 0 : $skip;
|
||||
$weekend = $weekend < 1 || $weekend > 4 ? 1 : $weekend;
|
||||
|
||||
if (false === $start || false === $end || false === $firstDate || false === $endDate) {
|
||||
if (null === $start || null === $end || null === $firstDate || null === $endDate) {
|
||||
return response()->json();
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ class RecurrenceController extends Controller
|
||||
$actualEnd = clone $end;
|
||||
|
||||
if ('until_date' === $endsAt) {
|
||||
$actualEnd = $endDate ?? clone $end;
|
||||
$actualEnd = $endDate;
|
||||
$occurrences = $this->recurring->getOccurrencesInRange($repetition, $actualStart, $actualEnd);
|
||||
}
|
||||
if ('times' === $endsAt) {
|
||||
@@ -155,7 +155,7 @@ class RecurrenceController extends Controller
|
||||
} catch (InvalidFormatException $e) {
|
||||
$date = Carbon::today(config('app.timezone'));
|
||||
}
|
||||
if (false === $date) {
|
||||
if (null === $date) {
|
||||
return response()->json();
|
||||
}
|
||||
$date->startOfDay();
|
||||
|
||||
@@ -115,7 +115,7 @@ class NewUserController extends Controller
|
||||
|
||||
// store frontpage preferences:
|
||||
$accounts = $this->repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray();
|
||||
app('preferences')->set('frontPageAccounts', $accounts);
|
||||
app('preferences')->set('frontpageAccounts', $accounts);
|
||||
|
||||
// mark.
|
||||
app('preferences')->mark();
|
||||
|
||||
@@ -89,10 +89,10 @@ class PreferencesController extends Controller
|
||||
/** @var array<int, int> $accountIds */
|
||||
$accountIds = $accounts->pluck('id')->toArray();
|
||||
$viewRange = app('navigation')->getViewRange(false);
|
||||
$frontPageAccountsPref = app('preferences')->get('frontPageAccounts', $accountIds);
|
||||
$frontPageAccounts = $frontPageAccountsPref->data;
|
||||
if (!is_array($frontPageAccounts)) {
|
||||
$frontPageAccounts = $accountIds;
|
||||
$frontpageAccountsPref = app('preferences')->get('frontpageAccounts', $accountIds);
|
||||
$frontpageAccounts = $frontpageAccountsPref->data;
|
||||
if (!is_array($frontpageAccounts)) {
|
||||
$frontpageAccounts = $accountIds;
|
||||
}
|
||||
$language = app('steam')->getLanguage();
|
||||
$languages = config('firefly.languages');
|
||||
@@ -128,8 +128,8 @@ class PreferencesController extends Controller
|
||||
$locales = ['equal' => (string)trans('firefly.equal_to_language')] + $locales;
|
||||
// an important fallback is that the frontPageAccount array gets refilled automatically
|
||||
// when it turns up empty.
|
||||
if (0 === count($frontPageAccounts)) {
|
||||
$frontPageAccounts = $accountIds;
|
||||
if (0 === count($frontpageAccounts)) {
|
||||
$frontpageAccounts = $accountIds;
|
||||
}
|
||||
|
||||
// for the demo user, the slackUrl is automatically emptied.
|
||||
@@ -139,7 +139,7 @@ class PreferencesController extends Controller
|
||||
$slackUrl = '';
|
||||
}
|
||||
|
||||
return view('preferences.index', compact('language', 'groupedAccounts', 'isDocker', 'frontPageAccounts', 'languages', 'darkMode', 'availableDarkModes', 'notifications', 'slackUrl', 'locales', 'locale', 'tjOptionalFields', 'viewRange', 'customFiscalYear', 'listPageSize', 'fiscalYearStart'));
|
||||
return view('preferences.index', compact('language', 'groupedAccounts', 'isDocker', 'frontpageAccounts', 'languages', 'darkMode', 'availableDarkModes', 'notifications', 'slackUrl', 'locales', 'locale', 'tjOptionalFields', 'viewRange', 'customFiscalYear', 'listPageSize', 'fiscalYearStart'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,12 +155,12 @@ class PreferencesController extends Controller
|
||||
public function postIndex(Request $request)
|
||||
{
|
||||
// front page accounts
|
||||
$frontPageAccounts = [];
|
||||
if (is_array($request->get('frontPageAccounts')) && count($request->get('frontPageAccounts')) > 0) {
|
||||
foreach ($request->get('frontPageAccounts') as $id) {
|
||||
$frontPageAccounts[] = (int)$id;
|
||||
$frontpageAccounts = [];
|
||||
if (is_array($request->get('frontpageAccounts')) && count($request->get('frontpageAccounts')) > 0) {
|
||||
foreach ($request->get('frontpageAccounts') as $id) {
|
||||
$frontpageAccounts[] = (int)$id;
|
||||
}
|
||||
app('preferences')->set('frontPageAccounts', $frontPageAccounts);
|
||||
app('preferences')->set('frontpageAccounts', $frontpageAccounts);
|
||||
}
|
||||
|
||||
// extract notifications:
|
||||
@@ -223,8 +223,8 @@ class PreferencesController extends Controller
|
||||
|
||||
// same for locale:
|
||||
if (!auth()->user()->hasRole('demo')) {
|
||||
/** @var Preference $locale */
|
||||
$locale = $request->get('locale');
|
||||
$locale = (string) $request->get('locale');
|
||||
$locale = '' === $locale ? null : $locale;
|
||||
app('preferences')->set('locale', $locale);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use FireflyIII\Events\UserChangedEmail;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Exceptions\ValidationException;
|
||||
@@ -467,9 +466,7 @@ class ProfileController extends Controller
|
||||
if (is_array($secret)) {
|
||||
$secret = null;
|
||||
}
|
||||
if (is_int($secret)) {
|
||||
$secret = (string)$secret;
|
||||
}
|
||||
$secret = (string)$secret;
|
||||
|
||||
$repository->setMFACode($user, $secret);
|
||||
|
||||
|
||||
45
app/Http/Controllers/UserGroup/CreateController.php
Normal file
45
app/Http/Controllers/UserGroup/CreateController.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/*
|
||||
* CreateController.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\UserGroup;
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Foundation\Application;
|
||||
|
||||
class CreateController extends Controller
|
||||
{
|
||||
/**
|
||||
* @return Application|Factory|\Illuminate\Contracts\Foundation\Application|View
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$title = (string)trans('firefly.administrations_page_title');
|
||||
$subTitle = (string)trans('firefly.administrations_page_create_sub_title');
|
||||
$mainTitleIcon = 'fa-book';
|
||||
app('log')->debug(sprintf('Now at %s', __METHOD__));
|
||||
|
||||
return view('administrations.create')->with(compact('title', 'subTitle', 'mainTitleIcon'));
|
||||
}
|
||||
}
|
||||
46
app/Http/Controllers/UserGroup/EditController.php
Normal file
46
app/Http/Controllers/UserGroup/EditController.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/*
|
||||
* CreateController.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\UserGroup;
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Foundation\Application;
|
||||
|
||||
class EditController extends Controller
|
||||
{
|
||||
/**
|
||||
* @return Application|Factory|\Illuminate\Contracts\Foundation\Application|View
|
||||
*/
|
||||
public function edit(UserGroup $userGroup)
|
||||
{
|
||||
$title = (string)trans('firefly.administrations_page_title');
|
||||
$subTitle = (string)trans('firefly.administrations_page_edit_sub_title', ['title' => $userGroup->title]);
|
||||
$mainTitleIcon = 'fa-book';
|
||||
app('log')->debug(sprintf('Now at %s', __METHOD__));
|
||||
|
||||
return view('administrations.edit')->with(compact('title', 'subTitle', 'mainTitleIcon'));
|
||||
}
|
||||
}
|
||||
47
app/Http/Controllers/UserGroup/IndexController.php
Normal file
47
app/Http/Controllers/UserGroup/IndexController.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/*
|
||||
* IndexController.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\UserGroup;
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class IndexController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show all administrations.
|
||||
*
|
||||
* @return Factory|View
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$title = (string)trans('firefly.administrations_page_title');
|
||||
$subTitle = (string)trans('firefly.administrations_page_sub_title');
|
||||
$mainTitleIcon = 'fa-book';
|
||||
app('log')->debug(sprintf('Now at %s', __METHOD__));
|
||||
|
||||
return view('administrations.index')->with(compact('title', 'subTitle', 'mainTitleIcon'));
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ class DownloadExchangeRates implements ShouldQueue
|
||||
return;
|
||||
}
|
||||
$date = Carbon::createFromFormat('Y-m-d', $json['date'], config('app.timezone'));
|
||||
if (false === $date) {
|
||||
if (null === $date) {
|
||||
return;
|
||||
}
|
||||
$this->saveRates($currency, $date, $json['rates']);
|
||||
|
||||
@@ -27,6 +27,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\Mailer\Exception\TransportException;
|
||||
|
||||
/**
|
||||
@@ -68,6 +69,14 @@ class MailError extends Job implements ShouldQueue
|
||||
$args['user'] = $this->userData;
|
||||
$args['ip'] = $this->ipAddress;
|
||||
$args['token'] = config('firefly.ipinfo_token');
|
||||
|
||||
// limit number of error mails that can be sent.
|
||||
if ($this->reachedLimit()) {
|
||||
Log::info('MailError: reached limit, not sending email.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->attempts() < 3 && '' !== $email) {
|
||||
try {
|
||||
\Mail::send(
|
||||
@@ -96,4 +105,62 @@ class MailError extends Job implements ShouldQueue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function reachedLimit(): bool
|
||||
{
|
||||
Log::debug('reachedLimit()');
|
||||
$types = [
|
||||
'5m' => ['limit' => 5, 'reset' => 5 * 60],
|
||||
'1h' => ['limit' => 15, 'reset' => 60 * 60],
|
||||
'24h' => ['limit' => 15, 'reset' => 24 * 60 * 60],
|
||||
];
|
||||
$file = storage_path('framework/cache/error-count.json');
|
||||
$directory = storage_path('framework/cache');
|
||||
$limits = [];
|
||||
|
||||
if (!is_writable($directory)) {
|
||||
Log::error(sprintf('MailError: cannot write to "%s", cannot rate limit errors!', $directory));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file_exists($file)) {
|
||||
Log::debug(sprintf('Wrote new file in "%s"', $file));
|
||||
file_put_contents($file, json_encode($limits, JSON_PRETTY_PRINT));
|
||||
}
|
||||
if (file_exists($file)) {
|
||||
Log::debug(sprintf('Read file in "%s"', $file));
|
||||
$limits = json_decode(file_get_contents($file), true);
|
||||
}
|
||||
// limit reached?
|
||||
foreach ($types as $type => $info) {
|
||||
Log::debug(sprintf('Now checking limit "%s"', $type), $info);
|
||||
if (!isset($limits[$type])) {
|
||||
Log::debug(sprintf('Limit "%s" reset to zero, did not exist yet.', $type));
|
||||
$limits[$type] = [
|
||||
'time' => time(),
|
||||
'sent' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
if (time() - $limits[$type]['time'] > $info['reset']) {
|
||||
Log::debug(sprintf('Time past for this limit is %d seconds, exceeding %d seconds. Reset to zero.', time() - $limits[$type]['time'], $info['reset']));
|
||||
$limits[$type] = [
|
||||
'time' => time(),
|
||||
'sent' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
if ($limits[$type]['sent'] > $info['limit']) {
|
||||
Log::warning(sprintf('Sent %d emails in %s, return true.', $limits[$type]['sent'], $type));
|
||||
|
||||
return true;
|
||||
}
|
||||
++$limits[$type]['sent'];
|
||||
}
|
||||
file_put_contents($file, json_encode($limits, JSON_PRETTY_PRINT));
|
||||
Log::debug('No limits reached, return FALSE.');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ class WarnAboutBills implements ShouldQueue
|
||||
$today = clone $this->date;
|
||||
$carbon = clone $bill->{$field};
|
||||
|
||||
return (int) $today->diffInDays($carbon);
|
||||
return (int)$today->diffInDays($carbon);
|
||||
}
|
||||
|
||||
private function sendWarning(Bill $bill, string $field): void
|
||||
|
||||
@@ -110,6 +110,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* @method static EloquentBuilder|Account whereUserGroupId($value)
|
||||
*
|
||||
* @property null|UserGroup $userGroup
|
||||
* @property mixed $account_id
|
||||
*
|
||||
* @mixin Eloquent
|
||||
*/
|
||||
|
||||
@@ -61,6 +61,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* @method static Builder|InvitedUser whereUpdatedAt($value)
|
||||
* @method static Builder|InvitedUser whereUserId($value)
|
||||
*
|
||||
* @property mixed $user_group_id
|
||||
*
|
||||
* @mixin Eloquent
|
||||
*/
|
||||
class InvitedUser extends Model
|
||||
|
||||
@@ -36,13 +36,13 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
/**
|
||||
* FireflyIII\Models\Preference
|
||||
*
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property int $user_id
|
||||
* @property string $name
|
||||
* @property null|array|int|string $data
|
||||
* @property User $user
|
||||
* @property int $id
|
||||
* @property null|Carbon $created_at
|
||||
* @property null|Carbon $updated_at
|
||||
* @property int $user_id
|
||||
* @property string $name
|
||||
* @property null|array|bool|int|string $data
|
||||
* @property User $user
|
||||
*
|
||||
* @method static Builder|Preference newModelQuery()
|
||||
* @method static Builder|Preference newQuery()
|
||||
@@ -54,6 +54,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* @method static Builder|Preference whereUpdatedAt($value)
|
||||
* @method static Builder|Preference whereUserId($value)
|
||||
*
|
||||
* @property mixed $user_group_id
|
||||
*
|
||||
* @mixin Eloquent
|
||||
*/
|
||||
class Preference extends Model
|
||||
@@ -79,22 +81,39 @@ class Preference extends Model
|
||||
{
|
||||
if (auth()->check()) {
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$user = auth()->user();
|
||||
|
||||
// some preferences do not have an administration ID.
|
||||
// some need it, to make sure the correct one is selected.
|
||||
$userGroupId = (int)$user->user_group_id;
|
||||
$userGroupId = 0 === $userGroupId ? null : $userGroupId;
|
||||
|
||||
/** @var null|Preference $preference */
|
||||
$preference = $user->preferences()->where('name', $value)->first();
|
||||
$preference = null;
|
||||
$items = config('firefly.admin_specific_prefs');
|
||||
if (null !== $userGroupId && in_array($value, $items, true)) {
|
||||
// find a preference with a specific user_group_id
|
||||
$preference = $user->preferences()->where('user_group_id', $userGroupId)->where('name', $value)->first();
|
||||
}
|
||||
if (!in_array($value, $items, true)) {
|
||||
// find any one.
|
||||
$preference = $user->preferences()->where('name', $value)->first();
|
||||
}
|
||||
|
||||
// try again with ID, but this time don't care about the preferred user_group_id
|
||||
if (null === $preference) {
|
||||
$preference = $user->preferences()->where('id', (int)$value)->first();
|
||||
}
|
||||
if (null !== $preference) {
|
||||
return $preference;
|
||||
}
|
||||
$default = config('firefly.default_preferences');
|
||||
$default = config('firefly.default_preferences');
|
||||
if (array_key_exists($value, $default)) {
|
||||
$preference = new self();
|
||||
$preference->name = $value;
|
||||
$preference->data = $default[$value];
|
||||
$preference->user_id = (int)$user->id;
|
||||
$preference = new self();
|
||||
$preference->name = $value;
|
||||
$preference->data = $default[$value];
|
||||
$preference->user_id = (int)$user->id;
|
||||
$preference->user_group_id = in_array($value, $items, true) ? $userGroupId : null;
|
||||
$preference->save();
|
||||
|
||||
return $preference;
|
||||
|
||||
@@ -81,6 +81,7 @@ use Illuminate\Database\Query\Builder;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|RecurrenceTransaction whereTransactionTypeId($value)
|
||||
*
|
||||
* @property null|TransactionType $transactionType
|
||||
* @property mixed $user_id
|
||||
*
|
||||
* @mixin Eloquent
|
||||
*/
|
||||
|
||||
@@ -98,7 +98,7 @@ class UserGroup extends Model
|
||||
{
|
||||
use ReturnsIntegerIdTrait;
|
||||
|
||||
protected $fillable = ['title'];
|
||||
protected $fillable = ['title', 'default_administration'];
|
||||
|
||||
/**
|
||||
* Route binder. Converts the key in the URL to the specified object (or throw 404).
|
||||
|
||||
@@ -25,10 +25,9 @@ namespace FireflyIII\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Laravel\Passport\Passport;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
/**
|
||||
* Class AppServiceProvider
|
||||
@@ -65,9 +64,17 @@ class AppServiceProvider extends ServiceProvider
|
||||
|
||||
return '';
|
||||
});
|
||||
Blade::if('partialroute', function (string $route) {
|
||||
$name = \Route::getCurrentRoute()->getName() ?? '';
|
||||
if (str_contains($name, $route)) {
|
||||
Blade::if('partialroute', function (string $route, string $firstParam = '') {
|
||||
$name = Route::getCurrentRoute()->getName() ?? '';
|
||||
if ('' === $firstParam && str_contains($name, $route)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var null|array $params */
|
||||
$params = Route::getCurrentRoute()->parameters();
|
||||
$params ??= [];
|
||||
$objectType = $params['objectType'] ?? '';
|
||||
if ($objectType === $firstParam && str_contains($name, $route)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -183,21 +183,21 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
// |-----------|
|
||||
// |----------------|
|
||||
if ($start->gte($limit->start_date) && $end->lte($limit->end_date)) {
|
||||
return (int) $start->diffInDays($end, true) + 1; // add one day
|
||||
return (int)$start->diffInDays($end, true) + 1; // add one day
|
||||
}
|
||||
// limit starts earlier and limit ends first:
|
||||
// |-----------|
|
||||
// |-------|
|
||||
if ($limit->start_date->lte($start) && $limit->end_date->lte($end)) {
|
||||
// return days in the range $start-$limit_end
|
||||
return (int) $start->diffInDays($limit->end_date, true) + 1; // add one day, the day itself
|
||||
return (int)$start->diffInDays($limit->end_date, true) + 1; // add one day, the day itself
|
||||
}
|
||||
// limit starts later and limit ends earlier
|
||||
// |-----------|
|
||||
// |-------|
|
||||
if ($limit->start_date->gte($start) && $limit->end_date->gte($end)) {
|
||||
// return days in the range $limit_start - $end
|
||||
return (int) $limit->start_date->diffInDays($end, true) + 1; // add one day, the day itself
|
||||
return (int)$limit->start_date->diffInDays($end, true) + 1; // add one day, the day itself
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -51,7 +51,7 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
$total = '0';
|
||||
$count = 0;
|
||||
foreach ($budget->budgetlimits as $limit) {
|
||||
$diff = (int) $limit->start_date->diffInDays($limit->end_date, true);
|
||||
$diff = (int)$limit->start_date->diffInDays($limit->end_date, true);
|
||||
$diff = 0 === $diff ? 1 : $diff;
|
||||
$amount = $limit->amount;
|
||||
$perDay = bcdiv($amount, (string)$diff);
|
||||
|
||||
@@ -301,7 +301,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
|
||||
if (null !== $piggyBank->targetdate && $repetition->currentamount < $piggyBank->targetamount) {
|
||||
$now = today(config('app.timezone'));
|
||||
$startDate = null !== $piggyBank->startdate && $piggyBank->startdate->gte($now) ? $piggyBank->startdate : $now;
|
||||
$diffInMonths = (int) $startDate->diffInMonths($piggyBank->targetdate);
|
||||
$diffInMonths = (int)$startDate->diffInMonths($piggyBank->targetdate);
|
||||
$remainingAmount = bcsub($piggyBank->targetamount, $repetition->currentamount);
|
||||
|
||||
// more than 1 month to go and still need money to save:
|
||||
|
||||
@@ -473,10 +473,10 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
if ('yearly' === $repetition->repetition_type) {
|
||||
$today = today(config('app.timezone'))->endOfYear();
|
||||
$repDate = Carbon::createFromFormat('Y-m-d', $repetition->repetition_moment);
|
||||
if (false === $repDate) {
|
||||
if (null === $repDate) {
|
||||
$repDate = clone $today;
|
||||
}
|
||||
$diffInYears = (int) $today->diffInYears($repDate, true);
|
||||
$diffInYears = (int)$today->diffInYears($repDate, true);
|
||||
$repDate->addYears($diffInYears); // technically not necessary.
|
||||
$string = $repDate->isoFormat((string)trans('config.month_and_day_no_year_js'));
|
||||
|
||||
|
||||
@@ -242,6 +242,30 @@ class UserRepository implements UserRepositoryInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getUserGroups(User $user): Collection
|
||||
{
|
||||
$memberships = $user->groupMemberships()->get();
|
||||
$set = [];
|
||||
$collection = new Collection();
|
||||
|
||||
/** @var GroupMembership $membership */
|
||||
foreach ($memberships as $membership) {
|
||||
/** @var null|UserGroup $group */
|
||||
$group = $membership->userGroup()->first();
|
||||
if (null !== $group) {
|
||||
$groupId = $group->id;
|
||||
if (in_array($groupId, array_keys($set), true)) {
|
||||
continue;
|
||||
}
|
||||
$set[$groupId] = $group;
|
||||
}
|
||||
}
|
||||
$collection->push(...$set);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function inviteUser(null|Authenticatable|User $user, string $email): InvitedUser
|
||||
{
|
||||
$now = today(config('app.timezone'));
|
||||
|
||||
@@ -94,6 +94,8 @@ interface UserRepositoryInterface
|
||||
*/
|
||||
public function getUserData(User $user): array;
|
||||
|
||||
public function getUserGroups(User $user): Collection;
|
||||
|
||||
public function hasRole(null|Authenticatable|User $user, string $role): bool;
|
||||
|
||||
public function inviteUser(null|Authenticatable|User $user, string $email): InvitedUser;
|
||||
|
||||
@@ -98,6 +98,7 @@ class UserGroupRepository implements UserGroupRepositoryInterface
|
||||
public function get(): Collection
|
||||
{
|
||||
$collection = new Collection();
|
||||
$set = [];
|
||||
$memberships = $this->user->groupMemberships()->get();
|
||||
|
||||
/** @var GroupMembership $membership */
|
||||
@@ -105,9 +106,14 @@ class UserGroupRepository implements UserGroupRepositoryInterface
|
||||
/** @var null|UserGroup $group */
|
||||
$group = $membership->userGroup()->first();
|
||||
if (null !== $group) {
|
||||
$collection->push($group);
|
||||
$groupId = $group->id;
|
||||
if (in_array($groupId, array_keys($set), true)) {
|
||||
continue;
|
||||
}
|
||||
$set[$groupId] = $group;
|
||||
}
|
||||
}
|
||||
$collection->push(...$set);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
@@ -282,4 +288,11 @@ class UserGroupRepository implements UserGroupRepositoryInterface
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function useUserGroup(UserGroup $userGroup): void
|
||||
{
|
||||
$this->user->user_group_id = $userGroup->id;
|
||||
$this->user->save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,4 +47,6 @@ interface UserGroupRepositoryInterface
|
||||
public function update(UserGroup $userGroup, array $data): UserGroup;
|
||||
|
||||
public function updateMembership(UserGroup $userGroup, array $data): UserGroup;
|
||||
|
||||
public function useUserGroup(UserGroup $userGroup): void;
|
||||
}
|
||||
|
||||
@@ -240,15 +240,19 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
|
||||
public function getAccountsByType(array $types, ?array $sort = []): Collection
|
||||
{
|
||||
$sortable = ['name', 'active']; // TODO yes this is a duplicate array.
|
||||
$res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types);
|
||||
$query = $this->userGroup->accounts();
|
||||
$sortable = ['name', 'active']; // TODO yes this is a duplicate array.
|
||||
$res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types);
|
||||
$query = $this->userGroup->accounts();
|
||||
if (0 !== count($types)) {
|
||||
$query->accountTypeIn($types);
|
||||
}
|
||||
|
||||
// add sort parameters. At this point they're filtered to allowed fields to sort by:
|
||||
$hasActiveColumn = array_key_exists('active', $sort);
|
||||
if (count($sort) > 0) {
|
||||
if (false === $hasActiveColumn) {
|
||||
$query->orderBy('accounts.active', 'DESC');
|
||||
}
|
||||
foreach ($sort as $column => $direction) {
|
||||
if (in_array($column, $sortable, true)) {
|
||||
$query->orderBy(sprintf('accounts.%s', $column), $direction);
|
||||
@@ -258,9 +262,9 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
|
||||
if (0 === count($sort)) {
|
||||
if (0 !== count($res)) {
|
||||
$query->orderBy('accounts.order', 'ASC');
|
||||
$query->orderBy('accounts.active', 'DESC');
|
||||
}
|
||||
$query->orderBy('accounts.active', 'DESC');
|
||||
$query->orderBy('accounts.order', 'ASC');
|
||||
$query->orderBy('accounts.name', 'ASC');
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,8 @@ interface AccountRepositoryInterface
|
||||
*/
|
||||
public function getMetaValue(Account $account, string $field): ?string;
|
||||
|
||||
public function getUserGroup(): UserGroup;
|
||||
|
||||
/**
|
||||
* Reset order types of the mentioned accounts.
|
||||
*/
|
||||
|
||||
63
app/Rules/IsDefaultUserGroupName.php
Normal file
63
app/Rules/IsDefaultUserGroupName.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* IsTransferAccount.php
|
||||
* Copyright (c) 2020 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Rules;
|
||||
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
/**
|
||||
* Class IsDefaultUserGroupName
|
||||
*/
|
||||
class IsDefaultUserGroupName implements ValidationRule
|
||||
{
|
||||
private UserGroup $userGroup;
|
||||
|
||||
public function __construct(UserGroup $userGroup)
|
||||
{
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, \Closure $fail): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s(%s)', __METHOD__, $value));
|
||||
|
||||
// are you owner of this group and the name is the same? fail.
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
/** @var UserRepositoryInterface $userRepos */
|
||||
$userRepos = app(UserRepositoryInterface::class);
|
||||
|
||||
$roles = $userRepos->getRolesInGroup($user, $this->userGroup->id);
|
||||
if ($this->userGroup->title === $user->email && in_array('owner', $roles, true)) {
|
||||
$fail('validation.administration_owner_rename')->translate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,7 @@ class UpdateRequest implements UpdateRequestInterface
|
||||
// parse response a bit. No message yet.
|
||||
$response = $json['firefly_iii'][$channel];
|
||||
$date = Carbon::createFromFormat('Y-m-d', $response['date']);
|
||||
if (false === $date) {
|
||||
if (null === $date) {
|
||||
$date = today(config('app.timezone'));
|
||||
}
|
||||
$return['version'] = $response['version'];
|
||||
|
||||
@@ -54,7 +54,7 @@ class GroupCloneService
|
||||
{
|
||||
$newJournal = $journal->replicate();
|
||||
$newJournal->transaction_group_id = $newGroup->id;
|
||||
$newJournal->date = today(config('app.timezone'));
|
||||
$newJournal->date = now();
|
||||
$newJournal->save();
|
||||
|
||||
foreach ($journal->transactions as $transaction) {
|
||||
|
||||
@@ -187,7 +187,7 @@ class JournalUpdateService
|
||||
|
||||
// make new account validator.
|
||||
$expectedType = $this->getExpectedType();
|
||||
app('log')->debug(sprintf('Expected type (new or unchanged) is %s', $expectedType));
|
||||
app('log')->debug(sprintf('(a) Expected type (new or unchanged) is %s', $expectedType));
|
||||
|
||||
// make a new validator.
|
||||
/** @var AccountValidator $validator */
|
||||
@@ -273,7 +273,7 @@ class JournalUpdateService
|
||||
|
||||
// make new account validator.
|
||||
$expectedType = $this->getExpectedType();
|
||||
app('log')->debug(sprintf('Expected type (new or unchanged) is %s', $expectedType));
|
||||
app('log')->debug(sprintf('(b) Expected type (new or unchanged) is %s', $expectedType));
|
||||
|
||||
// make a new validator.
|
||||
/** @var AccountValidator $validator */
|
||||
@@ -404,7 +404,7 @@ class JournalUpdateService
|
||||
|
||||
// make new account validator.
|
||||
$expectedType = $this->getExpectedType();
|
||||
app('log')->debug(sprintf('Expected type (new or unchanged) is %s', $expectedType));
|
||||
app('log')->debug(sprintf('(c) Expected type (new or unchanged) is %s', $expectedType));
|
||||
|
||||
try {
|
||||
$result = $this->getAccount($expectedType, 'destination', $destInfo);
|
||||
|
||||
@@ -146,7 +146,7 @@ class RemoteUserGuard implements Guard
|
||||
return $this->user?->id;
|
||||
}
|
||||
|
||||
public function setUser(null|Authenticatable|User $user): void
|
||||
public function setUser(null|Authenticatable|User $user): void // @phpstan-ignore-line
|
||||
{
|
||||
app('log')->debug(sprintf('Now at %s', __METHOD__));
|
||||
if ($user instanceof User) {
|
||||
|
||||
@@ -36,6 +36,14 @@ use Illuminate\Contracts\Auth\UserProvider;
|
||||
*/
|
||||
class RemoteUserProvider implements UserProvider
|
||||
{
|
||||
#[\Override]
|
||||
public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now at %s', __METHOD__));
|
||||
|
||||
throw new FireflyException(sprintf('Did not implement %s', __METHOD__));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*
|
||||
@@ -120,12 +128,4 @@ class RemoteUserProvider implements UserProvider
|
||||
|
||||
throw new FireflyException(sprintf('C) Did not implement %s', __METHOD__));
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now at %s', __METHOD__));
|
||||
|
||||
throw new FireflyException(sprintf('Did not implement %s', __METHOD__));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ class EitherConfigKey
|
||||
{
|
||||
public static array $static
|
||||
= [
|
||||
// currency conversion
|
||||
'cer.enabled',
|
||||
|
||||
// firefly iii settings
|
||||
'firefly.version',
|
||||
'firefly.api_version',
|
||||
'firefly.default_location',
|
||||
|
||||
@@ -23,10 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\Http\Api;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Models\GroupMembership;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
@@ -35,37 +37,68 @@ use Illuminate\Http\Request;
|
||||
trait ValidatesUserGroupTrait
|
||||
{
|
||||
/**
|
||||
* This check does not validate which rights the user has, that comes later.
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @throws AuthorizationException
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
protected function validateUserGroup(Request $request): ?UserGroup
|
||||
protected function validateUserGroup(Request $request): UserGroup
|
||||
{
|
||||
app('log')->debug(sprintf('validateUserGroup: %s', static::class));
|
||||
if (!auth()->check()) {
|
||||
app('log')->debug('validateUserGroup: user is not logged in, return NULL.');
|
||||
|
||||
return null;
|
||||
throw new AuthenticationException();
|
||||
}
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$groupId = 0;
|
||||
if (!$request->has('user_group_id')) {
|
||||
$group = $user->userGroup;
|
||||
app('log')->debug(sprintf('validateUserGroup: no user group submitted, return default group #%d.', $group?->id));
|
||||
|
||||
return $group;
|
||||
$groupId = $user->user_group_id;
|
||||
app('log')->debug(sprintf('validateUserGroup: no user group submitted, use default group #%d.', $groupId));
|
||||
}
|
||||
if ($request->has('user_group_id')) {
|
||||
$groupId = (int)$request->get('user_group_id');
|
||||
app('log')->debug(sprintf('validateUserGroup: user group submitted, search for memberships in group #%d.', $groupId));
|
||||
}
|
||||
$groupId = (int)$request->get('user_group_id');
|
||||
|
||||
/** @var null|GroupMembership $membership */
|
||||
$membership = $user->groupMemberships()->where('user_group_id', $groupId)->first();
|
||||
|
||||
if (null === $membership) {
|
||||
app('log')->debug('validateUserGroup: user has no access to this group.');
|
||||
app('log')->debug(sprintf('validateUserGroup: user has no access to group #%d.', $groupId));
|
||||
|
||||
throw new FireflyException((string)trans('validation.belongs_user_or_user_group'));
|
||||
throw new AuthorizationException((string)trans('validation.no_access_group'));
|
||||
}
|
||||
app('log')->debug(sprintf('validateUserGroup: user has role "%s" in group #%d.', $membership->userRole->title, $membership->userGroup->id));
|
||||
|
||||
return $membership->userGroup;
|
||||
// need to get the group from the membership:
|
||||
/** @var null|UserGroup $group */
|
||||
$group = $membership->userGroup;
|
||||
if (null === $group) {
|
||||
app('log')->debug(sprintf('validateUserGroup: group #%d does not exist.', $groupId));
|
||||
|
||||
throw new AuthorizationException((string)trans('validation.belongs_user_or_user_group'));
|
||||
}
|
||||
app('log')->debug(sprintf('validateUserGroup: validate access of user to group #%d ("%s").', $groupId, $group->title));
|
||||
$roles = property_exists($this, 'acceptedRoles') ? $this->acceptedRoles : [];
|
||||
if (0 === count($roles)) {
|
||||
app('log')->debug('validateUserGroup: no roles defined, so no access.');
|
||||
|
||||
throw new AuthorizationException((string)trans('validation.no_accepted_roles_defined'));
|
||||
}
|
||||
app('log')->debug(sprintf('validateUserGroup: have %d roles to check.', count($roles)), $roles);
|
||||
|
||||
/** @var UserRoleEnum $role */
|
||||
foreach ($roles as $role) {
|
||||
if ($user->hasRoleInGroupOrOwner($group, $role)) {
|
||||
app('log')->debug(sprintf('validateUserGroup: User has role "%s" in group #%d, return the group.', $role->value, $groupId));
|
||||
|
||||
return $group;
|
||||
}
|
||||
app('log')->debug(sprintf('validateUserGroup: User does NOT have role "%s" in group #%d, continue searching.', $role->value, $groupId));
|
||||
}
|
||||
|
||||
app('log')->debug('validateUserGroup: User does NOT have enough rights to access endpoint.');
|
||||
|
||||
throw new AuthorizationException((string)trans('validation.belongs_user_or_user_group'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ trait DateCalculation
|
||||
$difference = $today->diffInDays($end);
|
||||
}
|
||||
|
||||
return (int) (0 === $difference ? 1 : $difference);
|
||||
return (int)(0 === $difference ? 1 : $difference);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +63,7 @@ trait DateCalculation
|
||||
$difference = $start->diffInDays($today, true) + 1;
|
||||
}
|
||||
|
||||
return (int) $difference;
|
||||
return (int)$difference;
|
||||
}
|
||||
|
||||
protected function calculateStep(Carbon $start, Carbon $end): string
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace FireflyIII\Support\Http\Controllers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Trait GetConfigurationData
|
||||
@@ -82,6 +83,8 @@ trait GetConfigurationData
|
||||
{
|
||||
$viewRange = app('navigation')->getViewRange(false);
|
||||
|
||||
Log::debug(sprintf('dateRange: the view range is "%s"', $viewRange));
|
||||
|
||||
/** @var Carbon $start */
|
||||
$start = session('start');
|
||||
|
||||
@@ -97,6 +100,7 @@ trait GetConfigurationData
|
||||
// first range is the current range:
|
||||
$title => [$start, $end],
|
||||
];
|
||||
Log::debug(sprintf('dateRange: the date range in the session is"%s" - "%s"', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
|
||||
// when current range is a custom range, add the current period as the next range.
|
||||
if ($isCustom) {
|
||||
|
||||
@@ -145,14 +145,14 @@ trait RequestInformation
|
||||
$attributes['location'] ??= '';
|
||||
$attributes['accounts'] = AccountList::routeBinder($attributes['accounts'] ?? '', new Route('get', '', []));
|
||||
$date = Carbon::createFromFormat('Ymd', $attributes['startDate']);
|
||||
if (false === $date) {
|
||||
if (null === $date) {
|
||||
$date = today(config('app.timezone'));
|
||||
}
|
||||
$date->startOfMonth();
|
||||
$attributes['startDate'] = $date;
|
||||
|
||||
$date2 = Carbon::createFromFormat('Ymd', $attributes['endDate']);
|
||||
if (false === $date2) {
|
||||
if (null === $date2) {
|
||||
$date2 = today(config('app.timezone'));
|
||||
}
|
||||
$date2->endOfDay();
|
||||
|
||||
@@ -188,7 +188,7 @@ class Navigation
|
||||
Log::debug(sprintf('Function is ->%s()', $function));
|
||||
if (array_key_exists($function, $parameterMap)) {
|
||||
Log::debug(sprintf('Parameter map, function becomes ->%s(%s)', $function, implode(', ', $parameterMap[$function])));
|
||||
$date->{$function}($parameterMap[$function][0]);
|
||||
$date->{$function}($parameterMap[$function][0]); // @phpstan-ignore-line
|
||||
Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
|
||||
|
||||
return $date;
|
||||
@@ -248,9 +248,9 @@ class Navigation
|
||||
'1M' => 'addMonth',
|
||||
'month' => 'addMonth',
|
||||
'monthly' => 'addMonth',
|
||||
'3M' => 'addMonths',
|
||||
'quarter' => 'addMonths',
|
||||
'quarterly' => 'addMonths',
|
||||
'3M' => 'addQuarter',
|
||||
'quarter' => 'addQuarter',
|
||||
'quarterly' => 'addQuarter',
|
||||
'6M' => 'addMonths',
|
||||
'half-year' => 'addMonths',
|
||||
'half_year' => 'addMonths',
|
||||
@@ -258,7 +258,7 @@ class Navigation
|
||||
'yearly' => 'addYear',
|
||||
'1Y' => 'addYear',
|
||||
];
|
||||
$modifierMap = ['quarter' => 3, '3M' => 3, 'quarterly' => 3, 'half-year' => 6, 'half_year' => 6, '6M' => 6];
|
||||
$modifierMap = ['half-year' => 6, 'half_year' => 6, '6M' => 6];
|
||||
$subDay = ['week', 'weekly', '1W', 'month', 'monthly', '1M', '3M', 'quarter', 'quarterly', '6M', 'half-year', 'half_year', '1Y', 'year', 'yearly'];
|
||||
|
||||
if ('custom' === $repeatFreq) {
|
||||
@@ -274,7 +274,7 @@ class Navigation
|
||||
|
||||
/** @var Carbon $tEnd */
|
||||
$tEnd = session('end', today(config('app.timezone'))->endOfMonth());
|
||||
$diffInDays = (int) $tStart->diffInDays($tEnd, true);
|
||||
$diffInDays = (int)$tStart->diffInDays($tEnd, true);
|
||||
}
|
||||
Log::debug(sprintf('Diff in days is %d', $diffInDays));
|
||||
$currentEnd->addDays($diffInDays);
|
||||
@@ -327,7 +327,7 @@ class Navigation
|
||||
{
|
||||
$endOfMonth = $date->copy()->endOfMonth();
|
||||
|
||||
return (int) $date->diffInDays($endOfMonth, true);
|
||||
return (int)$date->diffInDays($endOfMonth, true);
|
||||
}
|
||||
|
||||
public function diffInPeriods(string $period, int $skip, Carbon $beginning, Carbon $end): int
|
||||
@@ -677,7 +677,7 @@ class Navigation
|
||||
|
||||
/** @var Carbon $tEnd */
|
||||
$tEnd = session('end', today(config('app.timezone'))->endOfMonth());
|
||||
$diffInDays = (int) $tStart->diffInDays($tEnd, true);
|
||||
$diffInDays = (int)$tStart->diffInDays($tEnd, true);
|
||||
$date->subDays($diffInDays * $subtract);
|
||||
|
||||
return $date;
|
||||
|
||||
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Support;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterface;
|
||||
use Carbon\Exceptions\InvalidFormatException;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -122,12 +123,12 @@ class ParseDateString
|
||||
{
|
||||
$today = today(config('app.timezone'))->startOfDay();
|
||||
|
||||
return match ($keyword) {
|
||||
return match ($keyword) { // @phpstan-ignore-line
|
||||
default => $today,
|
||||
'yesterday' => $today->subDay(),
|
||||
'tomorrow' => $today->addDay(),
|
||||
'start of this week' => $today->startOfWeek(Carbon::MONDAY),
|
||||
'end of this week' => $today->endOfWeek(Carbon::SUNDAY),
|
||||
'start of this week' => $today->startOfWeek(CarbonInterface::MONDAY),
|
||||
'end of this week' => $today->endOfWeek(CarbonInterface::SUNDAY),
|
||||
'start of this month' => $today->startOfMonth(),
|
||||
'end of this month' => $today->endOfMonth(),
|
||||
'start of this quarter' => $today->startOfQuarter(),
|
||||
|
||||
@@ -26,7 +26,10 @@ namespace FireflyIII\Support;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
/**
|
||||
* Class Preferences.
|
||||
@@ -40,20 +43,17 @@ class Preferences
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
return Preference::where('user_id', $user->id)->get();
|
||||
return Preference::where('user_id', $user->id)
|
||||
->where(function (Builder $q) use ($user): void {
|
||||
$q->whereNull('user_group_id');
|
||||
$q->orWhere('user_group_id', $user->user_group_id);
|
||||
})
|
||||
->get()
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $default
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function get(string $name, $default = null): ?Preference
|
||||
public function get(string $name, null|array|bool|int|string $default = null): ?Preference
|
||||
{
|
||||
if ('currencyPreference' === $name) {
|
||||
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
|
||||
}
|
||||
|
||||
/** @var null|User $user */
|
||||
$user = auth()->user();
|
||||
if (null === $user) {
|
||||
@@ -66,15 +66,12 @@ class Preferences
|
||||
return $this->getForUser($user, $name, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getForUser(User $user, string $name, null|array|bool|int|string $default = null): ?Preference
|
||||
{
|
||||
if ('currencyPreference' === $name) {
|
||||
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
|
||||
}
|
||||
$preference = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'user_id', 'name', 'data', 'updated_at', 'created_at']);
|
||||
// don't care about user group ID, except for some specific preferences.
|
||||
$userGroupId = $this->getUserGroupId($user, $name);
|
||||
$preference = Preference::where('user_group_id', $userGroupId)->where('user_id', $user->id)->where('name', $name)->first(['id', 'user_id', 'name', 'data', 'updated_at', 'created_at']);
|
||||
|
||||
if (null !== $preference && null === $preference->data) {
|
||||
$preference->delete();
|
||||
$preference = null;
|
||||
@@ -92,17 +89,22 @@ class Preferences
|
||||
return $this->setForUser($user, $name, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getUserGroupId(User $user, string $preferenceName): ?int
|
||||
{
|
||||
$groupId = null;
|
||||
$items = config('firefly.admin_specific_prefs') ?? [];
|
||||
if (in_array($preferenceName, $items, true)) {
|
||||
$groupId = (int)$user->user_group_id;
|
||||
}
|
||||
|
||||
return $groupId;
|
||||
}
|
||||
|
||||
public function delete(string $name): bool
|
||||
{
|
||||
if ('currencyPreference' === $name) {
|
||||
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
|
||||
}
|
||||
$fullName = sprintf('preference%s%s', auth()->user()->id, $name);
|
||||
if (\Cache::has($fullName)) {
|
||||
\Cache::forget($fullName);
|
||||
if (Cache::has($fullName)) {
|
||||
Cache::forget($fullName);
|
||||
}
|
||||
Preference::where('user_id', auth()->user()->id)->where('name', $name)->delete();
|
||||
|
||||
@@ -111,29 +113,19 @@ class Preferences
|
||||
|
||||
public function forget(User $user, string $name): void
|
||||
{
|
||||
if ('currencyPreference' === $name) {
|
||||
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
|
||||
}
|
||||
$key = sprintf('preference%s%s', $user->id, $name);
|
||||
\Cache::forget($key);
|
||||
\Cache::put($key, '', 5);
|
||||
Cache::forget($key);
|
||||
Cache::put($key, '', 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function setForUser(User $user, string $name, $value): Preference
|
||||
public function setForUser(User $user, string $name, null|array|bool|int|string $value): Preference
|
||||
{
|
||||
if ('currencyPreference' === $name) {
|
||||
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
|
||||
}
|
||||
$fullName = sprintf('preference%s%s', $user->id, $name);
|
||||
\Cache::forget($fullName);
|
||||
$groupId = $this->getUserGroupId($user, $name);
|
||||
Cache::forget($fullName);
|
||||
|
||||
/** @var null|Preference $pref */
|
||||
$pref = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data', 'updated_at', 'created_at']);
|
||||
$pref = Preference::where('user_group_id', $groupId)->where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data', 'updated_at', 'created_at']);
|
||||
|
||||
if (null !== $pref && null === $value) {
|
||||
$pref->delete();
|
||||
@@ -144,18 +136,14 @@ class Preferences
|
||||
return new Preference();
|
||||
}
|
||||
if (null === $pref) {
|
||||
$pref = new Preference();
|
||||
$pref->user_id = (int)$user->id;
|
||||
$pref->name = $name;
|
||||
$pref = new Preference();
|
||||
$pref->user_id = (int)$user->id;
|
||||
$pref->user_group_id = $groupId;
|
||||
$pref->name = $name;
|
||||
}
|
||||
$pref->data = $value;
|
||||
|
||||
try {
|
||||
$pref->save();
|
||||
} catch (\PDOException $e) {
|
||||
throw new FireflyException(sprintf('Could not save preference: %s', $e->getMessage()), 0, $e);
|
||||
}
|
||||
\Cache::forever($fullName, $pref);
|
||||
$pref->save();
|
||||
Cache::forever($fullName, $pref);
|
||||
|
||||
return $pref;
|
||||
}
|
||||
@@ -165,19 +153,25 @@ class Preferences
|
||||
return Preference::where('user_id', $user->id)->where('name', 'LIKE', $search.'%')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find by name, has no user ID in it, because the method is called from an unauthenticated route any way.
|
||||
*/
|
||||
public function findByName(string $name): Collection
|
||||
{
|
||||
if ('currencyPreference' === $name) {
|
||||
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
|
||||
}
|
||||
|
||||
return Preference::where('name', $name)->get();
|
||||
}
|
||||
|
||||
public function getArrayForUser(User $user, array $list): array
|
||||
{
|
||||
$result = [];
|
||||
$preferences = Preference::where('user_id', $user->id)->whereIn('name', $list)->get(['id', 'name', 'data']);
|
||||
$preferences = Preference::where('user_id', $user->id)
|
||||
->where(function (Builder $q) use ($user): void {
|
||||
$q->whereNull('user_group_id');
|
||||
$q->orWhere('user_group_id', $user->user_group_id);
|
||||
})
|
||||
->whereIn('name', $list)
|
||||
->get(['id', 'name', 'data'])
|
||||
;
|
||||
|
||||
/** @var Preference $preference */
|
||||
foreach ($preferences as $preference) {
|
||||
@@ -192,17 +186,8 @@ class Preferences
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $default
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getFresh(string $name, $default = null): ?Preference
|
||||
public function getFresh(string $name, null|array|bool|int|string $default = null): ?Preference
|
||||
{
|
||||
if ('currencyPreference' === $name) {
|
||||
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
|
||||
}
|
||||
|
||||
/** @var null|User $user */
|
||||
$user = auth()->user();
|
||||
if (null === $user) {
|
||||
@@ -212,22 +197,6 @@ class Preferences
|
||||
return $preference;
|
||||
}
|
||||
|
||||
return $this->getFreshForUser($user, $name, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO remove me.
|
||||
*
|
||||
* @param null $default
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getFreshForUser(User $user, string $name, $default = null): ?Preference
|
||||
{
|
||||
if ('currencyPreference' === $name) {
|
||||
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
|
||||
}
|
||||
|
||||
return $this->getForUser($user, $name, $default);
|
||||
}
|
||||
|
||||
@@ -252,19 +221,12 @@ class Preferences
|
||||
public function mark(): void
|
||||
{
|
||||
$this->set('lastActivity', microtime());
|
||||
\Session::forget('first');
|
||||
Session::forget('first');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function set(string $name, $value): Preference
|
||||
public function set(string $name, null|array|bool|int|string $value): Preference
|
||||
{
|
||||
if ('currencyPreference' === $name) {
|
||||
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
|
||||
}
|
||||
/** @var null|User $user */
|
||||
$user = auth()->user();
|
||||
if (null === $user) {
|
||||
// make new preference, return it:
|
||||
@@ -275,6 +237,6 @@ class Preferences
|
||||
return $pref;
|
||||
}
|
||||
|
||||
return $this->setForUser(auth()->user(), $name, $value);
|
||||
return $this->setForUser($user, $name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ trait ConvertsDataTypes
|
||||
|
||||
return null;
|
||||
}
|
||||
if (false === $carbon) {
|
||||
if (null === $carbon) {
|
||||
app('log')->error(sprintf('[2] "%s" is of an invalid format.', $value));
|
||||
|
||||
return null;
|
||||
|
||||
@@ -177,7 +177,7 @@ class OperatorQuerySearch implements SearchInterface
|
||||
default:
|
||||
app('log')->error(sprintf('Cannot handle node %s', $class));
|
||||
|
||||
throw new FireflyException(sprintf('Firefly III search cant handle "%s"-nodes', $class));
|
||||
throw new FireflyException(sprintf('Firefly III search can\'t handle "%s"-nodes', $class));
|
||||
|
||||
case Subquery::class:
|
||||
// loop all notes in subquery:
|
||||
|
||||
@@ -29,7 +29,7 @@ use Symfony\Component\ExpressionLanguage\SyntaxError;
|
||||
|
||||
class ActionExpression
|
||||
{
|
||||
private static array $NAMES
|
||||
private static array $NAMES
|
||||
= [
|
||||
// 'transaction_group_id',
|
||||
// 'user_id',
|
||||
|
||||
@@ -185,7 +185,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
// try classic date:
|
||||
if (10 === strlen($monthlyPaymentDate)) {
|
||||
$object = Carbon::createFromFormat('!Y-m-d', $monthlyPaymentDate, config('app.timezone'));
|
||||
if (false === $object) {
|
||||
if (null === $object) {
|
||||
$object = today(config('app.timezone'));
|
||||
}
|
||||
$monthlyPaymentDate = $object->toAtomString();
|
||||
@@ -212,7 +212,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
}
|
||||
if (null !== $openingBalanceDate) {
|
||||
$object = Carbon::createFromFormat('Y-m-d H:i:s', $openingBalanceDate, config('app.timezone'));
|
||||
if (false === $object) {
|
||||
if (null === $object) {
|
||||
$object = today(config('app.timezone'));
|
||||
}
|
||||
$openingBalanceDate = $object->toAtomString();
|
||||
|
||||
@@ -82,7 +82,7 @@ class BillTransformer extends AbstractTransformer
|
||||
$payDatesFormatted = [];
|
||||
foreach ($paidData as $object) {
|
||||
$date = Carbon::createFromFormat('!Y-m-d', $object['date'], config('app.timezone'));
|
||||
if (false === $date) {
|
||||
if (null === $date) {
|
||||
$date = today(config('app.timezone'));
|
||||
}
|
||||
$object['date'] = $date->toAtomString();
|
||||
@@ -91,7 +91,7 @@ class BillTransformer extends AbstractTransformer
|
||||
|
||||
foreach ($payDates as $string) {
|
||||
$date = Carbon::createFromFormat('!Y-m-d', $string, config('app.timezone'));
|
||||
if (false === $date) {
|
||||
if (null === $date) {
|
||||
$date = today(config('app.timezone'));
|
||||
}
|
||||
$payDatesFormatted[] = $date->toAtomString();
|
||||
@@ -104,13 +104,16 @@ class BillTransformer extends AbstractTransformer
|
||||
|
||||
if (null !== $firstPayDate) {
|
||||
$nemDate = Carbon::createFromFormat('!Y-m-d', $firstPayDate, config('app.timezone'));
|
||||
if (false === $nemDate) {
|
||||
if (null === $nemDate) {
|
||||
$nemDate = today(config('app.timezone'));
|
||||
}
|
||||
$nem = $nemDate->toAtomString();
|
||||
|
||||
// nullify again when it's outside the current view range.
|
||||
if ($nemDate->lt($this->parameters->get('start')) || $nemDate->gt($this->parameters->get('end'))) {
|
||||
if (
|
||||
(null !== $this->parameters->get('start') && $nemDate->lt($this->parameters->get('start')))
|
||||
|| (null !== $this->parameters->get('end') && $nemDate->gt($this->parameters->get('end')))
|
||||
) {
|
||||
$nem = null;
|
||||
$nemDate = null;
|
||||
$firstPayDate = null;
|
||||
@@ -126,7 +129,7 @@ class BillTransformer extends AbstractTransformer
|
||||
$current = $payDatesFormatted[0] ?? null;
|
||||
if (null !== $current && !$nemDate->isToday()) {
|
||||
$temp2 = Carbon::createFromFormat('Y-m-d\TH:i:sP', $current);
|
||||
if (false === $temp2) {
|
||||
if (null === $temp2) {
|
||||
$temp2 = today(config('app.timezone'));
|
||||
}
|
||||
$nemDiff = trans('firefly.bill_expected_date', ['date' => $temp2->diffForHumans(today(config('app.timezone')), CarbonInterface::DIFF_RELATIVE_TO_NOW)]);
|
||||
|
||||
@@ -35,12 +35,15 @@ class PreferenceTransformer extends AbstractTransformer
|
||||
*/
|
||||
public function transform(Preference $preference): array
|
||||
{
|
||||
$userGroupId = 0 === $preference->user_group_id ? null : $preference->user_group_id;
|
||||
|
||||
return [
|
||||
'id' => $preference->id,
|
||||
'created_at' => $preference->created_at->toAtomString(),
|
||||
'updated_at' => $preference->updated_at->toAtomString(),
|
||||
'name' => $preference->name,
|
||||
'data' => $preference->data,
|
||||
'id' => $preference->id,
|
||||
'created_at' => $preference->created_at->toAtomString(),
|
||||
'updated_at' => $preference->updated_at->toAtomString(),
|
||||
'user_group_id' => $userGroupId,
|
||||
'name' => $preference->name,
|
||||
'data' => $preference->data,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,18 @@ class AccountTransformer extends AbstractTransformer
|
||||
return $rightBalance <=> $leftBalance;
|
||||
});
|
||||
}
|
||||
if ('last_activity' === $column) {
|
||||
$dates = $this->lastActivity;
|
||||
$objects = $objects->sort(function (Account $left, Account $right) use ($dates, $direction) {
|
||||
$leftDate = $dates[$left->id] ?? Carbon::create(1900, 1, 1, 0, 0, 0);
|
||||
$rightDate = $dates[$right->id] ?? Carbon::create(1900, 1, 1, 0, 0, 0);
|
||||
if ('asc' === $direction) {
|
||||
return $leftDate->gt($rightDate) ? 1 : -1;
|
||||
}
|
||||
|
||||
return $rightDate->gt($leftDate) ? 1 : -1;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +180,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
|
||||
// no currency? use default
|
||||
$currency = $this->default;
|
||||
if (array_key_exists($id, $this->accountMeta) && 0 !== (int)$this->accountMeta[$id]['currency_id']) {
|
||||
if (array_key_exists($id, $this->accountMeta) && 0 !== (int)($this->accountMeta[$id]['currency_id'] ?? 0)) {
|
||||
$currency = $this->currencies[(int)$this->accountMeta[$id]['currency_id']];
|
||||
}
|
||||
// amounts and calculation.
|
||||
|
||||
@@ -248,7 +248,7 @@ class PiggyBankTransformer extends AbstractTransformer
|
||||
if (bccomp($currentAmount, $targetAmount) < 1) {
|
||||
$now = today(config('app.timezone'));
|
||||
$startDate = null !== $startDate && $startDate->gte($now) ? $startDate : $now;
|
||||
$diffInMonths = (int) $startDate->diffInMonths($targetDate);
|
||||
$diffInMonths = (int)$startDate->diffInMonths($targetDate);
|
||||
$remainingAmount = bcsub($targetAmount, $currentAmount);
|
||||
|
||||
// more than 1 month to go and still need money to save:
|
||||
|
||||
@@ -462,7 +462,7 @@ class TransactionGroupTransformer extends AbstractTransformer
|
||||
// app('log')->debug(sprintf('Now in date("%s")', $string));
|
||||
if (10 === strlen($string)) {
|
||||
$res = Carbon::createFromFormat('Y-m-d', $string, config('app.timezone'));
|
||||
if (false === $res) {
|
||||
if (null === $res) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -473,7 +473,7 @@ class TransactionGroupTransformer extends AbstractTransformer
|
||||
}
|
||||
if (19 === strlen($string) && str_contains($string, 'T')) {
|
||||
$res = Carbon::createFromFormat('Y-m-d\TH:i:s', substr($string, 0, 19), config('app.timezone'));
|
||||
if (false === $res) {
|
||||
if (null === $res) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ class TransactionGroupTransformer extends AbstractTransformer
|
||||
|
||||
// 2022-01-01 01:01:01
|
||||
$res = Carbon::createFromFormat('Y-m-d H:i:s', substr($string, 0, 19), config('app.timezone'));
|
||||
if (false === $res) {
|
||||
if (null === $res) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,11 +35,15 @@ use Illuminate\Support\Collection;
|
||||
*/
|
||||
class UserGroupTransformer extends AbstractTransformer
|
||||
{
|
||||
private array $inUse;
|
||||
private array $memberships;
|
||||
private array $membershipsVisible;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->memberships = [];
|
||||
$this->memberships = [];
|
||||
$this->membershipsVisible = [];
|
||||
$this->inUse = [];
|
||||
}
|
||||
|
||||
public function collectMetaData(Collection $objects): Collection
|
||||
@@ -51,8 +55,10 @@ class UserGroupTransformer extends AbstractTransformer
|
||||
|
||||
/** @var UserGroup $userGroup */
|
||||
foreach ($objects as $userGroup) {
|
||||
$userGroupId = $userGroup->id;
|
||||
$access = $user->hasRoleInGroupOrOwner($userGroup, UserRoleEnum::VIEW_MEMBERSHIPS) || $user->hasRole('owner');
|
||||
$userGroupId = $userGroup->id;
|
||||
$this->inUse[$userGroupId] = $user->user_group_id === $userGroupId;
|
||||
$access = $user->hasRoleInGroupOrOwner($userGroup, UserRoleEnum::VIEW_MEMBERSHIPS) || $user->hasRole('owner');
|
||||
$this->membershipsVisible[$userGroupId] = $access;
|
||||
if ($access) {
|
||||
$groupMemberships = $userGroup->groupMemberships()->get();
|
||||
|
||||
@@ -62,6 +68,7 @@ class UserGroupTransformer extends AbstractTransformer
|
||||
'user_id' => (string)$groupMembership->user_id,
|
||||
'user_email' => $groupMembership->user->email,
|
||||
'role' => $groupMembership->userRole->title,
|
||||
'you' => $groupMembership->user_id === $user->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -77,11 +84,13 @@ class UserGroupTransformer extends AbstractTransformer
|
||||
public function transform(UserGroup $userGroup): array
|
||||
{
|
||||
return [
|
||||
'id' => $userGroup->id,
|
||||
'created_at' => $userGroup->created_at->toAtomString(),
|
||||
'updated_at' => $userGroup->updated_at->toAtomString(),
|
||||
'title' => $userGroup->title,
|
||||
'members' => $this->memberships[$userGroup->id] ?? [],
|
||||
'id' => $userGroup->id,
|
||||
'created_at' => $userGroup->created_at->toAtomString(),
|
||||
'updated_at' => $userGroup->updated_at->toAtomString(),
|
||||
'in_use' => $this->inUse[$userGroup->id] ?? false,
|
||||
'title' => $userGroup->title,
|
||||
'can_see_members' => $this->membershipsVisible[$userGroup->id] ?? false,
|
||||
'members' => $this->memberships[$userGroup->id] ?? [],
|
||||
];
|
||||
// if the user has a specific role in this group, then collect the memberships.
|
||||
}
|
||||
|
||||
24
changelog.md
24
changelog.md
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
|
||||
## 6.1.14 - 2024-xx-xx
|
||||
|
||||
### Changed
|
||||
- You will have to define again which asset accounts you want to see on the dashboard. Sorry about that.
|
||||
|
||||
## 6.1.13 - 2024-04-01
|
||||
|
||||
### Added
|
||||
|
||||
- sha256 checksums for the release files
|
||||
- git HEAD added to the release files for easier validation
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated pages in the `v2`-layout
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Issue 8648](https://github.com/firefly-iii/firefly-iii/issues/8648) (Crashes during initial setup with PG 16 dbs) reported by @Lysholm
|
||||
- [Issue 8725](https://github.com/firefly-iii/firefly-iii/issues/8725) (API: Call to `api/v1/bills` without arguments fails) reported by @dreautall
|
||||
- [Issue 8732](https://github.com/firefly-iii/firefly-iii/issues/8732) (Error "Division by zero" when opening the "Budget" section) reported by @mrResident
|
||||
- [PR 8735](https://github.com/firefly-iii/firefly-iii/pull/8735) (Fix `Division error by zero` in budget views) reported by @mansuf
|
||||
|
||||
## 6.1.12 - 2024-03-21
|
||||
|
||||
### Fixed
|
||||
|
||||
478
composer.lock
generated
478
composer.lock
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user