mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2026-02-23 20:07:06 +00:00
Compare commits
166 Commits
develop-20
...
develop-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
532d6b21f8 | ||
|
|
3238441315 | ||
|
|
f75eff173b | ||
|
|
b0af654c01 | ||
|
|
f1515f6139 | ||
|
|
6e7758da05 | ||
|
|
29419cc514 | ||
|
|
370781b8ae | ||
|
|
5a638ba02e | ||
|
|
cf3c836293 | ||
|
|
3cd3dafb7f | ||
|
|
ed3f4f62ee | ||
|
|
80823cdfe3 | ||
|
|
defaef171e | ||
|
|
81f6f22efb | ||
|
|
4ad2508675 | ||
|
|
35f997be45 | ||
|
|
ad3fec1458 | ||
|
|
25eab80ec3 | ||
|
|
c27e9873b2 | ||
|
|
84f4f63104 | ||
|
|
02d37998f9 | ||
|
|
39c72a60e1 | ||
|
|
1980f73694 | ||
|
|
d135186149 | ||
|
|
3e36287374 | ||
|
|
37c4db2ce9 | ||
|
|
8e6ff3ceaf | ||
|
|
6e0e32dc6c | ||
|
|
b0e21dd553 | ||
|
|
88291c5f63 | ||
|
|
a87e10f734 | ||
|
|
75f42d57f1 | ||
|
|
3f60442281 | ||
|
|
dd14bb1664 | ||
|
|
403a2ce2cd | ||
|
|
e5366dbf6c | ||
|
|
091f264f3e | ||
|
|
3ad1420262 | ||
|
|
018f68b789 | ||
|
|
e23d0de8f9 | ||
|
|
04c3bf966d | ||
|
|
681619f732 | ||
|
|
cab298708c | ||
|
|
c4392f89d1 | ||
|
|
36789a310a | ||
|
|
e92a1b6dda | ||
|
|
1d687a632f | ||
|
|
1fa4a1bdc8 | ||
|
|
aee53cffb9 | ||
|
|
828b965c98 | ||
|
|
c008cd41db | ||
|
|
accac89ffb | ||
|
|
fb79e3b08c | ||
|
|
db2f804b6a | ||
|
|
986e86ed51 | ||
|
|
9cbd0380d6 | ||
|
|
f6cd45a44c | ||
|
|
0e321ad82a | ||
|
|
c353a4de95 | ||
|
|
57f828a73c | ||
|
|
11385b208b | ||
|
|
5584866b96 | ||
|
|
0b124d875a | ||
|
|
177cfad862 | ||
|
|
e929cf7cb0 | ||
|
|
528fda08e9 | ||
|
|
6f605e5fd2 | ||
|
|
7c76fc7721 | ||
|
|
474680dbbf | ||
|
|
8babc144fe | ||
|
|
6eb14f8a96 | ||
|
|
c4786586fb | ||
|
|
a22cc889b9 | ||
|
|
9ea7bd3b01 | ||
|
|
8d68cef8a5 | ||
|
|
b383ac1a95 | ||
|
|
d2bfb2e9df | ||
|
|
0afec28e5f | ||
|
|
e60e0bb99e | ||
|
|
02064445bc | ||
|
|
386bb811a8 | ||
|
|
653fa53da2 | ||
|
|
63d4572863 | ||
|
|
9eb31fc777 | ||
|
|
7fd33035a6 | ||
|
|
5a87f6b2c0 | ||
|
|
cbebd7928f | ||
|
|
73089c2084 | ||
|
|
34260f2a4f | ||
|
|
c3872aa738 | ||
|
|
4bcd163b47 | ||
|
|
31d444292f | ||
|
|
a27e21f002 | ||
|
|
0f7118d5b3 | ||
|
|
4f9a1fde5d | ||
|
|
bc596cb1c2 | ||
|
|
7110556ef0 | ||
|
|
3e1b703dfc | ||
|
|
fce0750509 | ||
|
|
f5385d0229 | ||
|
|
8599c4a3c1 | ||
|
|
d210e7dcdd | ||
|
|
8de9e0ba29 | ||
|
|
7fa9e79f2a | ||
|
|
4a5281fd80 | ||
|
|
ad60974430 | ||
|
|
195794881b | ||
|
|
3e6a997dd5 | ||
|
|
2eedfd9f26 | ||
|
|
9ec0515bb6 | ||
|
|
6b197eecb9 | ||
|
|
591c970882 | ||
|
|
15d91dbe1b | ||
|
|
6ff87bf447 | ||
|
|
147ce154d8 | ||
|
|
2c8be33000 | ||
|
|
b472890c84 | ||
|
|
2fabcf5193 | ||
|
|
5ed4e7aa79 | ||
|
|
973caad7e4 | ||
|
|
d273503a15 | ||
|
|
cc149adb5d | ||
|
|
ac8bcb786b | ||
|
|
642deefba5 | ||
|
|
5b13b64fd7 | ||
|
|
653a64d0a8 | ||
|
|
c9dcdc90ec | ||
|
|
1f04888331 | ||
|
|
28c21ecb7e | ||
|
|
7382030e61 | ||
|
|
a573e19dbc | ||
|
|
b152a2c8d9 | ||
|
|
7e14c36fc0 | ||
|
|
58bfc7f8ce | ||
|
|
6b8c005108 | ||
|
|
ceb660fd02 | ||
|
|
7fac1e8614 | ||
|
|
809e34f5fe | ||
|
|
73ca57c8c4 | ||
|
|
63088ffeb1 | ||
|
|
4e88ebd52d | ||
|
|
85fd38d146 | ||
|
|
e83d7051eb | ||
|
|
567f2dae17 | ||
|
|
c231ae4016 | ||
|
|
fb13a4cdcb | ||
|
|
724042e1d8 | ||
|
|
84a30c3c8f | ||
|
|
6c9dac831a | ||
|
|
e605ddb779 | ||
|
|
4b19ed8f07 | ||
|
|
57bd8e09d4 | ||
|
|
32f1a7c9c2 | ||
|
|
fc5b0db43f | ||
|
|
c2721f3f48 | ||
|
|
96291c9bce | ||
|
|
ab9400aaee | ||
|
|
31d1ee11cb | ||
|
|
694dc3816c | ||
|
|
c647fa7b94 | ||
|
|
022d59bfdd | ||
|
|
057b82b471 | ||
|
|
2872c6592f | ||
|
|
fa02c15f55 | ||
|
|
c74c8a96b8 |
@@ -43,6 +43,7 @@ return $config->setRules(
|
||||
// rule sets
|
||||
'@PHP8x3Migration' => true,
|
||||
'@PHP8x4Migration' => true,
|
||||
'@PHP8x5Migration' => true,
|
||||
'@PhpCsFixer' => true,
|
||||
'@PhpCsFixer:risky' => true,
|
||||
'@PSR12' => true,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"require": {
|
||||
"php": ">=8.5.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.12"
|
||||
}
|
||||
}
|
||||
|
||||
68
.ci/php-cs-fixer/composer.lock
generated
68
.ci/php-cs-fixer/composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "f1e0b38af4ded66da271a99d2bff5be8",
|
||||
"content-hash": "35b5ad9b3c4e1ffe78ef9bec73987499",
|
||||
"packages": [
|
||||
{
|
||||
"name": "clue/ndjson-react",
|
||||
@@ -402,16 +402,16 @@
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.93.1",
|
||||
"version": "v3.94.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "b3546ab487c0762c39f308dc1ec0ea2c461fc21a"
|
||||
"reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/b3546ab487c0762c39f308dc1ec0ea2c461fc21a",
|
||||
"reference": "b3546ab487c0762c39f308dc1ec0ea2c461fc21a",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7787ceff91365ba7d623ec410b8f429cdebb4f63",
|
||||
"reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -428,7 +428,7 @@
|
||||
"react/event-loop": "^1.5",
|
||||
"react/socket": "^1.16",
|
||||
"react/stream": "^1.4",
|
||||
"sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0",
|
||||
"sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0",
|
||||
"symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0",
|
||||
"symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0",
|
||||
"symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0",
|
||||
@@ -442,18 +442,18 @@
|
||||
"symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"facile-it/paraunit": "^1.3.1 || ^2.7",
|
||||
"infection/infection": "^0.32",
|
||||
"justinrainbow/json-schema": "^6.6",
|
||||
"facile-it/paraunit": "^1.3.1 || ^2.7.1",
|
||||
"infection/infection": "^0.32.3",
|
||||
"justinrainbow/json-schema": "^6.6.4",
|
||||
"keradus/cli-executor": "^2.3",
|
||||
"mikey179/vfsstream": "^1.6.12",
|
||||
"php-coveralls/php-coveralls": "^2.9",
|
||||
"php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6",
|
||||
"php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6",
|
||||
"phpunit/phpunit": "^9.6.31 || ^10.5.60 || ^11.5.48",
|
||||
"php-coveralls/php-coveralls": "^2.9.1",
|
||||
"php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.7",
|
||||
"php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.7",
|
||||
"phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.51",
|
||||
"symfony/polyfill-php85": "^1.33",
|
||||
"symfony/var-dumper": "^5.4.48 || ^6.4.26 || ^7.4.0 || ^8.0",
|
||||
"symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0"
|
||||
"symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.4.4 || ^8.0.4",
|
||||
"symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "For handling output formats in XML",
|
||||
@@ -494,7 +494,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.93.1"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.94.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -502,7 +502,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-28T23:50:50+00:00"
|
||||
"time": "2026-02-20T16:13:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
@@ -1185,29 +1185,29 @@
|
||||
},
|
||||
{
|
||||
"name": "sebastian/diff",
|
||||
"version": "7.0.0",
|
||||
"version": "8.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/diff.git",
|
||||
"reference": "7ab1ea946c012266ca32390913653d844ecd085f"
|
||||
"reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f",
|
||||
"reference": "7ab1ea946c012266ca32390913653d844ecd085f",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/a2b6d09d7729ee87d605a439469f9dcc39be5ea3",
|
||||
"reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.3"
|
||||
"php": ">=8.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^12.0",
|
||||
"phpunit/phpunit": "^13.0",
|
||||
"symfony/process": "^7.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "7.0-dev"
|
||||
"dev-main": "8.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -1240,15 +1240,27 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/diff/issues",
|
||||
"security": "https://github.com/sebastianbergmann/diff/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/diff/tree/7.0.0"
|
||||
"source": "https://github.com/sebastianbergmann/diff/tree/8.0.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://liberapay.com/sebastianbergmann",
|
||||
"type": "liberapay"
|
||||
},
|
||||
{
|
||||
"url": "https://thanks.dev/u/gh/sebastianbergmann",
|
||||
"type": "thanks_dev"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/sebastian/diff",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-07T04:55:46+00:00"
|
||||
"time": "2026-02-06T04:42:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
@@ -2671,7 +2683,9 @@
|
||||
"stability-flags": {},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {},
|
||||
"platform": {
|
||||
"php": ">=8.5.0"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.9.0"
|
||||
}
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/bug.yml
vendored
6
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -4,9 +4,9 @@ body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Support guidelines
|
||||
description: Please read the support guidelines before proceeding.
|
||||
description: Thank you for reading the support guidelines before proceeding.
|
||||
options:
|
||||
- label: I've read the <!-- MZ2udTpin6FL --> [support guidelines](https://github.com/firefly-iii/firefly-iii/blob/main/.github/support.md)
|
||||
- label: I'm smart and I read the <!-- MZ2udTpin6FL --> [support guidelines](https://github.com/firefly-iii/firefly-iii/blob/main/.github/support.md)
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
@@ -29,7 +29,7 @@ body:
|
||||
attributes:
|
||||
label: Debug information
|
||||
description: Please provide the table from the /debug page. Do not add backticks or quotes.
|
||||
placeholder: The output from the /debug page
|
||||
placeholder: The output from the /debug page or "N/A"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
7
.github/mergify.yml
vendored
7
.github/mergify.yml
vendored
@@ -1,11 +1,4 @@
|
||||
pull_request_rules:
|
||||
- name: Make sure PR are up to date before merging
|
||||
description: This automatically updates PRs when they are out-of-date with the
|
||||
base branch to avoid semantic conflicts (next step is using a merge
|
||||
queue).
|
||||
conditions: []
|
||||
actions:
|
||||
update:
|
||||
- name: Close all on main
|
||||
conditions:
|
||||
- base=main
|
||||
|
||||
20
.github/pull_request_template.md
vendored
20
.github/pull_request_template.md
vendored
@@ -1,20 +1,22 @@
|
||||
<!--
|
||||
Thank you for submitting new code to Firefly III, or any of the related projects. Please read the following rules carefully.
|
||||
|
||||
- Please do not submit solutions for problems that are not already reported in an issue.
|
||||
- Unfortunately, Firefly III can't be your learning experience. If you're new to all of this, please open an issue first.
|
||||
- Please do not open PRs to "discuss" possible solutions or to "get feedback" on your code. I simply don't have time for that.
|
||||
- Pull requests for the MAIN branch will be closed.
|
||||
- DO NOT include translated strings in your PR.
|
||||
- PRs (or parts thereof) that only fix issues inside code comments will not be accepted.
|
||||
Please TALK TO ME FIRST before you open a PR.
|
||||
|
||||
If it feels necessary to open an issue first, please do so, before you open a PR.
|
||||
1. If you fix a problem that has no ticket, talk to me FIRST.
|
||||
2. If you introduce new financial solutions or concepts, talk to me FIRST.
|
||||
3. If your PR is more than 25 lines, talk to me FIRST.
|
||||
4. If you used AI to write your PR, talk to me FIRST.
|
||||
5. If you fix spelling or code comments, talk to me FIRST.
|
||||
|
||||
Wanna talk to me? Open a GitHub Issue, Discussion, or send me an email: james@firefly-iii.org
|
||||
|
||||
See also: https://docs.firefly-iii.org/explanation/support/#contributing-code
|
||||
|
||||
-->
|
||||
|
||||
@JC5
|
||||
|
||||
This PR fixes issue # (if relevant).
|
||||
This PR fixes issue # <!-- mandatory field! -->.
|
||||
|
||||
Changes in this pull request:
|
||||
|
||||
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
phpversion:
|
||||
description: 'PHP version'
|
||||
required: true
|
||||
default: '8.4'
|
||||
default: '8.5'
|
||||
schedule:
|
||||
- cron: '0 3 * * MON'
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ github.event.inputs.phpversion || '8.4' }}
|
||||
php-version: ${{ github.event.inputs.phpversion || '8.5' }}
|
||||
extensions: mbstring, intl, zip, bcmath
|
||||
- name: Switch and pull
|
||||
run: |
|
||||
@@ -177,7 +177,7 @@ jobs:
|
||||
rm -rf vendor composer.lock
|
||||
composer update --no-scripts --no-plugins -q
|
||||
mago format || true
|
||||
mago analyze --reporting-format=github || true
|
||||
# mago analyze --reporting-format=github || true
|
||||
sudo chown -R runner:docker resources/lang
|
||||
.ci/phpcs.sh || true
|
||||
- name: Calculate variables
|
||||
|
||||
@@ -4,6 +4,8 @@ Over time, many people have contributed to Firefly III. Their efforts are not al
|
||||
Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution.
|
||||
|
||||
## 2026
|
||||
- RiDEN
|
||||
- Khoa Nguyen
|
||||
- Nick Huang
|
||||
- mateuszkulapl
|
||||
- Gianluca Martino
|
||||
|
||||
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Controllers\Chart;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Chart\ChartRequest;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
@@ -142,23 +143,29 @@ class AccountController extends Controller
|
||||
}
|
||||
// create array of values to collect.
|
||||
|
||||
$rangeDates = array_map(static fn (string $d): Carbon => Carbon::createFromFormat('Y-m-d', $d)->startOfDay(), array_keys($range));
|
||||
$rangeVals = array_values($range);
|
||||
$rangeIdx = 0;
|
||||
$rangeCount = count($rangeDates);
|
||||
|
||||
while ($currentStart <= $params['end']) {
|
||||
$format = $currentStart->format('Y-m-d');
|
||||
$label = $currentStart->toAtomString();
|
||||
$balance = array_key_exists($format, $range) ? $range[$format]['balance'] : $previous;
|
||||
$previous = $balance;
|
||||
$currentSet['entries'][$label] = $balance;
|
||||
|
||||
// do the same for the primary currency balance, if relevant:
|
||||
$pcBalance = null;
|
||||
if ($this->convertToPrimary) {
|
||||
$pcBalance = array_key_exists($format, $range) ? $range[$format]['pc_balance'] : $pcPrevious;
|
||||
$pcPrevious = $pcBalance;
|
||||
$currentSet['pc_entries'][$label] = $pcBalance;
|
||||
// Advance through all range entries up to current chart date
|
||||
while ($rangeIdx < $rangeCount && $rangeDates[$rangeIdx] <= $currentStart) {
|
||||
$previous = $rangeVals[$rangeIdx]['balance'];
|
||||
if ($this->convertToPrimary) {
|
||||
$pcPrevious = $rangeVals[$rangeIdx]['pc_balance'];
|
||||
}
|
||||
++$rangeIdx;
|
||||
}
|
||||
$currentStart = Navigation::addPeriod($currentStart, $period);
|
||||
|
||||
// $currentStart->addDay();
|
||||
$currentSet['entries'][$label] = $previous;
|
||||
if ($this->convertToPrimary) {
|
||||
$currentSet['pc_entries'][$label] = $pcPrevious;
|
||||
}
|
||||
|
||||
$currentStart = Navigation::addPeriod($currentStart, $period);
|
||||
}
|
||||
$this->chartData[] = $currentSet;
|
||||
}
|
||||
|
||||
@@ -25,11 +25,15 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V1\Controllers\Search;
|
||||
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Search\CountRequest;
|
||||
use FireflyIII\Api\V1\Requests\Search\TransactionSearchRequest;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
|
||||
use FireflyIII\Support\Search\SearchInterface;
|
||||
use FireflyIII\Transformers\TransactionGroupTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
use League\Fractal\Resource\Collection;
|
||||
|
||||
@@ -38,6 +42,51 @@ use League\Fractal\Resource\Collection;
|
||||
*/
|
||||
class TransactionController extends Controller
|
||||
{
|
||||
private JournalRepositoryInterface $repository;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware(function ($request, $next) {
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
|
||||
$this->repository = app(JournalRepositoryInterface::class);
|
||||
$this->repository->setUser($admin);
|
||||
|
||||
return $next($request);
|
||||
});
|
||||
}
|
||||
|
||||
public function count(CountRequest $request, SearchInterface $searcher): JsonResponse
|
||||
{
|
||||
$count = 0;
|
||||
$includeDeleted = $request->attributes->get('include_deleted', false);
|
||||
$externalId = (string) $request->attributes->get('external_identifier');
|
||||
$internalRef = (string) $request->attributes->get('internal_reference');
|
||||
$notes = (string) $request->attributes->get('notes');
|
||||
$description = (string) $request->attributes->get('description');
|
||||
Log::debug(sprintf('Include deleted? %s', var_export($includeDeleted, true)));
|
||||
if ('' !== $externalId) {
|
||||
$count += $this->repository->countByMeta('external_identifier', $externalId, $includeDeleted);
|
||||
Log::debug(sprintf('Search for transactions with external_identifier "%s", count is now %d', $externalId, $count));
|
||||
}
|
||||
if ('' !== $internalRef) {
|
||||
$count += $this->repository->countByMeta('internal_reference', $internalRef, $includeDeleted);
|
||||
Log::debug(sprintf('Search for transactions with internal_reference "%s", count is now %d', $internalRef, $count));
|
||||
}
|
||||
if ('' !== $notes) {
|
||||
$count += $this->repository->countByNotes($notes, $includeDeleted);
|
||||
Log::debug(sprintf('Search for transactions with notes LIKE "%s", count is now %d', $notes, $count));
|
||||
}
|
||||
if ('' !== $description) {
|
||||
$count += $this->repository->countByDescription($description, $includeDeleted);
|
||||
Log::debug(sprintf('Search for transactions with description "%s", count is now %d', $description, $count));
|
||||
}
|
||||
|
||||
return response()->json(['count' => $count]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/search/searchTransactions
|
||||
|
||||
@@ -61,7 +61,7 @@ class TransactionRequest extends FormRequest
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return ['query' => ['required', 'min:1', 'max:255', 'json', new IsValidBulkClause(ClauseType::TRANSACTION)]];
|
||||
return ['query' => ['required', 'min:1', 'max:255', 'json', new IsValidBulkClause(ClauseType::TRANSACTION->value)]];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
|
||||
@@ -92,7 +92,7 @@ class UpdateRequest extends FormRequest
|
||||
'description' => 'min:1|max:32768|nullable',
|
||||
'rule_group_id' => 'belongsToUser:rule_groups',
|
||||
'rule_group_title' => 'nullable|min:1|max:255|belongsToUser:rule_groups,title',
|
||||
'trigger' => 'in:store-journal,update-journal.manual-activation',
|
||||
'trigger' => 'in:store-journal,update-journal,manual-activation',
|
||||
'triggers.*.type' => 'required|in:'.implode(',', $validTriggers),
|
||||
'triggers.*.value' => 'required_if:actions.*.type,'.$contextTriggers.'|min:1|ruleTriggerValue|max:1024',
|
||||
'triggers.*.stop_processing' => [new IsBoolean()],
|
||||
|
||||
@@ -63,6 +63,9 @@ class UpdateRequest extends FormRequest
|
||||
{
|
||||
/** @var TransactionCurrency $currency */
|
||||
$currency = $this->route()->parameter('currency_code');
|
||||
if (is_string($currency)) {
|
||||
$currency = TransactionCurrency::whereCode($currency)->first();
|
||||
}
|
||||
|
||||
return [
|
||||
'name' => sprintf('min:1|max:255|unique:transaction_currencies,name,%d', $currency->id),
|
||||
|
||||
64
app/Api/V1/Requests/Search/CountRequest.php
Normal file
64
app/Api/V1/Requests/Search/CountRequest.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* SearchRequest.php
|
||||
* Copyright (c) 2026 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\Api\V1\Requests\Search;
|
||||
|
||||
use FireflyIII\Api\V1\Requests\AggregateFormRequest;
|
||||
use FireflyIII\Rules\IsBoolean;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Override;
|
||||
|
||||
class CountRequest extends AggregateFormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'notes' => 'string|min:1|max:255',
|
||||
'external_identifier' => 'string|min:1|max:255',
|
||||
'description' => 'string|min:1|max:255',
|
||||
'internal_reference' => 'string|min:1|max:255',
|
||||
'include_deleted' => new IsBoolean(),
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(function (Validator $validator): void {
|
||||
if ($validator->failed()) {
|
||||
return;
|
||||
}
|
||||
$this->attributes->set('include_deleted', $this->convertBoolean($this->input('include_deleted', 'false')));
|
||||
$this->attributes->set('notes', $this->convertString('notes'));
|
||||
$this->attributes->set('external_identifier', $this->convertString('external_identifier'));
|
||||
$this->attributes->set('description', $this->convertString('description'));
|
||||
$this->attributes->set('internal_reference', $this->convertString('internal_reference'));
|
||||
});
|
||||
}
|
||||
|
||||
#[Override]
|
||||
protected function getRequests(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -56,8 +56,8 @@ class CorrectsGroupAccounts extends Command
|
||||
}
|
||||
}
|
||||
$flags = new TransactionGroupEventFlags();
|
||||
$flags->applyRules = true;
|
||||
$flags->fireWebhooks = true;
|
||||
$flags->applyRules = false;
|
||||
$flags->fireWebhooks = false;
|
||||
$flags->recalculateCredit = true;
|
||||
$objects = new TransactionGroupEventObjects();
|
||||
foreach ($groups as $groupId) {
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace FireflyIII\Console\Commands\Integrity;
|
||||
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ValidatesFilePermissions extends Command
|
||||
{
|
||||
@@ -50,26 +51,39 @@ class ValidatesFilePermissions extends Command
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
Log::debug(sprintf('Start of %s', $this->signature));
|
||||
$directories = [storage_path('upload')];
|
||||
$errors = false;
|
||||
|
||||
/** @var string $directory */
|
||||
foreach ($directories as $directory) {
|
||||
Log::debug(sprintf('Processing directory: %s', $directory));
|
||||
if (!is_dir($directory)) {
|
||||
$this->friendlyError(sprintf('Directory "%s" cannot found. It is necessary to allow files to be uploaded.', $directory));
|
||||
$errors = true;
|
||||
$message = sprintf('Directory "%s" cannot found. It is necessary to allow files to be uploaded.', $directory);
|
||||
Log::error($message);
|
||||
$this->friendlyError($message);
|
||||
$errors = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
Log::debug('It is a directory!');
|
||||
if (!is_writable($directory)) {
|
||||
$this->friendlyError(sprintf('Directory "%s" is not writeable. Uploading attachments may fail silently.', $directory));
|
||||
$errors = true;
|
||||
$message = sprintf('Directory "%s" is not writeable. Uploading attachments may fail silently.', $directory);
|
||||
$this->friendlyError($message);
|
||||
Log::error($message);
|
||||
$errors = true;
|
||||
}
|
||||
Log::debug('It is writeable!');
|
||||
Log::debug(sprintf('Done processing %s', $directory));
|
||||
}
|
||||
Log::debug('Done with loop.');
|
||||
if (false === $errors) {
|
||||
Log::debug('No errors.');
|
||||
$this->friendlyInfo('All necessary file paths seem to exist, and are writeable.');
|
||||
}
|
||||
|
||||
Log::debug(sprintf('End of %s', $this->signature));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Console\Commands\System;
|
||||
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use FireflyIII\Console\Commands\Tools\VerifiesDatabaseConnectionTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
@@ -32,6 +33,7 @@ use PDOException;
|
||||
class CreatesDatabase extends Command
|
||||
{
|
||||
use ShowsFriendlyMessages;
|
||||
use VerifiesDatabaseConnectionTrait;
|
||||
|
||||
protected $description = 'Tries to create the database if it doesn\'t exist yet.';
|
||||
|
||||
@@ -39,21 +41,27 @@ class CreatesDatabase extends Command
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
if ('mysql' !== env('DB_CONNECTION')) { // @phpstan-ignore larastan.noEnvCallsOutsideOfConfig */
|
||||
$this->friendlyInfo(sprintf('CreateDB does not apply to "%s", skipped.', env('DB_CONNECTION')));
|
||||
$connected = $this->verifyDatabaseConnection();
|
||||
if (!$connected) {
|
||||
$this->friendlyError('Failed to connect to the database. Is it up?');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
if ('mysql' !== config('database.default')) {
|
||||
$this->friendlyInfo(sprintf('CreateDB does not apply to "%s", skipped.', config('database.default')));
|
||||
|
||||
return 0;
|
||||
}
|
||||
// try to set up a raw connection:
|
||||
$exists = false;
|
||||
$dsn = sprintf('mysql:host=%s;port=%d;charset=utf8mb4', env('DB_HOST'), env('DB_PORT'));
|
||||
$exists = false;
|
||||
$dsn = sprintf('mysql:host=%s;port=%d;charset=utf8mb4', env('DB_HOST'), env('DB_PORT'));
|
||||
|
||||
if ('' !== (string) env('DB_SOCKET')) {
|
||||
$dsn = sprintf('mysql:unix_socket=%s;charset=utf8mb4', env('DB_SOCKET'));
|
||||
}
|
||||
$this->friendlyLine(sprintf('DSN is %s', $dsn));
|
||||
|
||||
$options = [
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
@@ -71,7 +79,7 @@ class CreatesDatabase extends Command
|
||||
// only continue when no error.
|
||||
// with PDO, try to list DB's (
|
||||
/** @var array $stmt */
|
||||
$stmt = $pdo->query('SHOW DATABASES;');
|
||||
$stmt = $pdo->query('SHOW DATABASES;');
|
||||
// slightly more complex but less error-prone.
|
||||
foreach ($stmt as $row) {
|
||||
$name = $row['Database'] ?? false;
|
||||
|
||||
99
app/Console/Commands/Tools/ChecksForUpdates.php
Normal file
99
app/Console/Commands/Tools/ChecksForUpdates.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* ChecksForUpdates.php
|
||||
* Copyright (c) 2026 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\Tools;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use FireflyIII\Services\FireflyIIIOrg\Update\UpdateRequestInterface;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ChecksForUpdates extends Command
|
||||
{
|
||||
use ShowsFriendlyMessages;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly-iii:check-for-updates {--force}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Checks for Firefly III updates';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$build = Carbon::createFromTimestamp(config('firefly.build_time'), config('app.timezone'));
|
||||
$version = config('firefly.version');
|
||||
|
||||
$this->friendlyLine(sprintf('You are running version "%s", built on %s', $version, $build->format('Y-m-d H:i')));
|
||||
$permission = FireflyConfig::get('permission_update_check', -1)->data;
|
||||
if (1 !== $permission && false === $this->option('force')) {
|
||||
$this->friendlyWarning('Checking for updates is disabled. To overrule, use --force.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
if (str_contains(config('firefly.version'), 'develop')) {
|
||||
$this->friendlyWarning('You are running a development version.');
|
||||
}
|
||||
|
||||
/** @var UpdateRequestInterface $request */
|
||||
$request = app(UpdateRequestInterface::class);
|
||||
// stable, alpha or beta
|
||||
$info = $request->getUpdateInformation($version, $build, 'stable');
|
||||
|
||||
if ('' !== $info->getError()) {
|
||||
$this->friendlyError($info->getError());
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
if (!$info->isNewVersionAvailable()) {
|
||||
$this->friendlyInfo(trans('firefly.no_new_release_available'));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
// if running develop, slightly different message.
|
||||
if (str_contains($version, 'develop')) {
|
||||
$this->friendlyInfo(trans('firefly.update_current_dev_older', ['version' => $version, 'new_version' => $info->getNewVersion()]));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
$this->friendlyInfo(trans('firefly.update_new_version_alert', [
|
||||
'your_version' => $version,
|
||||
'new_version' => $info->getNewVersion(),
|
||||
'date' => $info->getPublishedAt()->format('Y-m-d H:i:s'),
|
||||
]));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
64
app/Console/Commands/Tools/VerifiesDatabaseConnection.php
Normal file
64
app/Console/Commands/Tools/VerifiesDatabaseConnection.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* VerifiesDatabaseConnection.php
|
||||
* Copyright (c) 2026 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\Tools;
|
||||
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class VerifiesDatabaseConnection extends Command
|
||||
{
|
||||
use ShowsFriendlyMessages;
|
||||
use VerifiesDatabaseConnectionTrait;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly-iii:verify-database-connection';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This command tries to connect to the database.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$connected = $this->verifyDatabaseConnection();
|
||||
if ($connected) {
|
||||
$this->friendlyPositive('Connected to the database.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
$this->friendlyError('Failed to connect to the database. Is it up?');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* VerifiesDatabaseConnectionTrait.php
|
||||
* Copyright (c) 2026 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\Tools;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
trait VerifiesDatabaseConnectionTrait
|
||||
{
|
||||
protected function verifyDatabaseConnection(): bool
|
||||
{
|
||||
$loops = 30;
|
||||
$loop = 0;
|
||||
$queries = ['pgsql' => 'SELECT * FROM pg_catalog.pg_tables;', 'sqlite' => 'SELECT name FROM sqlite_schema;', 'mysql' => 'SHOW TABLES;'];
|
||||
$default = config('database.default');
|
||||
if (!array_key_exists($default, $queries)) {
|
||||
$this->friendlyWarning(sprintf('Cannot validate database connection for "%s"', $default));
|
||||
|
||||
return true;
|
||||
}
|
||||
$query = $queries[$default];
|
||||
$connected = false;
|
||||
Log::debug(sprintf('Connecting to database "%s"...', config('database.default')));
|
||||
while (!$connected && $loop < $loops) {
|
||||
try {
|
||||
DB::select($query);
|
||||
$connected = true;
|
||||
} catch (QueryException $e) {
|
||||
Log::error(sprintf('Loop #%d: connection failed: %s', $loop, $e->getMessage()));
|
||||
$this->friendlyWarning(sprintf('Database connection attempt #%d failed. Sleep for 10 seconds...', $loop + 1));
|
||||
sleep(10);
|
||||
} catch (Exception $e) {
|
||||
Log::error(sprintf('Loop #%d: not connected yet because of a %s: %s', $loop, get_class($e), $e->getMessage()));
|
||||
$this->friendlyWarning(sprintf('Database connection attempt #%d failed. Sleep for 10 seconds...', $loop + 1));
|
||||
sleep(10);
|
||||
}
|
||||
++$loop;
|
||||
}
|
||||
|
||||
return $connected;
|
||||
}
|
||||
}
|
||||
@@ -94,6 +94,7 @@ class UpgradesDatabase extends Command
|
||||
|
||||
private function callInitialCommands(): void
|
||||
{
|
||||
$this->call('firefly-iii:verify-database-connection');
|
||||
$this->call('migrate', ['--seed' => true, '--force' => true, '--no-interaction' => true]);
|
||||
$this->call('upgrade:600-pgsql-sequences');
|
||||
$this->call('upgrade:480-decrypt-all');
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Kernel.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\Console;
|
||||
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* File to make sure commands work.
|
||||
*/
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* Register the commands for the application.
|
||||
*/
|
||||
#[Override]
|
||||
protected function commands(): void
|
||||
{
|
||||
$this->load(__DIR__.'/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
*/
|
||||
#[Override]
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
$schedule->call(static function (): void {
|
||||
Log::error('Firefly III no longer users the Laravel scheduler to do cron jobs! Please read the instructions at https://docs.firefly-iii.org/');
|
||||
echo "\n";
|
||||
echo '------------';
|
||||
echo "\n";
|
||||
echo wordwrap('Firefly III no longer users the Laravel scheduler to do cron jobs! Please read the instructions here:');
|
||||
echo "\n";
|
||||
echo 'https://docs.firefly-iii.org/';
|
||||
echo "\n\n";
|
||||
echo 'Disable this cron job!';
|
||||
echo "\n";
|
||||
echo '------------';
|
||||
echo "\n";
|
||||
})->daily();
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* AccountBalance.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\Entities;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
|
||||
class AccountBalance
|
||||
{
|
||||
public string $amount;
|
||||
public string $currencyId;
|
||||
public string $id;
|
||||
|
||||
public static function fromArray(): self
|
||||
{
|
||||
$balance = new self();
|
||||
$balance->id = (string) random_int(1, 1000);
|
||||
// $balance->name = (string) random_int(1, 1000);
|
||||
$balance->amount = (string) random_int(1, 1000);
|
||||
$balance->currencyId = '1';
|
||||
|
||||
return $balance;
|
||||
}
|
||||
|
||||
public function getAccount(): Account
|
||||
{
|
||||
return Account::inRandomOrder()->first();
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,9 @@ namespace FireflyIII\Enums;
|
||||
/**
|
||||
* Class ClauseType
|
||||
*/
|
||||
class ClauseType
|
||||
enum ClauseType: string
|
||||
{
|
||||
public const string TRANSACTION = 'transaction';
|
||||
public const string UPDATE = 'update';
|
||||
public const string WHERE = 'where';
|
||||
case TRANSACTION = 'transaction';
|
||||
case UPDATE = 'update';
|
||||
case WHERE = 'where';
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* api-noauth.php
|
||||
* Copyright (c) 2021 james@firefly-iii.org
|
||||
* UpdatedExistingBill.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -20,20 +22,21 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
namespace FireflyIII\Events\Model\Bill;
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
// Cron job API routes:
|
||||
use FireflyIII\Http\Middleware\AcceptHeaders;
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\Bill;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
Route::group(
|
||||
[
|
||||
'namespace' => 'FireflyIII\Api\V1\Controllers\System',
|
||||
'prefix' => '',
|
||||
'as' => 'api.v1.cron.',
|
||||
'middleware' => [AcceptHeaders::class],
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('{cliToken}', ['uses' => 'CronController@cron', 'as' => 'index']);
|
||||
}
|
||||
);
|
||||
class UpdatedExistingBill extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public Bill $bill,
|
||||
public array $oldData
|
||||
) {}
|
||||
}
|
||||
@@ -25,13 +25,14 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Events\Model\TransactionGroup;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class UserRequestedBatchProcessing extends Event
|
||||
{
|
||||
public TransactionGroupEventObjects $objects;
|
||||
|
||||
public function __construct(
|
||||
public TransactionGroupEventFlags $flags
|
||||
) {
|
||||
Log::debug(__METHOD__);
|
||||
$this->objects = new TransactionGroupEventObjects();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +208,7 @@ class Handler extends ExceptionHandler
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Error "%s" has no Firefly III treatment, parent will handle.', $e::class));
|
||||
Log::error($e->getMessage());
|
||||
|
||||
return parent::render($request, $e);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace FireflyIII\Generator\Chart\Basic;
|
||||
|
||||
use FireflyIII\Support\ChartColour;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class ChartJsGenerator.
|
||||
@@ -92,14 +93,21 @@ class ChartJsGenerator implements GeneratorInterface
|
||||
*
|
||||
* // it's five.
|
||||
*/
|
||||
public function multiSet(array $data): array
|
||||
public function multiSet(array $data, array $labels = []): array
|
||||
{
|
||||
reset($data);
|
||||
$first = current($data);
|
||||
if (!is_array($first)) {
|
||||
return [];
|
||||
}
|
||||
$labels = is_array($first['entries']) ? array_keys($first['entries']) : [];
|
||||
Log::debug('Now in multiSet()');
|
||||
if (0 !== count($labels)) {
|
||||
Log::debug('Labels are given: ', $labels);
|
||||
}
|
||||
if (0 === count($labels)) {
|
||||
$labels = is_array($first['entries']) ? array_keys($first['entries']) : [];
|
||||
Log::debug('Labels are generated: ', $labels);
|
||||
}
|
||||
|
||||
$chartData = [
|
||||
'count' => count($data),
|
||||
|
||||
@@ -58,7 +58,7 @@ interface GeneratorInterface
|
||||
*
|
||||
* // it's five.
|
||||
*/
|
||||
public function multiSet(array $data): array;
|
||||
public function multiSet(array $data, array $labels = []): array;
|
||||
|
||||
/**
|
||||
* Expects data as:.
|
||||
|
||||
@@ -192,6 +192,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
case WebhookResponse::BUDGET->name:
|
||||
$basicMessage['content'] = [];
|
||||
if ($model instanceof Budget) {
|
||||
$model->refresh();
|
||||
$enrichment = new BudgetEnrichment();
|
||||
$enrichment->setUser($model->user);
|
||||
|
||||
@@ -201,6 +202,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
$basicMessage['content'] = $transformer->transform($model);
|
||||
}
|
||||
if ($model instanceof BudgetLimit) {
|
||||
$model->refresh();
|
||||
$user = $model->budget->user;
|
||||
$enrichment = new BudgetLimitEnrichment();
|
||||
$enrichment->setUser($user);
|
||||
@@ -224,6 +226,8 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
break;
|
||||
|
||||
case WebhookResponse::TRANSACTIONS->name:
|
||||
$model->refresh();
|
||||
|
||||
/** @var TransactionGroup $model */
|
||||
$transformer = new TransactionGroupTransformer();
|
||||
|
||||
@@ -243,6 +247,8 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
break;
|
||||
|
||||
case WebhookResponse::ACCOUNTS->name:
|
||||
$model->refresh();
|
||||
|
||||
/** @var TransactionGroup $model */
|
||||
$accounts = $this->collectAccounts($model);
|
||||
$enrichment = new AccountEnrichment();
|
||||
|
||||
@@ -36,13 +36,13 @@ class ConvertsAmountToPrimaryAmount
|
||||
$primaryAmountField = $params->primaryAmountField;
|
||||
|
||||
if (!Amount::convertToPrimary($params->user)) {
|
||||
Log::debug(sprintf(
|
||||
'User does not want to do conversion, no need to convert "%s" and store it in field "%s" for %s #%d.',
|
||||
$params->amountField,
|
||||
$params->primaryAmountField,
|
||||
get_class($params->model),
|
||||
$params->model->id
|
||||
));
|
||||
// Log::debug(sprintf(
|
||||
// 'User does not want to do conversion, no need to convert "%s" and store it in field "%s" for %s #%d.',
|
||||
// $params->amountField,
|
||||
// $params->primaryAmountField,
|
||||
// get_class($params->model),
|
||||
// $params->model->id
|
||||
// ));
|
||||
$params->model->{$primaryAmountField} = null;
|
||||
$params->model->saveQuietly();
|
||||
|
||||
|
||||
@@ -24,7 +24,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Helpers\Update;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Services\FireflyIIIOrg\Update\UpdateRequestInterface;
|
||||
use FireflyIII\Services\FireflyIIIOrg\Update\UpdateResponse;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -38,7 +40,7 @@ trait UpdateTrait
|
||||
* 'message' => 'A new version is available.
|
||||
* 'level' => 'info' / 'success' / 'error'
|
||||
*/
|
||||
public function getLatestRelease(): array
|
||||
public function getLatestRelease(): UpdateResponse
|
||||
{
|
||||
Log::debug('Now in getLatestRelease()');
|
||||
|
||||
@@ -46,7 +48,9 @@ trait UpdateTrait
|
||||
$checker = app(UpdateRequestInterface::class);
|
||||
$channelConfig = FireflyConfig::get('update_channel', 'stable');
|
||||
$channel = (string) $channelConfig->data;
|
||||
$build = Carbon::createFromTimestamp(config('firefly.build_time'), config('app.timezone'));
|
||||
$version = config('firefly.version');
|
||||
|
||||
return $checker->getUpdateInformation($channel);
|
||||
return $checker->getUpdateInformation($version, $build, $channel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ class ConfigurationController extends Controller
|
||||
FireflyConfig::set('enable_external_map', $data['enable_external_map']);
|
||||
FireflyConfig::set('enable_external_rates', $data['enable_external_rates']);
|
||||
FireflyConfig::set('allow_webhooks', $data['allow_webhooks']);
|
||||
FireflyConfig::set('enable_batch_processing', $data['enable_batch_processing']);
|
||||
|
||||
FireflyConfig::set('valid_url_protocols', $data['valid_url_protocols']);
|
||||
FireflyConfig::set('is_demo_site', $data['is_demo_site']);
|
||||
|
||||
@@ -114,8 +114,27 @@ class UpdateController extends Controller
|
||||
public function updateCheck(): RedirectResponse
|
||||
{
|
||||
$release = $this->getLatestRelease();
|
||||
$level = 'info';
|
||||
$message = trans('firefly.no_new_release_available');
|
||||
if ('' !== $release->getError()) {
|
||||
$level = 'error';
|
||||
$message = $release->getError();
|
||||
}
|
||||
if ($release->isNewVersionAvailable()) {
|
||||
// if running develop, slightly different message.
|
||||
if (str_contains(config('firefly.version'), 'develop')) {
|
||||
$message = trans('firefly.update_current_dev_older', ['version' => config('firefly.version'), 'new_version' => $release->getNewVersion()]);
|
||||
}
|
||||
if (!str_contains(config('firefly.version'), 'develop')) {
|
||||
$message = trans('firefly.update_new_version_alert', [
|
||||
'your_version' => config('firefly.version'),
|
||||
'new_version' => $release->getNewVersion(),
|
||||
'date' => $release->getPublishedAt()->format('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
session()->flash($release['level'], $release['message']);
|
||||
session()->flash($level, $message);
|
||||
|
||||
return redirect(route('settings.update-check'));
|
||||
}
|
||||
|
||||
@@ -190,6 +190,14 @@ class LoginController extends Controller
|
||||
*/
|
||||
public function showLoginForm(Request $request): Factory|Redirector|RedirectResponse|View
|
||||
{
|
||||
if ('remote_user_guard' === config('auth.defaults.guard')) {
|
||||
$message = sprintf(
|
||||
'Firefly III is configured to use the "remote user guard", but was unable to link you to a user. Are you sure the "%s" header is in place?',
|
||||
config('auth.guard_header')
|
||||
);
|
||||
|
||||
return view('errors.error', ['message' => $message]);
|
||||
}
|
||||
Log::channel('audit')->info('Show login form (1.1).');
|
||||
|
||||
$count = DB::table('users')->count();
|
||||
|
||||
@@ -103,10 +103,10 @@ class CategoryController extends Controller
|
||||
*/
|
||||
public function frontPage(): JsonResponse
|
||||
{
|
||||
$start = session('start', today(config('app.timezone'))->startOfMonth());
|
||||
$end = session('end', today(config('app.timezone'))->endOfMonth());
|
||||
$start = session('start', today(config('app.timezone'))->startOfMonth());
|
||||
$end = session('end', today(config('app.timezone'))->endOfMonth());
|
||||
// chart properties for cache:
|
||||
$cache = new CacheProperties();
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($this->convertToPrimary);
|
||||
@@ -115,9 +115,11 @@ class CategoryController extends Controller
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
|
||||
$frontpageGenerator = new FrontpageChartGenerator($start, $end);
|
||||
$chartData = $frontpageGenerator->generate();
|
||||
$data = $this->generator->multiSet($chartData);
|
||||
$frontpageGenerator = new FrontpageChartGenerator($start, $end);
|
||||
$frontpageGenerator->convertToPrimary = $this->convertToPrimary;
|
||||
|
||||
$chartData = $frontpageGenerator->generate();
|
||||
$data = $this->generator->multiSet($chartData);
|
||||
$cache->store($data);
|
||||
|
||||
return response()->json($data);
|
||||
@@ -281,6 +283,6 @@ class CategoryController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
return $this->generator->multiSet($chartData);
|
||||
return $this->generator->multiSet($chartData, array_values($periods));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,11 +45,9 @@ class CategoryReportController extends Controller
|
||||
use AugumentData;
|
||||
use TransactionCalculation;
|
||||
|
||||
/** @var GeneratorInterface Chart generation methods. */
|
||||
private $generator;
|
||||
private GeneratorInterface $generator;
|
||||
|
||||
/** @var OperationsRepositoryInterface */
|
||||
private $opsRepository;
|
||||
private OperationsRepositoryInterface $opsRepository;
|
||||
|
||||
/**
|
||||
* CategoryReportController constructor.
|
||||
|
||||
@@ -147,6 +147,7 @@ class ReportController extends Controller
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($accounts);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($this->convertToPrimary);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
@@ -177,17 +178,32 @@ class ReportController extends Controller
|
||||
foreach ($journals as $journal) {
|
||||
$period = $journal['date']->format($format);
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$currencySymbol = (string) $journal['currency_symbol'];
|
||||
$currencyCode = (string) $journal['currency_code'];
|
||||
$currencyName = (string) $journal['currency_name'];
|
||||
$currencyDecimalPlaces = (int) $journal['currency_decimal_places'];
|
||||
$amount = (string) $journal['amount'];
|
||||
|
||||
if ($this->convertToPrimary && null !== $this->primaryCurrency && $journal['currency_id'] !== $this->primaryCurrency->id) {
|
||||
$currencyId = $this->primaryCurrency->id;
|
||||
$currencySymbol = $this->primaryCurrency->symbol;
|
||||
$currencyCode = $this->primaryCurrency->code;
|
||||
$currencyName = $this->primaryCurrency->name;
|
||||
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
|
||||
$amount = $journal['foreign_currency_id'] === $this->primaryCurrency->id ? $journal['foreign_amount'] : $journal['pc_amount'];
|
||||
}
|
||||
|
||||
$data[$currencyId] ??= [
|
||||
'currency_id' => $currencyId,
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
'currency_name' => $journal['currency_name'],
|
||||
'currency_decimal_places' => (int) $journal['currency_decimal_places'],
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_name' => $currencyName,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
];
|
||||
$data[$currencyId][$period] ??= ['period' => $period, 'spent' => '0', 'earned' => '0'];
|
||||
// in our outgoing?
|
||||
$key = 'spent';
|
||||
$amount = Steam::positive($journal['amount']);
|
||||
$amount = Steam::positive($amount);
|
||||
|
||||
// deposit = incoming
|
||||
// transfer or reconcile or opening balance, and these accounts are the destination.
|
||||
|
||||
@@ -80,6 +80,14 @@ abstract class Controller extends BaseController
|
||||
View::share('FF_VERSION', config('firefly.version'));
|
||||
View::share('FF_BUILD_TIME', config('firefly.build_time'));
|
||||
|
||||
// this breaks when running < PHP 8.5 and is totally intentional.
|
||||
$input = ' James is cool';
|
||||
$output = $input
|
||||
|> trim(...)
|
||||
|> (fn (string $string) => str_replace(' ', '-', $string))
|
||||
|> (fn (string $string) => str_replace(['.', '/', '…'], '', $string))
|
||||
|> strtolower(...);
|
||||
|
||||
// is webhooks enabled?
|
||||
View::share(
|
||||
'featuringWebhooks',
|
||||
|
||||
@@ -58,6 +58,7 @@ class InstallController extends Controller
|
||||
private array $upgradeCommands = [
|
||||
// there are 5 initial commands
|
||||
// Check 4 places: InstallController, Docker image, UpgradeDatabase, composer.json
|
||||
'firefly-iii:create-database' => [],
|
||||
'migrate' => ['--seed' => true, '--force' => true],
|
||||
'generate-keys' => [], // an exception :(
|
||||
'firefly-iii:upgrade-database' => [],
|
||||
|
||||
@@ -103,10 +103,11 @@ class IndexController extends Controller
|
||||
$firstJournal = $this->repository->firstNull();
|
||||
$startPeriod = $firstJournal instanceof TransactionJournal ? $firstJournal->date : new Carbon();
|
||||
$endPeriod = clone $end;
|
||||
|
||||
// limit to 3 years for the time being.
|
||||
if (now()->diffInYears($startPeriod, true) > 3) {
|
||||
$startPeriod = now()->subYears(3);
|
||||
$endPeriod->endOfDay();
|
||||
// limit to 6 years for the time being.
|
||||
$max = 6;
|
||||
if (now()->diffInYears($startPeriod, true) > $max) {
|
||||
$startPeriod = now()->subYears($max);
|
||||
}
|
||||
|
||||
$periods = $this->getTransactionPeriodOverview($objectType, $startPeriod, $endPeriod);
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Kernel.php
|
||||
* Copyright (c) 2019 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http;
|
||||
|
||||
use FireflyIII\Http\Middleware\Authenticate;
|
||||
use FireflyIII\Http\Middleware\Binder;
|
||||
use FireflyIII\Http\Middleware\InstallationId;
|
||||
use FireflyIII\Http\Middleware\RedirectIfAuthenticated;
|
||||
use FireflyIII\Http\Middleware\StartFireflySession;
|
||||
use FireflyIII\Http\Middleware\TrimStrings;
|
||||
use FireflyIII\Http\Middleware\TrustProxies;
|
||||
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
|
||||
use Illuminate\Auth\Middleware\Authorize;
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
|
||||
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
|
||||
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
|
||||
/**
|
||||
* Class Kernel
|
||||
*/
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
protected $middleware = [
|
||||
// SecureHeaders::class,
|
||||
CheckForMaintenanceMode::class,
|
||||
ValidatePostSize::class,
|
||||
TrimStrings::class,
|
||||
ConvertEmptyStringsToNull::class,
|
||||
TrustProxies::class,
|
||||
InstallationId::class,
|
||||
];
|
||||
protected $middlewareAliases = [
|
||||
'auth' => Authenticate::class,
|
||||
'auth.basic' => AuthenticateWithBasicAuth::class,
|
||||
'bindings' => Binder::class,
|
||||
'can' => Authorize::class,
|
||||
'guest' => RedirectIfAuthenticated::class,
|
||||
'throttle' => ThrottleRequests::class,
|
||||
];
|
||||
protected $middlewarePriority = [StartFireflySession::class, ShareErrorsFromSession::class, Authenticate::class, Binder::class, Authorize::class];
|
||||
}
|
||||
@@ -82,22 +82,25 @@ class Authenticate
|
||||
protected function authenticate($request, array $guards)
|
||||
{
|
||||
if (0 === count($guards)) {
|
||||
// go for default guard:
|
||||
// @noinspection PhpUndefinedMethodInspection
|
||||
if ($this->auth->check()) {
|
||||
// do an extra check on user object.
|
||||
/** @noinspection PhpUndefinedMethodInspection */
|
||||
|
||||
/** @var User $user */
|
||||
$user = $this->auth->authenticate();
|
||||
Log::debug('in Authenticate::authenticate() with zero guards.');
|
||||
// There are no guards defined, go for the default guard:
|
||||
if (auth()->check()) {
|
||||
Log::debug('User is authenticated.');
|
||||
$user = auth()->user();
|
||||
$this->validateBlockedUser($user, $guards);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
// @noinspection PhpUndefinedMethodInspection
|
||||
return $this->auth->authenticate();
|
||||
$this->auth->authenticate();
|
||||
if (!$this->auth->check()) {
|
||||
throw new AuthenticationException('The user is not logged in but must be.', $guards);
|
||||
}
|
||||
}
|
||||
|
||||
exit('five');
|
||||
foreach ($guards as $guard) {
|
||||
exit('six');
|
||||
if ('api' !== $guard) {
|
||||
$this->auth->guard($guard)->authenticate();
|
||||
}
|
||||
@@ -111,6 +114,7 @@ class Authenticate
|
||||
}
|
||||
}
|
||||
|
||||
exit('seven');
|
||||
// this is a massive hack, but if the handler has the oauth exception
|
||||
// at this point we can report its error instead of a generic one.
|
||||
$message = 'Unauthenticated.';
|
||||
@@ -143,5 +147,6 @@ class Authenticate
|
||||
// @phpstan-ignore-line (thinks function is undefined)
|
||||
throw new AuthenticationException('Blocked account.', $guards);
|
||||
}
|
||||
Log::debug(sprintf('User #%d is not blocked.', $user->id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ use Override;
|
||||
/**
|
||||
* Class StartFireflySession.
|
||||
*/
|
||||
class StartFireflySession extends StartSession
|
||||
class StartFireflyIIISession extends StartSession
|
||||
{
|
||||
/**
|
||||
* Store the current URL for the request if necessary.
|
||||
81
app/Listeners/Model/Bill/UpdatesRulesForChangedBill.php
Normal file
81
app/Listeners/Model/Bill/UpdatesRulesForChangedBill.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* UpdatesRulesForChangedBill.php
|
||||
* Copyright (c) 2026 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\Listeners\Model\Bill;
|
||||
|
||||
use FireflyIII\Events\Model\Bill\UpdatedExistingBill;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use FireflyIII\Models\RuleTrigger;
|
||||
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class UpdatesRulesForChangedBill implements ShouldQueue
|
||||
{
|
||||
public function handle(UpdatedExistingBill $event): void
|
||||
{
|
||||
// update rule actions.
|
||||
if ($event->bill->name !== $event->oldData['name']) {
|
||||
$this->updateBillTriggersAndActions($event->bill, $event->oldData);
|
||||
}
|
||||
}
|
||||
|
||||
private function updateBillTriggersAndActions(Bill $bill, array $oldData): void
|
||||
{
|
||||
Log::debug(sprintf('Now in updateBillTriggersAndActions(#%d)', $bill->id));
|
||||
$repository = app(RuleRepositoryInterface::class);
|
||||
$repository->setUser($bill->user);
|
||||
$rules = $repository->getAll();
|
||||
|
||||
/** @var Rule $rule */
|
||||
foreach ($rules as $rule) {
|
||||
$this->updateRule($bill, $rule, $oldData);
|
||||
}
|
||||
}
|
||||
|
||||
private function updateRule(Bill $bill, Rule $rule, array $oldData): void
|
||||
{
|
||||
$triggers = ['bill_is', 'bill_ends', 'bill_starts', 'bill_contains'];
|
||||
|
||||
/** @var RuleTrigger $trigger */
|
||||
foreach ($rule->ruleTriggers as $trigger) {
|
||||
if (in_array($trigger->trigger_type, $triggers, true) && $trigger->trigger_value === $oldData['name']) {
|
||||
Log::debug(sprintf('Updated trigger #%d in rule #%d to new subscription name', $trigger->id, $rule->id));
|
||||
$trigger->trigger_value = $bill->name;
|
||||
$trigger->save();
|
||||
}
|
||||
}
|
||||
|
||||
/** @var RuleAction $action */
|
||||
foreach ($rule->ruleActions as $action) {
|
||||
if ('link_to_bill' === $action->action_type && $action->action_value === $oldData['name']) {
|
||||
Log::debug(sprintf('Updated action #%d in rule #%d to new subscription name', $action->id, $rule->id));
|
||||
$action->action_value = $bill->name;
|
||||
$action->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,21 +104,13 @@ trait SupportsGroupProcessingTrait
|
||||
$repository->deleteStatisticsForType(Category::class, $objects->categories, $dates);
|
||||
$repository->deleteStatisticsForType(Tag::class, $objects->tags, $dates);
|
||||
|
||||
// remove if no stuff present:
|
||||
// remove for no tag, no cat, etc.
|
||||
if (0 === $objects->budgets->count()) {
|
||||
Log::debug('No budgets, delete "no_category" stats.');
|
||||
$repository->deleteStatisticsForPrefix('no_budget', $dates);
|
||||
}
|
||||
if (0 === $objects->categories->count()) {
|
||||
Log::debug('No categories, delete "no_category" stats.');
|
||||
$repository->deleteStatisticsForPrefix('no_category', $dates);
|
||||
}
|
||||
if (0 === $objects->tags->count()) {
|
||||
Log::debug('No tags, delete "no_category" stats.');
|
||||
$repository->deleteStatisticsForPrefix('no_tag', $dates);
|
||||
}
|
||||
Log::debug('Done with remove period statistics for all objects.');
|
||||
// remove generic statistics:
|
||||
$repository->deleteStatisticsForPrefix('all_', $dates);
|
||||
|
||||
// ALWAYS remove for no tag, no cat, etc.
|
||||
$repository->deleteStatisticsForPrefix('no_budget', $dates);
|
||||
$repository->deleteStatisticsForPrefix('no_category', $dates);
|
||||
$repository->deleteStatisticsForPrefix('no_tag', $dates);
|
||||
}
|
||||
|
||||
private function collectDatesFromJournals(Collection $journals): Collection
|
||||
|
||||
@@ -76,8 +76,27 @@ class ChecksForNewVersion implements ShouldQueue
|
||||
// last check time was more than a week ago.
|
||||
Log::debug('Have not checked for a new version in a week!');
|
||||
$release = $this->getLatestRelease();
|
||||
$level = 'info';
|
||||
$message = trans('firefly.no_new_release_available');
|
||||
if ('' !== $release->getError()) {
|
||||
$level = 'error';
|
||||
$message = $release->getError();
|
||||
}
|
||||
if ($release->isNewVersionAvailable()) {
|
||||
// if running develop, slightly different message.
|
||||
if (str_contains(config('firefly.version'), 'develop')) {
|
||||
$message = trans('firefly.update_current_dev_older', ['version' => config('firefly.version'), 'new_version' => $release->getNewVersion()]);
|
||||
}
|
||||
if (!str_contains(config('firefly.version'), 'develop')) {
|
||||
$message = trans('firefly.update_new_version_alert', [
|
||||
'your_version' => config('firefly.version'),
|
||||
'new_version' => $release->getNewVersion(),
|
||||
'date' => $release->getPublishedAt()->format('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
session()->flash($release['level'], $release['message']);
|
||||
session()->flash($level, $message);
|
||||
FireflyConfig::set('last_update_check', Carbon::now()->getTimestamp());
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class NotifiesAboutNewInvitation implements ShouldQueue
|
||||
$url = route('invite', [$invitee->invite_code]);
|
||||
|
||||
try {
|
||||
Mail::to($email)->send(new InvitationMail($invitee, $admin, $url));
|
||||
Mail::to($email)->send(new InvitationMail($email, $admin, $url));
|
||||
} catch (Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
|
||||
@@ -149,12 +149,10 @@ class RecalculatesPrimaryCurrencyAmounts
|
||||
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->where('transaction_journals.user_group_id', $userGroup->id)
|
||||
->where(static function (Builder $q): void {
|
||||
$q
|
||||
->whereNotNull('native_amount')
|
||||
->orWhereNotNull('native_foreign_amount')
|
||||
->orWhere('native_amount', '!=', '')
|
||||
->orWhere('native_foreign_amount', '!=', '')
|
||||
;
|
||||
$q->whereNotNull('native_amount')->orWhereNotNull('native_foreign_amount');
|
||||
if ('pgsql' !== config('database.default')) {
|
||||
$q->orWhere('native_amount', '!=', '')->orWhere('native_foreign_amount', '!=', '');
|
||||
}
|
||||
})
|
||||
->update(['native_amount' => null, 'native_foreign_amount' => null])
|
||||
;
|
||||
|
||||
@@ -81,7 +81,7 @@ class SendsTestNotification
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('Will send %s as a notification.', $class));
|
||||
NotificationSender::send($event->user, new $class());
|
||||
NotificationSender::send($event->{$type}, new $class());
|
||||
Log::debug(sprintf('If you see no errors above this line, test notification was sent over channel "%s"', $event->channel));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Deprecated;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
@@ -32,65 +31,9 @@ class AccountType extends Model
|
||||
{
|
||||
use ReturnsIntegerIdTrait;
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string ASSET = 'Asset account';
|
||||
protected $casts = ['created_at' => 'datetime', 'updated_at' => 'datetime'];
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string BENEFICIARY = 'Beneficiary account';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string CASH = 'Cash account';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string CREDITCARD = 'Credit card';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string DEBT = 'Debt';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string DEFAULT = 'Default account';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string EXPENSE = 'Expense account';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string IMPORT = 'Import account';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string INITIAL_BALANCE = 'Initial balance account';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string LIABILITY_CREDIT = 'Liability credit account';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string LOAN = 'Loan';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string MORTGAGE = 'Mortgage';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string RECONCILIATION = 'Reconciliation account';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string REVENUE = 'Revenue account';
|
||||
|
||||
protected $casts = ['created_at' => 'datetime', 'updated_at' => 'datetime'];
|
||||
|
||||
protected $fillable = ['type'];
|
||||
protected $fillable = ['type'];
|
||||
|
||||
public function accounts(): HasMany
|
||||
{
|
||||
|
||||
@@ -24,7 +24,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Deprecated;
|
||||
use FireflyIII\Handlers\Observer\AutoBudgetObserver;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
@@ -39,20 +38,8 @@ class AutoBudget extends Model
|
||||
use ReturnsIntegerIdTrait;
|
||||
use SoftDeletes;
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const int AUTO_BUDGET_ADJUSTED = 3;
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const int AUTO_BUDGET_RESET = 1;
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const int AUTO_BUDGET_ROLLOVER = 2;
|
||||
|
||||
protected $casts = ['amount' => 'string', 'native_amount' => 'string'];
|
||||
protected $fillable = ['budget_id', 'amount', 'period', 'native_amount'];
|
||||
protected $casts = ['amount' => 'string', 'native_amount' => 'string'];
|
||||
protected $fillable = ['budget_id', 'amount', 'period', 'native_amount'];
|
||||
|
||||
public function budget(): BelongsTo
|
||||
{
|
||||
|
||||
@@ -24,7 +24,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Deprecated;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -36,23 +35,7 @@ class RecurrenceRepetition extends Model
|
||||
use ReturnsIntegerIdTrait;
|
||||
use SoftDeletes;
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const int WEEKEND_DO_NOTHING = 1;
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const int WEEKEND_SKIP_CREATION = 2;
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const int WEEKEND_TO_FRIDAY = 3;
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const int WEEKEND_TO_MONDAY = 4;
|
||||
|
||||
protected $casts = [
|
||||
protected $casts = [
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
@@ -62,9 +45,9 @@ class RecurrenceRepetition extends Model
|
||||
'weekend' => 'int',
|
||||
];
|
||||
|
||||
protected $fillable = ['recurrence_id', 'weekend', 'repetition_type', 'repetition_moment', 'repetition_skip'];
|
||||
protected $fillable = ['recurrence_id', 'weekend', 'repetition_type', 'repetition_moment', 'repetition_skip'];
|
||||
|
||||
protected $table = 'recurrences_repetitions';
|
||||
protected $table = 'recurrences_repetitions';
|
||||
|
||||
public function recurrence(): BelongsTo
|
||||
{
|
||||
|
||||
@@ -30,7 +30,6 @@ use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Attributes\Scope;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
@@ -39,7 +38,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
#[ObservedBy([TransactionObserver::class])]
|
||||
class Transaction extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use ReturnsIntegerIdTrait;
|
||||
use SoftDeletes;
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Attributes\Scope;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
@@ -53,7 +52,6 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
#[ObservedBy([DeletedTransactionJournalObserver::class])]
|
||||
class TransactionJournal extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use ReturnsIntegerIdTrait;
|
||||
use ReturnsIntegerUserIdTrait;
|
||||
use SoftDeletes;
|
||||
|
||||
@@ -23,7 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Deprecated;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -36,36 +35,8 @@ class TransactionType extends Model
|
||||
use ReturnsIntegerIdTrait;
|
||||
use SoftDeletes;
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string DEPOSIT = 'Deposit';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string INVALID = 'Invalid';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string LIABILITY_CREDIT = 'Liability credit';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string OPENING_BALANCE = 'Opening balance';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string RECONCILIATION = 'Reconciliation';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string TRANSFER = 'Transfer';
|
||||
|
||||
/** @deprecated */
|
||||
#[Deprecated]
|
||||
public const string WITHDRAWAL = 'Withdrawal';
|
||||
|
||||
protected $casts = ['created_at' => 'datetime', 'updated_at' => 'datetime', 'deleted_at' => 'datetime'];
|
||||
protected $fillable = ['type'];
|
||||
protected $casts = ['created_at' => 'datetime', 'updated_at' => 'datetime', 'deleted_at' => 'datetime'];
|
||||
protected $fillable = ['type'];
|
||||
|
||||
/**
|
||||
* Route binder. Converts the key in the URL to the specified object (or throw 404).
|
||||
|
||||
@@ -52,6 +52,7 @@ class NotificationSender
|
||||
|
||||
return;
|
||||
}
|
||||
Log::error('Could not send notification :(.');
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* AccountPolicy.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\Policies;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class AccountPolicy
|
||||
{
|
||||
public function create(): bool
|
||||
{
|
||||
return auth()->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO needs better authentication, also for group.
|
||||
*/
|
||||
public function view(User $user, Account $account): bool
|
||||
{
|
||||
return auth()->check() && $user->id === $account->user_id;
|
||||
}
|
||||
|
||||
public function viewAccountBalances(User $user, Account $account): bool
|
||||
{
|
||||
return $this->view($user, $account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Everybody can do this, but selection should limit to user.
|
||||
*/
|
||||
public function viewAny(): bool
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
|
||||
return auth()->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Everybody can do this, but selection should limit to user.
|
||||
*/
|
||||
public function viewUser(User $user, Account $account): bool
|
||||
{
|
||||
return $this->view($user, $account);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* BalancePolicy.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\Policies;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\User;
|
||||
|
||||
class BalancePolicy
|
||||
{
|
||||
/**
|
||||
* TODO needs better authentication.
|
||||
*/
|
||||
public function view(User $user, Account $account): bool
|
||||
{
|
||||
return auth()->check() && $user->id === $account->user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Everybody can do this, but selection should limit to user.
|
||||
*/
|
||||
public function viewAny(): bool
|
||||
{
|
||||
return auth()->check();
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* UserPolicy.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\Policies;
|
||||
|
||||
use FireflyIII\User;
|
||||
|
||||
class UserPolicy
|
||||
{
|
||||
/**
|
||||
* TODO needs better authentication.
|
||||
*/
|
||||
public function view(User $user, User $user1): bool
|
||||
{
|
||||
return true;
|
||||
|
||||
// return auth()->check() && $user->id === $account->user_id;
|
||||
}
|
||||
|
||||
public function viewAccounts(User $user): bool
|
||||
{
|
||||
return true;
|
||||
|
||||
// return auth()->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Everybody can do this, but selection should limit to user.
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
public function viewAny(): bool
|
||||
{
|
||||
return true;
|
||||
|
||||
// return auth()->check();
|
||||
}
|
||||
}
|
||||
@@ -34,10 +34,6 @@ use Laravel\Passport\Passport;
|
||||
*/
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
protected $policies = [
|
||||
// 'FireflyIII\Model' => 'FireflyIII\Policies\ModelPolicy',
|
||||
];
|
||||
|
||||
/**
|
||||
* Register any authentication / authorization services.
|
||||
*
|
||||
|
||||
@@ -53,6 +53,7 @@ use FireflyIII\Repositories\UserGroup\UserGroupRepository;
|
||||
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
|
||||
use FireflyIII\Repositories\Webhook\WebhookRepository;
|
||||
use FireflyIII\Repositories\Webhook\WebhookRepositoryInterface;
|
||||
use FireflyIII\Services\FireflyIIIOrg\Update\GitHubUpdateRequest;
|
||||
use FireflyIII\Services\FireflyIIIOrg\Update\UpdateRequest;
|
||||
use FireflyIII\Services\FireflyIIIOrg\Update\UpdateRequestInterface;
|
||||
use FireflyIII\Services\Password\PwndVerifierV2;
|
||||
@@ -191,7 +192,8 @@ class FireflyServiceProvider extends ServiceProvider
|
||||
$this->app->bind(PopupReportInterface::class, PopupReport::class);
|
||||
$this->app->bind(ReportHelperInterface::class, ReportHelper::class);
|
||||
$this->app->bind(FiscalHelperInterface::class, FiscalHelper::class);
|
||||
$this->app->bind(UpdateRequestInterface::class, UpdateRequest::class);
|
||||
// $this->app->bind(UpdateRequestInterface::class, UpdateRequest::class);
|
||||
$this->app->bind(UpdateRequestInterface::class, GitHubUpdateRequest::class);
|
||||
|
||||
// webhooks:
|
||||
$this->app->bind(MessageGeneratorInterface::class, StandardMessageGenerator::class);
|
||||
|
||||
@@ -23,7 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Providers;
|
||||
|
||||
use FireflyIII\Http\Middleware\StartFireflySession;
|
||||
use FireflyIII\Http\Middleware\StartFireflyIIISession;
|
||||
use Illuminate\Session\SessionManager;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Override;
|
||||
@@ -43,7 +43,7 @@ class FireflySessionProvider extends ServiceProvider
|
||||
|
||||
$this->registerSessionDriver();
|
||||
|
||||
$this->app->singleton(StartFireflySession::class);
|
||||
$this->app->singleton(StartFireflyIIISession::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,20 +42,18 @@ class RouteServiceProvider extends ServiceProvider
|
||||
#[Override]
|
||||
public function boot(): void
|
||||
{
|
||||
$this->routes(function (): void {
|
||||
Route::prefix('api')
|
||||
->middleware('api')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/api.php'))
|
||||
;
|
||||
|
||||
Route::prefix('api/v1/cron')
|
||||
->middleware('api_basic')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/api-noauth.php'))
|
||||
;
|
||||
|
||||
Route::middleware('web')->namespace($this->namespace)->group(base_path('routes/web.php'));
|
||||
});
|
||||
// $this->routes(function (): void {
|
||||
// Route::prefix('api')
|
||||
// ->middleware('api')
|
||||
// ->namespace($this->namespace)
|
||||
// ->group(base_path('routes/api.php'))
|
||||
// ;
|
||||
// Route::prefix('api/v1/cron')
|
||||
// ->middleware('api_basic')
|
||||
// ->namespace($this->namespace)
|
||||
// ->group(base_path('routes/api-noauth.php'))
|
||||
// ;
|
||||
// Route::middleware('web')->namespace($this->namespace)->group(base_path('routes/web.php'));
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Providers;
|
||||
|
||||
use FireflyIII\Http\Middleware\StartFireflySession;
|
||||
use FireflyIII\Http\Middleware\StartFireflyIIISession;
|
||||
use Illuminate\Session\SessionServiceProvider as BaseSessionServiceProvider;
|
||||
use Override;
|
||||
|
||||
@@ -42,6 +42,6 @@ class SessionServiceProvider extends BaseSessionServiceProvider
|
||||
|
||||
$this->registerSessionDriver();
|
||||
|
||||
$this->app->singleton(StartFireflySession::class);
|
||||
$this->app->singleton(StartFireflyIIISession::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
|
||||
foreach ($set as $bill) {
|
||||
if ($bill->order !== $current) {
|
||||
$bill->order = $current;
|
||||
$bill->save();
|
||||
$bill->saveQuietly();
|
||||
}
|
||||
++$current;
|
||||
}
|
||||
@@ -613,7 +613,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
|
||||
public function setOrder(Bill $bill, int $order): void
|
||||
{
|
||||
$bill->order = $order;
|
||||
$bill->save();
|
||||
$bill->saveQuietly();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -105,6 +105,22 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
|
||||
return $this->user->availableBudgets->find($id);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function findInRange(TransactionCurrency $currency, Carbon $start, Carbon $end): Collection
|
||||
{
|
||||
/** @var null|AvailableBudget */
|
||||
return $this->user
|
||||
->availableBudgets()
|
||||
->where('transaction_currency_id', $currency->id)
|
||||
// start date OR end date must be the same or equal to start or END respectively.
|
||||
->where(function ($q1) use ($start, $end): void {
|
||||
$q1->whereBetween('start_date', [$start, $end]);
|
||||
$q1->orWhereBetween('end_date', [$start, $end]);
|
||||
})
|
||||
->get(['available_budgets.*'])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of all available budgets (in all currencies) (for the selected period).
|
||||
*/
|
||||
|
||||
@@ -62,6 +62,11 @@ interface AvailableBudgetRepositoryInterface
|
||||
|
||||
public function findById(int $id): ?AvailableBudget;
|
||||
|
||||
/**
|
||||
* Find existing ABs in this exact time range.
|
||||
*/
|
||||
public function findInRange(TransactionCurrency $currency, Carbon $start, Carbon $end): Collection;
|
||||
|
||||
/**
|
||||
* Return a list of all available budgets (in all currencies) (for the selected period).
|
||||
*/
|
||||
|
||||
@@ -52,6 +52,7 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface, UserGroupIn
|
||||
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value])->withoutCategory();
|
||||
if ($accounts instanceof Collection && $accounts->count() > 0) {
|
||||
$collector->setAccounts($accounts);
|
||||
$collector->excludeDestinationAccounts($accounts); // to exclude withdrawals to liabilities.
|
||||
}
|
||||
$journals = $collector->getExtractedJournals();
|
||||
$array = [];
|
||||
@@ -97,6 +98,7 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface, UserGroupIn
|
||||
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::DEPOSIT->value])->withoutCategory();
|
||||
if ($accounts instanceof Collection && $accounts->count() > 0) {
|
||||
$collector->setAccounts($accounts);
|
||||
$collector->excludeSourceAccounts($accounts); // to prevent income from liabilities.
|
||||
}
|
||||
$journals = $collector->getExtractedJournals();
|
||||
$array = [];
|
||||
|
||||
@@ -48,6 +48,47 @@ class JournalRepository implements JournalRepositoryInterface, UserGroupInterfac
|
||||
{
|
||||
use UserGroupTrait;
|
||||
|
||||
#[Override]
|
||||
public function countByDescription(string $value, bool $includeDeleted): int
|
||||
{
|
||||
$search = $this->user->transactionJournals()->where('description', $value);
|
||||
if ($includeDeleted) {
|
||||
$search->withTrashed();
|
||||
}
|
||||
|
||||
return $search->count();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function countByMeta(string $field, string $value, bool $includeDeleted): int
|
||||
{
|
||||
$search = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
|
||||
->where('name', $field)
|
||||
->where('data', json_encode($value))
|
||||
->where('transaction_journals.user_id', $this->user->id)
|
||||
;
|
||||
if ($includeDeleted) {
|
||||
$search->withTrashed();
|
||||
}
|
||||
|
||||
return $search->count();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function countByNotes(string $value, bool $includeDeleted): int
|
||||
{
|
||||
$search = Note::where('noteable_type', TransactionJournal::class)
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'notes.noteable_id')
|
||||
->where('transaction_journals.user_id', $this->user->id)
|
||||
->where('text', 'LIKE', sprintf('%%%s%%', $value))
|
||||
;
|
||||
if ($includeDeleted) {
|
||||
$search->withTrashed();
|
||||
}
|
||||
|
||||
return $search->count();
|
||||
}
|
||||
|
||||
public function destroyGroup(TransactionGroup $transactionGroup): void
|
||||
{
|
||||
/** @var TransactionGroupDestroyService $service */
|
||||
|
||||
@@ -47,6 +47,12 @@ use Illuminate\Support\Collection;
|
||||
*/
|
||||
interface JournalRepositoryInterface
|
||||
{
|
||||
public function countByDescription(string $value, bool $includeDeleted): int;
|
||||
|
||||
public function countByMeta(string $field, string $value, bool $includeDeleted): int;
|
||||
|
||||
public function countByNotes(string $value, bool $includeDeleted): int;
|
||||
|
||||
/**
|
||||
* Deletes a transaction group.
|
||||
*/
|
||||
|
||||
@@ -49,6 +49,9 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface, U
|
||||
#[Override]
|
||||
public function allInRangeForPrefix(string $prefix, Carbon $start, Carbon $end): Collection
|
||||
{
|
||||
Log::debug(sprintf('Collect all statistics where type starts with "%s"', $prefix));
|
||||
Log::debug(sprintf('Between %s and %s', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
|
||||
return $this->userGroup
|
||||
->periodStatistics()
|
||||
->where('type', 'LIKE', sprintf('%s%%', $prefix))
|
||||
@@ -156,6 +159,7 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface, U
|
||||
int $count,
|
||||
string $amount
|
||||
): PeriodStatistic {
|
||||
Log::debug(sprintf('Store as type "%s"', sprintf('%s_%s', $prefix, $type)));
|
||||
$stat = new PeriodStatistic();
|
||||
$stat->transaction_currency_id = $currencyId;
|
||||
$stat->user_group_id = $this->getUserGroup()->id;
|
||||
|
||||
@@ -102,7 +102,7 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
|
||||
{
|
||||
return $this->user
|
||||
->rules()
|
||||
->with(['ruleGroup'])
|
||||
->with(['ruleGroup', 'ruleTriggers', 'ruleActions'])
|
||||
->get()
|
||||
;
|
||||
}
|
||||
|
||||
226
app/Services/FireflyIIIOrg/Update/GitHubUpdateRequest.php
Normal file
226
app/Services/FireflyIIIOrg/Update/GitHubUpdateRequest.php
Normal file
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* GitHubUpdateRequest.php
|
||||
* Copyright (c) 2026 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\Services\FireflyIIIOrg\Update;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Override;
|
||||
|
||||
class GitHubUpdateRequest implements UpdateRequestInterface
|
||||
{
|
||||
private string $currentVersion = '1.0.0';
|
||||
private Carbon $currentBuild;
|
||||
private string $channel = 'stable';
|
||||
private bool $localDebug = false;
|
||||
|
||||
#[Override]
|
||||
public function getUpdateInformation(string $currentVersion, Carbon $currentBuild, string $channel): UpdateResponse
|
||||
{
|
||||
$this->currentVersion = $currentVersion;
|
||||
$this->channel = $channel;
|
||||
$this->currentBuild = $currentBuild;
|
||||
Log::debug(sprintf('Now in getUpdateInformation(%s, %s)', $currentVersion, $channel));
|
||||
|
||||
$response = new UpdateResponse();
|
||||
$releases = $this->getReleases();
|
||||
$filtered = $this->filterReleases($releases);
|
||||
|
||||
Log::debug(sprintf('Left with %d release(s) to compare', count($filtered)));
|
||||
|
||||
if (0 === count($filtered)) {
|
||||
$response->setNewVersionAvailable(false);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
$newest = $this->getNewest($filtered);
|
||||
|
||||
Log::debug(sprintf('Newest release is "%s" released on %s', $newest['version'], $newest['published_at']->format('Y-m-d H:i')));
|
||||
|
||||
$response->setNewVersion($newest['version']);
|
||||
$response->setPublishedAt($newest['published_at']);
|
||||
|
||||
// main question, is this release newer than the current one?
|
||||
// if current version is "develop", compare date.
|
||||
if (str_contains($currentVersion, 'develop')) {
|
||||
Log::debug(sprintf('Compare with current version "%s", built on %s', $currentVersion, $currentBuild->format('Y-m-d H:i')));
|
||||
if ($currentBuild->lt($newest['published_at']) && $currentBuild->diffInMinutes($newest['published_at']) > 10) {
|
||||
Log::debug(sprintf(
|
||||
'Current build %s is older than newest build %s, so a new release is available.',
|
||||
$currentBuild->format('Y-m-d H:i:s e'),
|
||||
$newest['published_at']->format('Y-m-d H:i:s e')
|
||||
));
|
||||
$response->setNewVersionAvailable(true);
|
||||
|
||||
return $response;
|
||||
}
|
||||
if ($currentBuild->gt($newest['published_at'])) {
|
||||
Log::debug(sprintf(
|
||||
'Current build %s is newer than newest build %s, so NO new release is available.',
|
||||
$currentBuild->format('Y-m-d H:i'),
|
||||
$newest['published_at']->format('Y-m-d H:i')
|
||||
));
|
||||
$response->setNewVersionAvailable(false);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
// if not, compare version.
|
||||
$res = version_compare($newest['version'], $currentVersion);
|
||||
if ($res < 1) {
|
||||
$response->setNewVersionAvailable(false);
|
||||
|
||||
return $response;
|
||||
}
|
||||
if (1 === $res) {
|
||||
$response->setNewVersionAvailable(true);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function filterReleases(array $releases): array
|
||||
{
|
||||
$return = [];
|
||||
$weekAgo = now()->subWeek();
|
||||
|
||||
/** @var array $release */
|
||||
foreach ($releases as $release) {
|
||||
if ($release['published_at']->lte($this->currentBuild)) {
|
||||
Log::debug(sprintf('Skip older version "%s"', $release['version']));
|
||||
|
||||
continue;
|
||||
}
|
||||
// new version must be at least a week old, unless it is a develop release.
|
||||
if ($release['published_at']->gt($weekAgo) && !str_contains($release['version'], 'develop')) {
|
||||
Log::debug(sprintf('Skip too new version "%s"', $release['version']));
|
||||
|
||||
continue;
|
||||
}
|
||||
// if channel is stable, and version is "alpha", continue.
|
||||
// if channel is stable, and version is "beta", continue.
|
||||
// if channel is beta, and version is "alpha", continue.
|
||||
if (('stable' === $this->channel || 'beta' === $this->channel) && str_contains($release['version'], 'alpha')) {
|
||||
Log::debug(sprintf('Skip version "%s"', $release['version']));
|
||||
|
||||
continue;
|
||||
}
|
||||
if ('stable' === $this->channel && str_contains($release['version'], 'beta')) {
|
||||
Log::debug(sprintf('Skip version "%s"', $release['version']));
|
||||
|
||||
continue;
|
||||
}
|
||||
$return[] = $release;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getNewest(array $releases): array
|
||||
{
|
||||
$latest = ['version' => '1.0.0', 'published_at' => now(config('app.timezone'))->startOfYear()];
|
||||
foreach ($releases as $release) {
|
||||
Log::debug(sprintf('Comparing current version "%s" with latest version "%s"', $release['version'], $latest['version']));
|
||||
if (str_contains($release['version'], 'develop') || str_contains($latest['version'], 'develop')) {
|
||||
Log::debug('Compare based on build time.');
|
||||
// compare build times.
|
||||
if ($latest['published_at']->lt($release['published_at'])) {
|
||||
Log::debug(sprintf(
|
||||
'Current date "%s" is newer than latest date "%s"',
|
||||
$release['published_at']->format('Y-m-d H:i'),
|
||||
$latest['published_at']->format('Y-m-d H:i')
|
||||
));
|
||||
$latest = $release;
|
||||
}
|
||||
}
|
||||
if (!str_contains($release['version'], 'develop') && !str_contains($latest['version'], 'develop')) {
|
||||
Log::debug('[a] Compare based on version.');
|
||||
// compare version.
|
||||
$res = version_compare($release['version'], $latest['version']);
|
||||
if (1 === $res) {
|
||||
Log::debug(sprintf('Version "%s" is newer than version "%s".', $release['version'], $latest['version']));
|
||||
$latest = $release;
|
||||
}
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Latest version seems to be "%s", released at %s', $latest['version'], $latest['published_at']->format('Y-m-d H:i e')));
|
||||
|
||||
return $latest;
|
||||
}
|
||||
|
||||
private function getReleases(): array
|
||||
{
|
||||
$client = new Client();
|
||||
$opts = ['headers' => ['User-Agent' => 'FireflyIII/'.config('firefly.version')]];
|
||||
$return = [];
|
||||
$body = '';
|
||||
if ($this->localDebug && file_exists('json.json')) {
|
||||
$body = file_get_contents('json.json');
|
||||
}
|
||||
if (!$this->localDebug) {
|
||||
try {
|
||||
$res = $client->get('https://api.github.com/repos/firefly-iii/firefly-iii/releases', $opts);
|
||||
} catch (ClientException $e) {
|
||||
Log::error($e->getMessage());
|
||||
|
||||
return [];
|
||||
}
|
||||
Log::debug('Successfully contacted GitHub');
|
||||
$body = (string) $res->getBody();
|
||||
}
|
||||
|
||||
if (!json_validate($body)) {
|
||||
Log::debug('GitHub returned invalid JSON');
|
||||
|
||||
return [];
|
||||
}
|
||||
$json = json_decode($body, true);
|
||||
|
||||
/** @var array $release */
|
||||
foreach ($json as $release) {
|
||||
$version = $release['tag_name'];
|
||||
$version = 'v' === $version[0] ? substr($version, 1) : $version;
|
||||
$published = Carbon::parse($release['published_at'], config('app.timezone'));
|
||||
|
||||
// always skip "develop" releases, unless user is also running develop.
|
||||
if (str_contains($version, 'develop') && !str_contains($this->currentVersion, 'develop')) {
|
||||
Log::debug(sprintf('Skip version "%s"', $release['tag_name']));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$return[] = ['version' => $version, 'published_at' => $published];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* UpdateRequest.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\Services\FireflyIIIOrg\Update;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Events\Security\System\SystemFoundNewVersionOnline;
|
||||
use FireflyIII\Support\System\IsOldVersion;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use JsonException;
|
||||
|
||||
use function Safe\json_decode;
|
||||
|
||||
/**
|
||||
* Class UpdateRequest
|
||||
*/
|
||||
class UpdateRequest implements UpdateRequestInterface
|
||||
{
|
||||
use IsOldVersion;
|
||||
|
||||
public function getUpdateInformation(string $channel): array
|
||||
{
|
||||
Log::debug(sprintf('Now in getUpdateInformation(%s)', $channel));
|
||||
$information = ['level' => 'error', 'message' => (string) trans('firefly.unknown_error')];
|
||||
|
||||
// try to get array from update server:
|
||||
$updateInfo = $this->contactServer($channel);
|
||||
if ('error' === $updateInfo['level']) {
|
||||
Log::error('Update information contains an error.');
|
||||
Log::error($updateInfo['message']);
|
||||
$information['message'] = $updateInfo['message'];
|
||||
|
||||
return $information;
|
||||
}
|
||||
|
||||
// if no error, parse the result and return
|
||||
return $this->parseResult($updateInfo);
|
||||
}
|
||||
|
||||
private function contactServer(string $channel): array
|
||||
{
|
||||
Log::debug(sprintf('Now in contactServer(%s)', $channel));
|
||||
// always fall back to current version:
|
||||
$return = [
|
||||
'version' => config('firefly.version'),
|
||||
'date' => today(config('app.timezone'))->startOfDay(),
|
||||
'level' => 'error',
|
||||
'message' => (string) trans('firefly.unknown_error'),
|
||||
];
|
||||
|
||||
$url = config('firefly.update_endpoint');
|
||||
Log::debug(sprintf('Going to call %s', $url));
|
||||
|
||||
try {
|
||||
$client = new Client();
|
||||
$options = ['headers' => ['User-Agent' => sprintf('FireflyIII/%s/%s', config('firefly.version'), $channel)], 'timeout' => 3.1415];
|
||||
$res = $client->request('GET', $url, $options);
|
||||
} catch (GuzzleException $e) {
|
||||
Log::error('Ran into Guzzle error.');
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
$return['message'] = sprintf('Guzzle: %s', strip_tags($e->getMessage()));
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
if (200 !== $res->getStatusCode()) {
|
||||
Log::error(sprintf('Response status from server is %d.', $res->getStatusCode()));
|
||||
Log::error((string) $res->getBody());
|
||||
$return['message'] = sprintf('Error: %d', $res->getStatusCode());
|
||||
|
||||
return $return;
|
||||
}
|
||||
$body = (string) $res->getBody();
|
||||
|
||||
try {
|
||||
$json = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException) {
|
||||
Log::error('Body is not valid JSON');
|
||||
Log::error($body);
|
||||
$return['message'] = 'Invalid JSON :(';
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
if (!array_key_exists($channel, $json['firefly_iii'])) {
|
||||
Log::error(sprintf('No valid update channel "%s"', $channel));
|
||||
Log::error($body);
|
||||
$return['message'] = sprintf('Unknown update channel "%s" :(', $channel);
|
||||
}
|
||||
|
||||
// parse response a bit. No message yet.
|
||||
$response = $json['firefly_iii'][$channel];
|
||||
$date = Carbon::createFromFormat('Y-m-d', $response['date']);
|
||||
if (!$date instanceof Carbon) {
|
||||
$date = today(config('app.timezone'));
|
||||
}
|
||||
$return['version'] = $response['version'];
|
||||
$return['level'] = 'success';
|
||||
$return['date'] = $date->startOfDay();
|
||||
|
||||
Log::info('Response from update server', $response);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO make shorter
|
||||
*/
|
||||
private function parseResult(array $information): array
|
||||
{
|
||||
Log::debug('Now in parseResult()', $information);
|
||||
$current = (string) config('firefly.version');
|
||||
$latest = (string) $information['version'];
|
||||
|
||||
// strip the 'v' from the version if it's there.
|
||||
if (str_starts_with($latest, 'v')) {
|
||||
$latest = substr($latest, 1);
|
||||
}
|
||||
if (str_starts_with($current, 'develop')) {
|
||||
return $this->parseResultDevelop($current, $latest);
|
||||
}
|
||||
|
||||
$compare = version_compare($latest, $current);
|
||||
|
||||
Log::debug(sprintf('Current version is "%s", latest is "%s", result is: %d', $current, $latest, $compare));
|
||||
|
||||
// -1: you're running a newer version:
|
||||
if (-1 === $compare) {
|
||||
return $this->runsNewerVersion($current, $latest);
|
||||
}
|
||||
// running the current version:
|
||||
if (0 === $compare) {
|
||||
return $this->runsSameVersion($current);
|
||||
}
|
||||
|
||||
// a newer version is available!
|
||||
/** @var Carbon $released */
|
||||
$released = $information['date'];
|
||||
$isBeta = $information['is_beta'] ?? false;
|
||||
$isAlpha = $information['is_alpha'] ?? false;
|
||||
|
||||
// it's new but alpha:
|
||||
if (true === $isAlpha) {
|
||||
return $this->releasedNewAlpha($current, $latest, $released);
|
||||
}
|
||||
|
||||
if (true === $isBeta) {
|
||||
return $this->releasedNewBeta($current, $latest, $released);
|
||||
}
|
||||
|
||||
return $this->releasedNewVersion($current, $latest, $released);
|
||||
}
|
||||
|
||||
private function parseResultDevelop(string $current, string $latest): array
|
||||
{
|
||||
Log::debug(sprintf('User is running develop version "%s"', $current));
|
||||
$compare = $this->compareDevelopVersions($current, $latest);
|
||||
$return = [];
|
||||
|
||||
if (-1 === $compare) {
|
||||
$return['level'] = 'info';
|
||||
$return['message'] = (string) trans('firefly.update_current_dev_older', ['version' => $current, 'new_version' => $latest]);
|
||||
|
||||
return $return;
|
||||
}
|
||||
$return['level'] = 'info';
|
||||
$return['message'] = (string) trans('firefly.update_current_dev_newer', ['version' => $current, 'new_version' => $latest]);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function releasedNewAlpha(string $current, string $latest, Carbon $date): array
|
||||
{
|
||||
Log::debug('New release is also a alpha!');
|
||||
$message = (string) trans('firefly.update_new_version_alert', [
|
||||
'your_version' => $current,
|
||||
'new_version' => $latest,
|
||||
'date' => $date->isoFormat((string) trans('config.month_and_day_js')),
|
||||
]);
|
||||
|
||||
return ['level' => 'success', 'message' => sprintf('%s %s', $message, trans('firefly.update_version_alpha'))];
|
||||
}
|
||||
|
||||
private function releasedNewBeta(string $current, string $latest, Carbon $date): array
|
||||
{
|
||||
Log::debug('New release is also a beta!');
|
||||
$message = (string) trans('firefly.update_new_version_alert', [
|
||||
'your_version' => $current,
|
||||
'new_version' => $latest,
|
||||
'date' => $date->isoFormat((string) trans('config.month_and_day_js')),
|
||||
]);
|
||||
|
||||
return ['level' => 'success', 'message' => sprintf('%s %s', $message, trans('firefly.update_version_beta'))];
|
||||
}
|
||||
|
||||
private function releasedNewVersion(string $current, string $latest, Carbon $date): array
|
||||
{
|
||||
Log::debug('New release is old enough.');
|
||||
$message = (string) trans('firefly.update_new_version_alert', [
|
||||
'your_version' => $current,
|
||||
'new_version' => $latest,
|
||||
'date' => $date->isoFormat((string) trans('config.month_and_day_js')),
|
||||
]);
|
||||
Log::debug('New release is here!', [$message]);
|
||||
event(new SystemFoundNewVersionOnline($message));
|
||||
|
||||
return ['level' => 'success', 'message' => $message];
|
||||
}
|
||||
|
||||
private function runsNewerVersion(string $current, string $latest): array
|
||||
{
|
||||
$return = [
|
||||
'level' => 'info',
|
||||
'message' => (string) trans('firefly.update_newer_version_alert', ['your_version' => $current, 'new_version' => $latest]),
|
||||
];
|
||||
Log::debug('User is running a newer version', $return);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function runsSameVersion(string $current): array
|
||||
{
|
||||
$return = ['level' => 'info', 'message' => (string) trans('firefly.update_current_version_alert', ['version' => $current])];
|
||||
Log::debug('User is the current version.', $return);
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\FireflyIIIOrg\Update;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* Interface UpdateRequestInterface
|
||||
*/
|
||||
interface UpdateRequestInterface
|
||||
{
|
||||
public function getUpdateInformation(string $channel): array;
|
||||
public function getUpdateInformation(string $currentVersion, Carbon $currentBuild, string $channel): UpdateResponse;
|
||||
}
|
||||
|
||||
75
app/Services/FireflyIIIOrg/Update/UpdateResponse.php
Normal file
75
app/Services/FireflyIIIOrg/Update/UpdateResponse.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* UpdateResponse.php
|
||||
* Copyright (c) 2026 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\Services\FireflyIIIOrg\Update;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
class UpdateResponse
|
||||
{
|
||||
private bool $newVersionAvailable = false;
|
||||
private string $error = '';
|
||||
private string $newVersion = '1.0.0';
|
||||
private Carbon $publishedAt;
|
||||
|
||||
public function getError(): string
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
public function getNewVersion(): string
|
||||
{
|
||||
return $this->newVersion;
|
||||
}
|
||||
|
||||
public function getPublishedAt(): Carbon
|
||||
{
|
||||
return $this->publishedAt;
|
||||
}
|
||||
|
||||
public function isNewVersionAvailable(): bool
|
||||
{
|
||||
return $this->newVersionAvailable;
|
||||
}
|
||||
|
||||
public function setError(string $error): void
|
||||
{
|
||||
$this->error = $error;
|
||||
}
|
||||
|
||||
public function setNewVersion(string $newVersion): void
|
||||
{
|
||||
$this->newVersion = $newVersion;
|
||||
}
|
||||
|
||||
public function setNewVersionAvailable(bool $newVersionAvailable): void
|
||||
{
|
||||
$this->newVersionAvailable = $newVersionAvailable;
|
||||
}
|
||||
|
||||
public function setPublishedAt(Carbon $publishedAt): void
|
||||
{
|
||||
$this->publishedAt = $publishedAt;
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
use FireflyIII\Rules\UniqueIban;
|
||||
use FireflyIII\Support\NullArrayObject;
|
||||
use Illuminate\Database\UniqueConstraintViolationException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Safe\Exceptions\JsonException;
|
||||
|
||||
@@ -227,7 +228,12 @@ trait JournalServiceTrait
|
||||
$set = array_unique($set);
|
||||
Log::debug('End of loop.');
|
||||
Log::debug(sprintf('Total nr. of tags: %d', count($tags)), $tags);
|
||||
$journal->tags()->sync($set);
|
||||
|
||||
try {
|
||||
$journal->tags()->sync($set);
|
||||
} catch (UniqueConstraintViolationException $e) {
|
||||
Log::error(sprintf('Firefly III could not sync tags: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,12 +24,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Internal\Update;
|
||||
|
||||
use FireflyIII\Events\Model\Bill\UpdatedExistingBill;
|
||||
use FireflyIII\Factory\TransactionCurrencyFactory;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\RuleTrigger;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups;
|
||||
use FireflyIII\Services\Internal\Support\BillServiceTrait;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
@@ -50,6 +50,7 @@ class BillUpdateService
|
||||
public function update(Bill $bill, array $data): Bill
|
||||
{
|
||||
$this->user = $bill->user;
|
||||
$oldData = $bill->toArray();
|
||||
|
||||
if (array_key_exists('currency_id', $data) || array_key_exists('currency_code', $data)) {
|
||||
$factory = app(TransactionCurrencyFactory::class);
|
||||
@@ -68,13 +69,6 @@ class BillUpdateService
|
||||
$bill = $this->updateBillProperties($bill, $data);
|
||||
$bill->save();
|
||||
$bill->refresh();
|
||||
// old values
|
||||
$oldData = [
|
||||
'name' => $bill->name,
|
||||
'amount_min' => $bill->amount_min,
|
||||
'amount_max' => $bill->amount_max,
|
||||
'transaction_currency_name' => $bill->transactionCurrency->name,
|
||||
];
|
||||
// update note:
|
||||
if (array_key_exists('notes', $data)) {
|
||||
$this->updateNote($bill, (string) $data['notes']);
|
||||
@@ -90,12 +84,6 @@ class BillUpdateService
|
||||
}
|
||||
}
|
||||
|
||||
// update rule actions.
|
||||
if (array_key_exists('name', $data)) {
|
||||
$this->updateBillActions($bill, $oldData['name'], $data['name']);
|
||||
$this->updateBillTriggers($bill, $oldData, $data);
|
||||
}
|
||||
|
||||
// update using name:
|
||||
if (array_key_exists('object_group_title', $data)) {
|
||||
$objectGroupTitle = $data['object_group_title'] ?? '';
|
||||
@@ -127,6 +115,7 @@ class BillUpdateService
|
||||
$bill->objectGroups()->sync([]);
|
||||
$bill->save();
|
||||
}
|
||||
event(new UpdatedExistingBill($bill, $oldData));
|
||||
|
||||
return $bill;
|
||||
}
|
||||
@@ -181,39 +170,6 @@ class BillUpdateService
|
||||
return $bill;
|
||||
}
|
||||
|
||||
private function updateBillTriggers(Bill $bill, array $oldData, array $newData): void
|
||||
{
|
||||
Log::debug(sprintf('Now in updateBillTriggers(%d, "%s")', $bill->id, $bill->name));
|
||||
|
||||
/** @var BillRepositoryInterface $repository */
|
||||
$repository = app(BillRepositoryInterface::class);
|
||||
$repository->setUser($bill->user);
|
||||
$rules = $repository->getRulesForBill($bill);
|
||||
if (0 === $rules->count()) {
|
||||
Log::debug('Found no rules.');
|
||||
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('Found %d rules', $rules->count()));
|
||||
$fields = [
|
||||
'name' => 'description_contains',
|
||||
'amount_min' => 'amount_more',
|
||||
'amount_max' => 'amount_less',
|
||||
'transaction_currency_name' => 'currency_is',
|
||||
];
|
||||
foreach ($fields as $field => $ruleTriggerKey) {
|
||||
if (!array_key_exists($field, $newData)) {
|
||||
continue;
|
||||
}
|
||||
if ($oldData[$field] === $newData[$field]) {
|
||||
Log::debug(sprintf('Field %s is unchanged ("%s"), continue.', $field, $oldData[$field]));
|
||||
|
||||
continue;
|
||||
}
|
||||
$this->updateRules($rules, $ruleTriggerKey, $oldData[$field], $newData[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
private function updateOrder(Bill $bill, int $oldOrder, int $newOrder): void
|
||||
{
|
||||
if ($newOrder > $oldOrder) {
|
||||
|
||||
@@ -31,6 +31,7 @@ use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Illuminate\Contracts\Auth\UserProvider;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
@@ -40,6 +41,7 @@ class RemoteUserGuard implements Guard
|
||||
{
|
||||
protected Application $application;
|
||||
protected ?User $user = null;
|
||||
private $tried = false;
|
||||
|
||||
/**
|
||||
* Create a new authentication guard.
|
||||
@@ -49,13 +51,19 @@ class RemoteUserGuard implements Guard
|
||||
Application $app
|
||||
) {
|
||||
$app->get('request');
|
||||
// Log::debug(sprintf('Created RemoteUserGuard for %s "%s"', $request?->getMethod(), $request?->getRequestUri()));
|
||||
Log::debug(sprintf('Created RemoteUserGuard for %s "%s"', $app->get('request')?->getMethod(), $app->get('request')?->getRequestUri()));
|
||||
$this->application = $app;
|
||||
}
|
||||
|
||||
public function authenticate(): void
|
||||
{
|
||||
// Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
$this->tried = true;
|
||||
Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
if (App::runningInConsole()) {
|
||||
Log::debug('Running in console, will not authenticate.');
|
||||
|
||||
return;
|
||||
}
|
||||
if ($this->user instanceof User) {
|
||||
Log::debug(sprintf('%s is found: #%d, "%s".', $this->user::class, $this->user->id, $this->user->email));
|
||||
|
||||
@@ -70,10 +78,14 @@ class RemoteUserGuard implements Guard
|
||||
$userID = request()->server($header) ?? apache_request_headers()[$header] ?? null;
|
||||
}
|
||||
|
||||
// test value for development
|
||||
// $userID = 'james@firefly';
|
||||
|
||||
if (null === $userID || '' === $userID) {
|
||||
Log::error(sprintf('No user in header "%s".', $header));
|
||||
|
||||
throw new FireflyException('The guard header was unexpectedly empty. See the logs.');
|
||||
// throw new FireflyException('The guard header was unexpectedly empty. See the logs.');
|
||||
return;
|
||||
}
|
||||
|
||||
Log::debug(sprintf('User ID found in header is "%s"', $userID));
|
||||
@@ -145,8 +157,18 @@ class RemoteUserGuard implements Guard
|
||||
|
||||
public function user(): ?User
|
||||
{
|
||||
if (App::runningInConsole()) {
|
||||
Log::debug('Running in console, will not authenticate.');
|
||||
|
||||
return null;
|
||||
}
|
||||
if (false === $this->tried) {
|
||||
Log::debug('Have not tried authentication, do it now.');
|
||||
$this->authenticate();
|
||||
}
|
||||
// Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
$user = $this->user;
|
||||
|
||||
if (!$user instanceof User) {
|
||||
Log::debug('User is NULL');
|
||||
|
||||
|
||||
@@ -35,19 +35,8 @@ class Calculator
|
||||
{
|
||||
public const int DEFAULT_INTERVAL = 1;
|
||||
|
||||
private static ?SplObjectStorage $intervalMap = null; // @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
private static array $intervals = [];
|
||||
private static ?SplObjectStorage $intervalMap = null;
|
||||
private static array $intervals = [];
|
||||
|
||||
public function isAvailablePeriodicity(Periodicity $periodicity): bool
|
||||
{
|
||||
|
||||
@@ -75,12 +75,12 @@ class UpdateCheckCronjob extends AbstractCronjob
|
||||
// last check time was more than a week ago.
|
||||
Log::debug('Have not checked for a new version in a week!');
|
||||
$release = $this->getLatestRelease();
|
||||
if ('error' === $release['level']) {
|
||||
if ('' !== $release->getError()) {
|
||||
// get stuff from job:
|
||||
$this->jobFired = true;
|
||||
$this->jobErrored = true;
|
||||
$this->jobSucceeded = false;
|
||||
$this->message = $release['message'];
|
||||
$this->message = $release->getError();
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -88,6 +88,23 @@ class UpdateCheckCronjob extends AbstractCronjob
|
||||
$this->jobFired = true;
|
||||
$this->jobErrored = false;
|
||||
$this->jobSucceeded = false;
|
||||
$this->message = $release['message'];
|
||||
$this->message = trans('firefly.no_new_release_available');
|
||||
|
||||
if ($release->isNewVersionAvailable()) {
|
||||
// if running develop, slightly different message.
|
||||
if (str_contains(config('firefly.version'), 'develop')) {
|
||||
$this->message = trans('firefly.update_current_dev_older', [
|
||||
'version' => config('firefly.version'),
|
||||
'new_version' => $release->getNewVersion(),
|
||||
]);
|
||||
}
|
||||
if (!str_contains(config('firefly.version'), 'develop')) {
|
||||
$this->message = trans('firefly.update_new_version_alert', [
|
||||
'your_version' => config('firefly.version'),
|
||||
'new_version' => $release->getNewVersion(),
|
||||
'date' => $release->getPublishedAt()->format('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,82 @@ class ExportDataGenerator
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->accounts = new Collection();
|
||||
|
||||
@@ -37,7 +37,6 @@ use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
|
||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Navigation;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
@@ -80,41 +79,8 @@ trait PeriodOverview
|
||||
protected TagRepositoryInterface $tagRepository;
|
||||
protected JournalRepositoryInterface $journalRepos;
|
||||
protected PeriodStatisticRepositoryInterface $periodStatisticRepo;
|
||||
private Collection $statistics; // temp data holder
|
||||
// temp data holder
|
||||
// temp data holder
|
||||
// temp data holder
|
||||
// temp data holder
|
||||
// temp data holder
|
||||
// temp data holder
|
||||
// temp data holder
|
||||
// temp data holder
|
||||
// temp data holder
|
||||
// temp data holder
|
||||
// temp data holder
|
||||
private array $transactions; // temp data holder
|
||||
|
||||
// temp data holder
|
||||
|
||||
// temp data holder
|
||||
|
||||
// temp data holder
|
||||
|
||||
// temp data holder
|
||||
|
||||
// temp data holder
|
||||
|
||||
// temp data holder
|
||||
|
||||
// temp data holder
|
||||
|
||||
// temp data holder
|
||||
|
||||
// temp data holder
|
||||
|
||||
// temp data holder
|
||||
|
||||
// temp data holder
|
||||
private Collection $statistics;
|
||||
private array $transactions;
|
||||
|
||||
/**
|
||||
* This method returns "period entries", so nov-2015, dec-2015, etc. (this depends on the users session range)
|
||||
@@ -175,6 +141,34 @@ trait PeriodOverview
|
||||
return $entries;
|
||||
}
|
||||
|
||||
protected function getGenericPeriod(string $type, string $period, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$return = [
|
||||
'title' => Navigation::periodShow($start, $period),
|
||||
'route' => route('transactions.index', [$type, $start->format('Y-m-d'), $end->format('Y-m-d')]),
|
||||
'total_transactions' => 0,
|
||||
];
|
||||
$setTypes = [
|
||||
'withdrawal' => 'spent',
|
||||
'expenses' => 'spent',
|
||||
'deposit' => 'earned',
|
||||
'revenue' => 'earned',
|
||||
'transfer' => 'transferred',
|
||||
'transfers' => 'transferred',
|
||||
];
|
||||
if (!array_key_exists($type, $setTypes)) {
|
||||
throw new FireflyException(sprintf('[c] Cannot deal with type "%s"', $type));
|
||||
}
|
||||
$setType = $setTypes[$type];
|
||||
|
||||
$this->transactions = [];
|
||||
$set = $this->getSingleGenericPeriodByType($start, $end, $type);
|
||||
$return['total_transactions'] += $set['count'];
|
||||
$return[$setType] = $set;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as above, but for lists that involve transactions without a budget.
|
||||
*
|
||||
@@ -260,60 +254,18 @@ trait PeriodOverview
|
||||
*/
|
||||
protected function getTransactionPeriodOverview(string $transactionType, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$range = Navigation::getViewRange(true);
|
||||
$types = config(sprintf('firefly.transactionTypesByType.%s', $transactionType));
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
// properties for cache
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('transactions-period-entries');
|
||||
$cache->addProperty($transactionType);
|
||||
if ($cache->has()) {
|
||||
return $cache->get();
|
||||
}
|
||||
$this->periodStatisticRepo = app(PeriodStatisticRepositoryInterface::class);
|
||||
$range = Navigation::getViewRange(true);
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
$spent = [];
|
||||
$earned = [];
|
||||
$transferred = [];
|
||||
// collect all journals in this period (regardless of type)
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setTypes($types)->setRange($start, $end);
|
||||
$genericSet = $collector->getExtractedJournals();
|
||||
$loops = 0;
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
$this->statistics = $this->periodStatisticRepo->allInRangeForPrefix('all_', $start, $end);
|
||||
Log::debug(sprintf('Collected %d statistics', $this->statistics->count()));
|
||||
|
||||
foreach ($dates as $currentDate) {
|
||||
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
|
||||
|
||||
if ($loops < 10) {
|
||||
// set to correct array
|
||||
if ('expenses' === $transactionType || 'withdrawal' === $transactionType) {
|
||||
$spent = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
|
||||
}
|
||||
if ('revenue' === $transactionType || 'deposit' === $transactionType) {
|
||||
$earned = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
|
||||
}
|
||||
if ('transfer' === $transactionType || 'transfers' === $transactionType) {
|
||||
$transferred = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
|
||||
}
|
||||
}
|
||||
$entries[] = [
|
||||
'title' => $title,
|
||||
'route' => route('transactions.index', [
|
||||
$transactionType,
|
||||
$currentDate['start']->format('Y-m-d'),
|
||||
$currentDate['end']->format('Y-m-d'),
|
||||
]),
|
||||
'total_transactions' => count($spent) + count($earned) + count($transferred),
|
||||
'spent' => $this->groupByCurrency($spent),
|
||||
'earned' => $this->groupByCurrency($earned),
|
||||
'transferred' => $this->groupByCurrency($transferred),
|
||||
];
|
||||
++$loops;
|
||||
$entries[] = $this->getGenericPeriod($transactionType, $currentDate['period'], $currentDate['start'], $currentDate['end']);
|
||||
}
|
||||
|
||||
return $entries;
|
||||
@@ -360,10 +312,11 @@ trait PeriodOverview
|
||||
|
||||
return new Collection();
|
||||
}
|
||||
Log::debug(sprintf('Now in filterStatistics("%s")', $type));
|
||||
|
||||
return $this->statistics->filter(
|
||||
static fn (PeriodStatistic $statistic): bool => $statistic->start->eq($start) && $statistic->end->eq($end) && $statistic->type === $type
|
||||
);
|
||||
return $this->statistics->filter(static function (PeriodStatistic $statistic) use ($start, $end, $type): bool {
|
||||
return $statistic->start->isSameSecond($start) && $statistic->end->isSameSecond($end) && $statistic->type === $type;
|
||||
});
|
||||
}
|
||||
|
||||
private function filterTransactionsByType(TransactionTypeEnum $type, Carbon $start, Carbon $end): array
|
||||
@@ -425,21 +378,63 @@ trait PeriodOverview
|
||||
return [$start, $end];
|
||||
}
|
||||
|
||||
private function getSingleGenericPeriodByType(Carbon $start, Carbon $end, string $type): array
|
||||
{
|
||||
$filterType = sprintf('all_%s', $type);
|
||||
$statistics = $this->filterStatistics($start, $end, $filterType);
|
||||
$types = config(sprintf('firefly.transactionTypesByType.%s', $type));
|
||||
// nothing found, regenerate them.
|
||||
if (0 === $statistics->count()) {
|
||||
if (0 === count($this->transactions)) {
|
||||
// get collection!
|
||||
// collect all journals in this period (regardless of type)
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setTypes($types)->setRange($start, $end);
|
||||
$this->transactions = $collector->getExtractedJournals();
|
||||
Log::debug(sprintf('Going to group %d found journal(s)', count($types)));
|
||||
}
|
||||
|
||||
$grouped = $this->groupByCurrency($this->filterJournalsByDate($this->transactions, $start, $end));
|
||||
$this->saveGroupedForPrefix('all', $start, $end, $type, $grouped);
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
$grouped = ['count' => 0];
|
||||
|
||||
/** @var PeriodStatistic $statistic */
|
||||
foreach ($statistics as $statistic) {
|
||||
$id = (int) $statistic->transaction_currency_id;
|
||||
$currency = Amount::getTransactionCurrencyById($id);
|
||||
$grouped[$id] = [
|
||||
'amount' => (string) $statistic->amount,
|
||||
'count' => (int) $statistic->count,
|
||||
'currency_id' => $currency->id,
|
||||
'currency_name' => $currency->name,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
];
|
||||
$grouped['count'] += (int) $statistic->count;
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
private function getSingleModelPeriodByType(Model $model, Carbon $start, Carbon $end, string $type): array
|
||||
{
|
||||
Log::debug(sprintf(
|
||||
'Now in getSingleModelPeriodByType(%s #%d, %s %s, %s)',
|
||||
$model::class,
|
||||
$model->id,
|
||||
$start->format('Y-m-d'),
|
||||
$end->format('Y-m-d'),
|
||||
$start->format('Y-m-d H:i:s.u'),
|
||||
$end->format('Y-m-d H:i:s.u'),
|
||||
$type
|
||||
));
|
||||
$statistics = $this->filterStatistics($start, $end, $type);
|
||||
|
||||
// nothing found, regenerate them.
|
||||
if (0 === $statistics->count()) {
|
||||
Log::debug(sprintf('Found nothing in this period for type "%s"', $type));
|
||||
Log::debug(sprintf('Found nothing between %s and %s for type "%s"', $start->format('Y-m-d H:i:s.u'), $end->format('Y-m-d H:i:s.u'), $type));
|
||||
if (0 === count($this->transactions)) {
|
||||
switch ($model::class) {
|
||||
default:
|
||||
@@ -464,7 +459,7 @@ trait PeriodOverview
|
||||
|
||||
switch ($type) {
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot deal with category period type %s', $type));
|
||||
throw new FireflyException(sprintf('Cannot deal with type %s', $type));
|
||||
|
||||
case 'spent':
|
||||
$result = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $start, $end);
|
||||
@@ -526,7 +521,7 @@ trait PeriodOverview
|
||||
|
||||
switch ($model) {
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot deal with model of type "%s"', $model));
|
||||
throw new FireflyException(sprintf('[b] Cannot deal with model of type "%s"', $model));
|
||||
|
||||
case 'budget':
|
||||
// get all expenses without a budget.
|
||||
|
||||
@@ -40,30 +40,8 @@ use Override;
|
||||
|
||||
class AvailableBudgetEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private Collection $collection; // @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
private readonly bool $convertToPrimary; // @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
private Collection $collection;
|
||||
private readonly bool $convertToPrimary;
|
||||
private array $currencies = [];
|
||||
private array $currencyIds = [];
|
||||
private array $ids = [];
|
||||
|
||||
@@ -40,18 +40,7 @@ use Illuminate\Support\Facades\Log;
|
||||
class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private Collection $collection;
|
||||
private readonly bool $convertToPrimary; // @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
private readonly bool $convertToPrimary;
|
||||
private array $currencies = [];
|
||||
private array $currencyIds = [];
|
||||
private Carbon $end;
|
||||
|
||||
@@ -42,30 +42,8 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PiggyBankEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private array $accountIds = []; // @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
private array $accounts = []; // @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
private array $accountIds = [];
|
||||
private array $accounts = [];
|
||||
private array $amounts = [];
|
||||
private Collection $collection;
|
||||
private array $currencies = [];
|
||||
|
||||
@@ -37,30 +37,8 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PiggyBankEventEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private array $accountCurrencies = []; // @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
private array $accountIds = []; // @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
private array $accountCurrencies = [];
|
||||
private array $accountIds = [];
|
||||
private Collection $collection;
|
||||
private array $currencies = [];
|
||||
private array $groupIds = [];
|
||||
|
||||
@@ -46,18 +46,7 @@ use Illuminate\Support\Facades\Log;
|
||||
class SubscriptionEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private BillDateCalculator $calculator;
|
||||
private Collection $collection; // @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
private Collection $collection;
|
||||
private readonly bool $convertToPrimary;
|
||||
private ?Carbon $end = null;
|
||||
private array $mappedObjects = [];
|
||||
|
||||
@@ -53,29 +53,7 @@ class TransactionGroupEnrichment implements EnrichmentInterface
|
||||
private array $metaData = [];
|
||||
private array $notes = [];
|
||||
private readonly TransactionCurrency $primaryCurrency;
|
||||
private array $tags = []; // @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
|
||||
// @phpstan-ignore-line
|
||||
private array $tags = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
@@ -42,30 +42,8 @@ use stdClass;
|
||||
class WebhookEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private Collection $collection;
|
||||
private array $deliveries = []; // @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
private array $ids = []; // @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
// @phpstan-ignore-line
|
||||
private array $deliveries = [];
|
||||
private array $ids = [];
|
||||
private array $responses = [];
|
||||
private array $triggers = [];
|
||||
private array $webhookDeliveries = [];
|
||||
|
||||
@@ -131,33 +131,41 @@ class AvailableBudgetCalculator
|
||||
|
||||
private function refreshAvailableBudget(Carbon $start): void
|
||||
{
|
||||
$end = Navigation::endOfPeriod($start, $this->viewRange);
|
||||
$end = Navigation::endOfPeriod($start, $this->viewRange);
|
||||
Log::debug(sprintf('refreshAvailableBudget(%s), end is %s', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
$availableBudget = $this->abRepository->find($this->currency, $start, $end);
|
||||
|
||||
if (null !== $availableBudget) {
|
||||
Log::debug('Found available budget for this period, will update it.');
|
||||
$this->abRepository->recalculateAmount($availableBudget);
|
||||
if ($end->lt($start)) {
|
||||
Log::error(sprintf('%s is less than %s, stop.', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$availableBudget = $this->abRepository->find($this->currency, $start, $end);
|
||||
$availableBudgets = $this->abRepository->findInRange($this->currency, $start, $end);
|
||||
|
||||
foreach ($availableBudgets as $item) {
|
||||
Log::debug(sprintf(
|
||||
'findInRange found available budget #%d (%s - %s), will update it.',
|
||||
$item->id,
|
||||
$item->start_date->format('Y-m-d'),
|
||||
$item->end_date->format('Y-m-d')
|
||||
));
|
||||
$this->abRepository->recalculateAmount($availableBudget);
|
||||
}
|
||||
if (!$this->create) {
|
||||
Log::debug('Can stop here. have not been asked to create an available budget.');
|
||||
|
||||
return;
|
||||
}
|
||||
if ($end->lt($start)) {
|
||||
Log::error(sprintf('%s is less than %s, stop.', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
|
||||
return;
|
||||
if (null === $availableBudget) {
|
||||
Log::debug(sprintf('Will create new available budget for period %s to %s', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
$availableBudget = $this->abRepository->store([
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
'currency_id' => $this->currency->id,
|
||||
'amount' => '1',
|
||||
]);
|
||||
$this->abRepository->recalculateAmount($availableBudget);
|
||||
}
|
||||
Log::debug(sprintf('Will create new available budget for period %s to %s', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
$availableBudget = $this->abRepository->store([
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
'currency_id' => $this->currency->id,
|
||||
'amount' => '1',
|
||||
]);
|
||||
$this->abRepository->recalculateAmount($availableBudget);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2217,7 +2217,7 @@ class OperatorQuerySearch implements SearchInterface
|
||||
// changed from includeTags to includeAnyTags for #8632
|
||||
$ids = array_values($tags->pluck('id')->toArray());
|
||||
$index = count($this->includeAnyTags);
|
||||
$this->includeAnyTags[$index] = array_unique(array_merge($this->includeAnyTags[$index], $ids));
|
||||
$this->includeAnyTags[$index] = array_unique(array_merge($this->includeAnyTags[$index] ?? [], $ids));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -2252,7 +2252,7 @@ class OperatorQuerySearch implements SearchInterface
|
||||
if ($tags->count() > 0) {
|
||||
$ids = array_values($tags->pluck('id')->toArray());
|
||||
$index = count($this->includeAnyTags);
|
||||
$this->includeAnyTags[$index] = array_unique(array_merge($this->includeAnyTags[$index], $ids));
|
||||
$this->includeAnyTags[$index] = array_unique(array_merge($this->includeAnyTags[$index] ?? [], $ids));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -87,6 +87,7 @@ class Steam
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
|
||||
->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s'))
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->groupBy(['transactions.account_id', 'transaction_currencies.code'])
|
||||
->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])
|
||||
->toArray()
|
||||
@@ -435,7 +436,7 @@ class Steam
|
||||
if ($cache->has()) {
|
||||
Log::debug('Return cached finalAccountBalanceInRange');
|
||||
|
||||
// return $cache->get();
|
||||
return $cache->get();
|
||||
}
|
||||
|
||||
$balances = [];
|
||||
@@ -469,7 +470,7 @@ class Steam
|
||||
->transactions()
|
||||
->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s'))
|
||||
->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s'))
|
||||
->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s'))
|
||||
->groupBy('transaction_journals.date')
|
||||
->groupBy('transactions.transaction_currency_id')
|
||||
->orderBy('transaction_journals.date', 'ASC')
|
||||
|
||||
@@ -246,7 +246,7 @@ class UpdatePiggyBank implements ActionInterface
|
||||
Log::warning(sprintf('Cannot remove %s from piggy bank.', $amount));
|
||||
$currency = $accountRepository->getAccountCurrency($account) ?? Amount::getPrimaryCurrency();
|
||||
event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_remove_from_piggy', [
|
||||
'amount' => Amount::formatAnything($amount, $currency, false),
|
||||
'amount' => Amount::formatAnything($currency, $amount, false),
|
||||
'name' => $piggyBank->name,
|
||||
])));
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ class ObjectGroupTransformer extends AbstractTransformer
|
||||
|
||||
return [
|
||||
'id' => (string) $objectGroup->id,
|
||||
'created_at' => $objectGroup->created_at?->toAtomString(),
|
||||
'updated_at' => $objectGroup->updated_at?->toAtomString(),
|
||||
'created_at' => $objectGroup->created_at->toAtomString(),
|
||||
'updated_at' => $objectGroup->updated_at->toAtomString(),
|
||||
'title' => $objectGroup->title,
|
||||
'order' => $objectGroup->order,
|
||||
'links' => [['rel' => 'self', 'uri' => '/object_groups/'.$objectGroup->id]],
|
||||
|
||||
@@ -66,8 +66,8 @@ class PiggyBankEventTransformer extends AbstractTransformer
|
||||
|
||||
return [
|
||||
'id' => (string) $event->id,
|
||||
'created_at' => $event->created_at?->toAtomString(),
|
||||
'updated_at' => $event->updated_at?->toAtomString(),
|
||||
'created_at' => $event->created_at->toAtomString(),
|
||||
'updated_at' => $event->updated_at->toAtomString(),
|
||||
'amount' => $amount,
|
||||
'pc_amount' => $primaryAmount,
|
||||
|
||||
|
||||
@@ -32,11 +32,9 @@ use FireflyIII\Http\Middleware\IsAdmin;
|
||||
use FireflyIII\Http\Middleware\Range;
|
||||
use FireflyIII\Http\Middleware\RedirectIfAuthenticated;
|
||||
use FireflyIII\Http\Middleware\SecureHeaders;
|
||||
use FireflyIII\Http\Middleware\StartFireflySession;
|
||||
use FireflyIII\Http\Middleware\TrustProxies;
|
||||
use FireflyIII\Http\Middleware\StartFireflyIIISession;
|
||||
use FireflyIII\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Contracts\Debug\ExceptionHandler;
|
||||
use Illuminate\Contracts\Http\Kernel;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
@@ -47,7 +45,6 @@ use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance;
|
||||
use Illuminate\Foundation\Http\Middleware\TrimStrings;
|
||||
use Illuminate\Http\Middleware\HandleCors;
|
||||
use Illuminate\Http\Middleware\ValidatePostSize;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
use Laravel\Passport\Http\Middleware\CreateFreshApiToken;
|
||||
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
|
||||
@@ -92,115 +89,99 @@ if (!function_exists('stringIsEqual')) {
|
||||
$app = Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
web : __DIR__ . '/../routes/web.php',
|
||||
api : __DIR__ . '/../routes/api.php',
|
||||
commands: __DIR__ . '/../routes/console.php',
|
||||
health : '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
|
||||
// overrule the standard middleware
|
||||
$middleware->use(
|
||||
[
|
||||
InvokeDeferredCallbacks::class,
|
||||
\Illuminate\Http\Middleware\TrustProxies::class, // use the DEFAULT middleware for this.
|
||||
HandleCors::class,
|
||||
PreventRequestsDuringMaintenance::class,
|
||||
ValidatePostSize::class,
|
||||
TrimStrings::class,
|
||||
ConvertEmptyStringsToNull::class,
|
||||
SecureHeaders::class,
|
||||
TrustProxies::class,
|
||||
SecureHeaders::class, // is a Firefly III specific middleware class.
|
||||
]
|
||||
);
|
||||
|
||||
// overrule the web group
|
||||
// append and extend the default "web" middleware
|
||||
// to include our own custom "StartFireflyIIISession" class.
|
||||
// this class in turns contains a better "previous URL" feature.
|
||||
// See https://laravel.com/docs/12.x/middleware for the default list.
|
||||
$middleware->group('web',
|
||||
[
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartFireflySession::class,
|
||||
StartFireflyIIISession::class, // this is different.
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
Binder::class, // this is also different.
|
||||
CreateFreshApiToken::class,
|
||||
]
|
||||
);
|
||||
// new group?
|
||||
$middleware->appendToGroup('binders-only',
|
||||
[
|
||||
Installer::class,
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
Binder::class,
|
||||
]);
|
||||
|
||||
//
|
||||
$middleware->appendToGroup('user-not-logged-in', [
|
||||
Installer::class,
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartFireflySession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
Binder::class,
|
||||
RedirectIfAuthenticated::class,
|
||||
]);
|
||||
// the default API group only contains "substitute bindings" middleware
|
||||
// so here we replace the entire API group and add more sensible stuff.
|
||||
$middleware->group('api',
|
||||
[
|
||||
AcceptHeaders::class,
|
||||
// EnsureFrontendRequestsAreStateful::class,
|
||||
'auth:api',
|
||||
Binder::class,
|
||||
]
|
||||
);
|
||||
$middleware->appendToGroup('api_basic', [AcceptHeaders::class, Binder::class]);
|
||||
|
||||
// more
|
||||
$middleware->appendToGroup('user-logged-in-no-2fa', [
|
||||
Installer::class,
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartFireflySession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
Binder::class,
|
||||
Authenticate::class,
|
||||
]);
|
||||
|
||||
// simple auth
|
||||
// "simple auth" means the user must be logged in and present,
|
||||
// but does not have to be 2FA authenticated. This is so all users
|
||||
// can always log out, for example.
|
||||
$middleware->appendToGroup('user-simple-auth', [
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartFireflySession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
Binder::class,
|
||||
Authenticate::class,
|
||||
]);
|
||||
|
||||
// user full auth
|
||||
// This middleware is added for all routes where the user MUST have full authentication.
|
||||
// this includes 2FA etc.
|
||||
// incidentally, this group also includes the range middleware and the message thing.
|
||||
$middleware->appendToGroup('user-full-auth', [
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartFireflySession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
Authenticate::class,
|
||||
MFAMiddleware::class,
|
||||
Range::class,
|
||||
Binder::class,
|
||||
InterestingMessage::class,
|
||||
CreateFreshApiToken::class,
|
||||
]);
|
||||
|
||||
// admin
|
||||
// This middleware is added to ensure that the user is not only logged in and
|
||||
// authenticated (with MFA and everything), but also admin.
|
||||
$middleware->appendToGroup('admin', [
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartFireflySession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
Authenticate::class,
|
||||
// AuthenticateTwoFactor::class,
|
||||
MFAMiddleware::class,
|
||||
IsAdmin::class,
|
||||
Range::class,
|
||||
Binder::class,
|
||||
CreateFreshApiToken::class,
|
||||
InterestingMessage::class,
|
||||
]);
|
||||
|
||||
// api
|
||||
$middleware->appendToGroup('api', [AcceptHeaders::class, EnsureFrontendRequestsAreStateful::class, 'auth:api,sanctum', Binder::class]);
|
||||
// api basic,
|
||||
$middleware->appendToGroup('api_basic', [AcceptHeaders::class, Binder::class]);
|
||||
// if the user is not logged in, this group applies.
|
||||
// on top of everything else of course.
|
||||
$middleware->appendToGroup('user-not-logged-in', [
|
||||
Installer::class,
|
||||
RedirectIfAuthenticated::class,
|
||||
]);
|
||||
|
||||
// the "binders only" group does not need or ask for authentication
|
||||
// it just makes sure strings from routes are bound to objects if possible.
|
||||
$middleware->group('binders-only',
|
||||
[
|
||||
Installer::class,
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
Binder::class,
|
||||
]);
|
||||
|
||||
// $middleware->priority([StartFireflyIIISession::class, ShareErrorsFromSession::class, Authenticate::class, Binder::class, Authorize::class]);
|
||||
})
|
||||
->withEvents(discover: [
|
||||
__DIR__ . '/../app/Listeners',
|
||||
@@ -225,16 +206,6 @@ $app = Application::configure(basePath: dirname(__DIR__))
|
||||
|
|
||||
*/
|
||||
|
||||
$app->singleton(
|
||||
Kernel::class,
|
||||
FireflyIII\Http\Kernel::class
|
||||
);
|
||||
|
||||
$app->singleton(
|
||||
Illuminate\Contracts\Console\Kernel::class,
|
||||
FireflyIII\Console\Kernel::class
|
||||
);
|
||||
|
||||
$app->singleton(
|
||||
ExceptionHandler::class,
|
||||
Handler::class
|
||||
|
||||
71
bootstrap/providers.php
Normal file
71
bootstrap/providers.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/*
|
||||
* providers.php
|
||||
* Copyright (c) 2026 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/>.
|
||||
*/
|
||||
|
||||
use FireflyIII\Providers\AccountServiceProvider;
|
||||
use FireflyIII\Providers\AdminServiceProvider;
|
||||
use FireflyIII\Providers\AppServiceProvider;
|
||||
use FireflyIII\Providers\AttachmentServiceProvider;
|
||||
use FireflyIII\Providers\BillServiceProvider;
|
||||
use FireflyIII\Providers\BudgetServiceProvider;
|
||||
use FireflyIII\Providers\CategoryServiceProvider;
|
||||
use FireflyIII\Providers\CurrencyServiceProvider;
|
||||
use FireflyIII\Providers\FireflyServiceProvider;
|
||||
use FireflyIII\Providers\JournalServiceProvider;
|
||||
use FireflyIII\Providers\PiggyBankServiceProvider;
|
||||
use FireflyIII\Providers\RecurringServiceProvider;
|
||||
use FireflyIII\Providers\RouteServiceProvider;
|
||||
use FireflyIII\Providers\RuleGroupServiceProvider;
|
||||
use FireflyIII\Providers\RuleServiceProvider;
|
||||
use FireflyIII\Providers\SearchServiceProvider;
|
||||
use FireflyIII\Providers\TagServiceProvider;
|
||||
use TwigBridge\ServiceProvider;
|
||||
|
||||
return [
|
||||
// Package Service Providers...
|
||||
|
||||
// Application Service Providers...
|
||||
AppServiceProvider::class,
|
||||
FireflyIII\Providers\AuthServiceProvider::class,
|
||||
// FireflyIII\Providers\BroadcastServiceProvider::class,
|
||||
// EventServiceProvider::class,
|
||||
RouteServiceProvider::class,
|
||||
|
||||
// own stuff:
|
||||
PragmaRX\Google2FALaravel\ServiceProvider::class,
|
||||
ServiceProvider::class,
|
||||
|
||||
// More service providers.
|
||||
AccountServiceProvider::class,
|
||||
AttachmentServiceProvider::class,
|
||||
BillServiceProvider::class,
|
||||
BudgetServiceProvider::class,
|
||||
CategoryServiceProvider::class,
|
||||
CurrencyServiceProvider::class,
|
||||
FireflyServiceProvider::class,
|
||||
JournalServiceProvider::class,
|
||||
PiggyBankServiceProvider::class,
|
||||
RuleServiceProvider::class,
|
||||
RuleGroupServiceProvider::class,
|
||||
SearchServiceProvider::class,
|
||||
TagServiceProvider::class,
|
||||
AdminServiceProvider::class,
|
||||
RecurringServiceProvider::class,
|
||||
];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user