mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-12-22 11:11:25 +00:00
Compare commits
248 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9795f46bea | ||
|
|
10bc0098b9 | ||
|
|
7a900fd3d9 | ||
|
|
45cfb4e565 | ||
|
|
5320480767 | ||
|
|
3e18e984fa | ||
|
|
fce759e98f | ||
|
|
f4439778f1 | ||
|
|
11176fc212 | ||
|
|
ffc71da2eb | ||
|
|
246fa0d6e4 | ||
|
|
3444146da3 | ||
|
|
5c4a482f64 | ||
|
|
01f84ea11d | ||
|
|
08402babbd | ||
|
|
7ef3dcbd23 | ||
|
|
8836fa205b | ||
|
|
f544a278c5 | ||
|
|
4b8fd6adb9 | ||
|
|
1ac8a9b4ac | ||
|
|
08a8a69b34 | ||
|
|
dbd668bda5 | ||
|
|
cc0760553b | ||
|
|
1142ff6b1f | ||
|
|
927da5d742 | ||
|
|
9a734e48fe | ||
|
|
345fe39b54 | ||
|
|
c7e9d62712 | ||
|
|
5918095f11 | ||
|
|
24612eb634 | ||
|
|
f7125d6198 | ||
|
|
47870cd50c | ||
|
|
45fbf83971 | ||
|
|
6044cffef3 | ||
|
|
4d933e1ef7 | ||
|
|
181e088ad5 | ||
|
|
179b86c3ba | ||
|
|
083c15b956 | ||
|
|
7c780dd75c | ||
|
|
d35d51c014 | ||
|
|
14c6926360 | ||
|
|
bedfcb5c5d | ||
|
|
19461020ef | ||
|
|
36ecf25804 | ||
|
|
0229fc243a | ||
|
|
a2f09b305c | ||
|
|
32d7a0fd1b | ||
|
|
5d3f95762e | ||
|
|
4ca9ee6eec | ||
|
|
9026b0bfd7 | ||
|
|
3d30a5938a | ||
|
|
bcb06779b9 | ||
|
|
26b70f37d6 | ||
|
|
b20a354344 | ||
|
|
753f518f77 | ||
|
|
33d3019ffd | ||
|
|
26e3157ae7 | ||
|
|
b592528318 | ||
|
|
936d55d7de | ||
|
|
6df45fccc5 | ||
|
|
f43ebcce87 | ||
|
|
23fc261a82 | ||
|
|
344db19232 | ||
|
|
5f777845a3 | ||
|
|
f32bdd6c76 | ||
|
|
219c3c11f9 | ||
|
|
b3b367fcb3 | ||
|
|
52a43e7f30 | ||
|
|
04f5098d06 | ||
|
|
9731503826 | ||
|
|
b431351a22 | ||
|
|
089097a41c | ||
|
|
4468fdd76b | ||
|
|
79c70c59e3 | ||
|
|
c8ffc81527 | ||
|
|
048db14a08 | ||
|
|
a9ee07b19a | ||
|
|
e316d9ce6d | ||
|
|
6bf354cf93 | ||
|
|
384ebf9341 | ||
|
|
b2c74ba86a | ||
|
|
0c915de314 | ||
|
|
327e7efa89 | ||
|
|
c361b6cc07 | ||
|
|
59948d6746 | ||
|
|
1d93cf41fd | ||
|
|
8bbbb05adc | ||
|
|
6e314b6a30 | ||
|
|
499b2ca7ef | ||
|
|
77823d3f33 | ||
|
|
88dfb954ca | ||
|
|
ddc229f270 | ||
|
|
8af8cc2c9a | ||
|
|
775492b391 | ||
|
|
1419176094 | ||
|
|
aa0ca5fdcf | ||
|
|
0d61a3ad5d | ||
|
|
bdbb3d9ad1 | ||
|
|
fc73bddd43 | ||
|
|
7515578ba7 | ||
|
|
84b9841886 | ||
|
|
6b66476927 | ||
|
|
7357f19f81 | ||
|
|
f29d8322d5 | ||
|
|
31a3f6f4d8 | ||
|
|
7432a0c5cb | ||
|
|
f6d58191b2 | ||
|
|
89fbf0869a | ||
|
|
b1e453438e | ||
|
|
284c6033d3 | ||
|
|
b85b32560c | ||
|
|
35bc92bb49 | ||
|
|
243a5217e7 | ||
|
|
b2eeeed0af | ||
|
|
afd4700758 | ||
|
|
03a1601bf3 | ||
|
|
edfff4ec57 | ||
|
|
7b5bc3a25e | ||
|
|
d98ca0bb44 | ||
|
|
518b4ba5a7 | ||
|
|
9c1f781be3 | ||
|
|
fa7c1b3ec8 | ||
|
|
89acb9b6f3 | ||
|
|
45971f8f26 | ||
|
|
21f12b87a0 | ||
|
|
d264333ab8 | ||
|
|
0f9c1b9427 | ||
|
|
c273f309d4 | ||
|
|
17fd2f9909 | ||
|
|
0436701f29 | ||
|
|
840632b34e | ||
|
|
764481690c | ||
|
|
66b9d7421b | ||
|
|
a02c1557ac | ||
|
|
ff8af87179 | ||
|
|
9c8b31fdbb | ||
|
|
bda1413da4 | ||
|
|
f1cc8a10f5 | ||
|
|
babf462bcf | ||
|
|
dba24d2dee | ||
|
|
42066cfca2 | ||
|
|
32e550c8e0 | ||
|
|
4f2c94a5a8 | ||
|
|
95076bdb6a | ||
|
|
547caadeb2 | ||
|
|
2469e1e811 | ||
|
|
ded267c9ac | ||
|
|
2c9733e739 | ||
|
|
72fd263ddf | ||
|
|
8b84a4b336 | ||
|
|
3caf1f2d36 | ||
|
|
16391fe99c | ||
|
|
6d3c858cbe | ||
|
|
7afe9fac0a | ||
|
|
e6a2c10a19 | ||
|
|
cc9867bcc4 | ||
|
|
ba7ae19533 | ||
|
|
47fa9e3956 | ||
|
|
0b739b0e3f | ||
|
|
221e912645 | ||
|
|
81f4e92231 | ||
|
|
4fef85d5d0 | ||
|
|
04a6b09861 | ||
|
|
e31842d768 | ||
|
|
e99efae839 | ||
|
|
36f91aa11b | ||
|
|
140658c1a3 | ||
|
|
38be52d12f | ||
|
|
a7fd8069c2 | ||
|
|
2df57bf684 | ||
|
|
4a6cac96f6 | ||
|
|
0e6eb89cea | ||
|
|
0c883259d9 | ||
|
|
13bd98b842 | ||
|
|
c641046aa5 | ||
|
|
05bddbb40e | ||
|
|
4de6d10e27 | ||
|
|
253ed7b2bd | ||
|
|
0d38741de7 | ||
|
|
b5cef50dc7 | ||
|
|
16ee3df31f | ||
|
|
341478db73 | ||
|
|
ef1bf7c3e6 | ||
|
|
ee5b64904d | ||
|
|
0ff3b95f89 | ||
|
|
b70c904ec3 | ||
|
|
3e3465fb56 | ||
|
|
9e37a107ee | ||
|
|
4de2512b34 | ||
|
|
90d4545a1f | ||
|
|
8561f3e177 | ||
|
|
51b5b3a846 | ||
|
|
1401282aa0 | ||
|
|
8b81526e54 | ||
|
|
a16b6cb2b8 | ||
|
|
7876030737 | ||
|
|
402d5748e4 | ||
|
|
280975b81a | ||
|
|
6f8778d87f | ||
|
|
fbf7578fb0 | ||
|
|
7dd560a2e3 | ||
|
|
0b2e1bc8e2 | ||
|
|
baea455fde | ||
|
|
eb303157ca | ||
|
|
733b0d183f | ||
|
|
66233b7b00 | ||
|
|
46a820fda5 | ||
|
|
0244989b94 | ||
|
|
990c369cab | ||
|
|
af501952c2 | ||
|
|
d7ba71b1c0 | ||
|
|
b8df070d93 | ||
|
|
c55c4e0e89 | ||
|
|
4249336320 | ||
|
|
903b9bfd08 | ||
|
|
e32a4a84a8 | ||
|
|
b42d8d1e30 | ||
|
|
e9e9a359c8 | ||
|
|
b3f424fa28 | ||
|
|
ba740322f4 | ||
|
|
898a517ae9 | ||
|
|
c2c8c42ef3 | ||
|
|
4d9c4a415d | ||
|
|
f4fe930430 | ||
|
|
04dfeaf488 | ||
|
|
797064a119 | ||
|
|
4d8d9cb87d | ||
|
|
d1ae2cffcb | ||
|
|
102412cdb9 | ||
|
|
f9ef5868cc | ||
|
|
d9b2112b19 | ||
|
|
5edcd97c78 | ||
|
|
9227a3039e | ||
|
|
f97521c118 | ||
|
|
119870fe9a | ||
|
|
f4942e32ed | ||
|
|
0ef5c10749 | ||
|
|
d37fc311db | ||
|
|
7b05b78cf7 | ||
|
|
576a429426 | ||
|
|
47ef0c3a0b | ||
|
|
66303f614b | ||
|
|
98358f7578 | ||
|
|
8486b846f2 | ||
|
|
6cb35137cb | ||
|
|
f6182412a0 | ||
|
|
a67c87101d | ||
|
|
84b32514df |
17
.env.example
17
.env.example
@@ -177,6 +177,12 @@ MAP_DEFAULT_ZOOM=6
|
||||
# https://docs.firefly-iii.org/advanced-installation/authentication
|
||||
AUTHENTICATION_GUARD=web
|
||||
|
||||
#
|
||||
# Your LDAP server may speak a dialect. You can choose between 'OpenLDAP' and 'ActiveDirectory'
|
||||
# Anything else defaults to 'ActiveDirectory'
|
||||
#
|
||||
LDAP_DIALECT=OpenLDAP
|
||||
|
||||
#
|
||||
# LDAP connection settings:
|
||||
#
|
||||
@@ -193,14 +199,13 @@ LDAP_PASSWORD=super_secret
|
||||
LDAP_AUTH_FIELD=uid
|
||||
|
||||
#
|
||||
# If you wish to only authenticate users from a specific group, use the
|
||||
# group filter. Leave empty or remove if not in use.
|
||||
# If you wish to only authenticate users from a specific group, use the base DN above.
|
||||
#
|
||||
# Example: cn=Administrators,dc=local,dc=com
|
||||
# If you require extra/special filters please use the LDAP_EXTRA_FILTER with a valid DN.
|
||||
#
|
||||
# The group filter will only be applied after the user is authenticated.
|
||||
# The extra filter will only be applied after the user is authenticated.
|
||||
#
|
||||
LDAP_GROUP_FILTER=
|
||||
LDAP_EXTRA_FILTER=
|
||||
|
||||
#
|
||||
# Remote user guard settings
|
||||
@@ -297,6 +302,8 @@ FIREFLY_III_LAYOUT=v1
|
||||
# It won't work. It doesn't do ANYTHING. Don't believe the lies you read online. I'm not joking.
|
||||
# This configuration value WILL NOT HELP.
|
||||
#
|
||||
# Notable exception to this rule is Synology, which, according to some users, will use APP_URL to rewrite stuff.
|
||||
#
|
||||
# This variable is ONLY used in some of the emails Firefly III sends around. Nowhere else.
|
||||
# So when configuring anything WEB related this variable doesn't do anything. Nothing
|
||||
#
|
||||
|
||||
7
.github/.mergify.yml
vendored
7
.github/.mergify.yml
vendored
@@ -1,7 +0,0 @@
|
||||
pull_request_rules:
|
||||
- name: PR on main is never approved.
|
||||
conditions:
|
||||
- base=main
|
||||
actions:
|
||||
close:
|
||||
message: Please reopen this PR on the `develop` branch. Thank you.
|
||||
3
.github/ISSUE_TEMPLATE/bug.yml
vendored
3
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -14,8 +14,9 @@ body:
|
||||
label: I've found a bug and checked that ...
|
||||
description: Make sure that your request fulfills all of the following requirements. If one requirement cannot be satisfied, explain in detail why.
|
||||
options:
|
||||
- label: ... the documentation does not mention anything about my problem
|
||||
- label: ... [the documentation](https://docs.firefly-iii.org/) does not mention anything about my problem
|
||||
- label: ... there are no open or closed issues that are related to my problem
|
||||
- label: ... it's [definitely me, not you](https://github.com/firefly-iii/firefly-iii/blob/main/.github/its_you_not_me.md)
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
||||
15
.github/its_you_not_me.md
vendored
Normal file
15
.github/its_you_not_me.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# It's not you, it's me
|
||||
|
||||
Sometimes bugs reported to Firefly III are configuration and system problems on the user side.
|
||||
|
||||
If you run into one of the following problems, there's a good chance it's not a Firefly III issue, but a configuration issue.
|
||||
|
||||
- ⚠️ Can't connect to the database when starting Firefly III
|
||||
- ⚠️ Errors with missing app keys or encryption problems
|
||||
- ⚠️ Can't login
|
||||
- ⚠️ Can't register the first account
|
||||
- ⚠️ 500 error when starting Firefly III
|
||||
- ⚠️ White page when starting Firefly III
|
||||
- ⚠️ Time-out when starting Firefly III
|
||||
|
||||
If you run into an issue like this, please start a [discussion](https://github.com/firefly-iii/firefly-iii/discussions) or chat on [Gitter.im](https://gitter.im/firefly-iii/firefly-iii). There's a good chance it's not a bug but something we can fix rather quickly :+1:
|
||||
13
.github/mergify.yml
vendored
Normal file
13
.github/mergify.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
pull_request_rules:
|
||||
- name: Security update by dependabot
|
||||
conditions:
|
||||
- author~=^dependabot(|-preview)\[bot\]$
|
||||
actions:
|
||||
merge:
|
||||
method: merge
|
||||
- name: Close all on main
|
||||
conditions:
|
||||
- base=main
|
||||
actions:
|
||||
close:
|
||||
message: Please do not open PR's on the `main` branch, but on the `develop` branch only. Thank you!
|
||||
4
.github/stale.yml
vendored
4
.github/stale.yml
vendored
@@ -1,11 +1,11 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 7
|
||||
daysUntilStale: 14
|
||||
|
||||
# Number of days of inactivity before a stale Issue or Pull Request is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
daysUntilClose: 7
|
||||
daysUntilClose: 14
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
# - "[Status] Maybe Later"
|
||||
|
||||
21
.github/workflows/build.yml
vendored
Normal file
21
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Sonarcloud CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
jobs:
|
||||
sonarcloud:
|
||||
name: SonarCloud
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: SonarCloud Scan
|
||||
uses: SonarSource/sonarcloud-github-action@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -15,4 +15,5 @@ yarn-error.log
|
||||
public/google*.html
|
||||
report.html
|
||||
composer.phar
|
||||
app.js.map
|
||||
app.js.map
|
||||
frontend-v3
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
build:
|
||||
nodes:
|
||||
analysis:
|
||||
project_setup:
|
||||
override: true
|
||||
tests:
|
||||
override:
|
||||
- php-scrutinizer-run
|
||||
checks:
|
||||
javascript: true
|
||||
php:
|
||||
align_assignments: true
|
||||
avoid_fixme_comments: true
|
||||
avoid_multiple_statements_on_same_line: true
|
||||
avoid_perl_style_comments: true
|
||||
avoid_todo_comments: true
|
||||
duplication: false
|
||||
encourage_single_quotes: true
|
||||
newline_at_end_of_file: true
|
||||
no_goto: true
|
||||
no_long_variable_names:
|
||||
maximum: "20"
|
||||
no_short_method_names:
|
||||
minimum: "3"
|
||||
no_short_variable_names:
|
||||
minimum: "3"
|
||||
optional_parameters_at_the_end: true
|
||||
parameter_doc_comments: true
|
||||
remove_extra_empty_lines: true
|
||||
return_doc_comment_if_not_inferrable: true
|
||||
return_doc_comments: true
|
||||
uppercase_constants: true
|
||||
use_self_instead_of_fqcn: true
|
||||
coding_style:
|
||||
php:
|
||||
spaces:
|
||||
around_operators:
|
||||
concatenation: true
|
||||
other:
|
||||
after_type_cast: false
|
||||
filter:
|
||||
excluded_paths:
|
||||
- database/migrations/*
|
||||
- bootstrap/*
|
||||
- config/*
|
||||
- docker/*
|
||||
- public/js/lib/*
|
||||
- public/lib/adminlte/js/*
|
||||
- public/lib/bootstrap/js/*
|
||||
- resources/*
|
||||
- routes/*
|
||||
- storage/*
|
||||
paths:
|
||||
- app/*
|
||||
- public/js/ff/*
|
||||
tools:
|
||||
external_code_coverage: false
|
||||
20
.travis.yml
20
.travis.yml
@@ -1,20 +0,0 @@
|
||||
language: php
|
||||
php:
|
||||
- '7.4'
|
||||
dist: xenial
|
||||
os: linux
|
||||
cache:
|
||||
directories:
|
||||
- "/home/travis/.config"
|
||||
- "/home/travis/build/firefly-iii/firefly-iii/vendor"
|
||||
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
|
||||
before_script:
|
||||
- phpenv config-rm xdebug.ini || return 0
|
||||
|
||||
script:
|
||||
- "./.ci/phpstan.sh"
|
||||
- "./.ci/phpunit.sh"
|
||||
@@ -75,6 +75,7 @@ class DestroyController extends Controller
|
||||
$this->repository->destroyGroup($transactionGroup);
|
||||
// trigger just after destruction
|
||||
event(new DestroyedTransactionGroup($transactionGroup));
|
||||
app('preferences')->mark();
|
||||
|
||||
return response()->json([], 204);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ class AutocompleteRequest extends FormRequest
|
||||
return [
|
||||
'types' => $array,
|
||||
'query' => $this->string('query'),
|
||||
'date' => $this->date('date'),
|
||||
'date' => $this->getCarbonDate('date'),
|
||||
'limit' => $limit,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ class DateRequest extends FormRequest
|
||||
public function getAll(): array
|
||||
{
|
||||
return [
|
||||
'start' => $this->date('start'),
|
||||
'end' => $this->date('end'),
|
||||
'start' => $this->getCarbonDate('start'),
|
||||
'end' => $this->getCarbonDate('end'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ class ExportRequest extends FormRequest
|
||||
public function getAll(): array
|
||||
{
|
||||
$result = [
|
||||
'start' => $this->date('start') ?? Carbon::now()->subYear(),
|
||||
'end' => $this->date('end') ?? Carbon::now(),
|
||||
'start' => $this->getCarbonDate('start') ?? Carbon::now()->subYear(),
|
||||
'end' => $this->getCarbonDate('end') ?? Carbon::now(),
|
||||
'type' => $this->string('type'),
|
||||
];
|
||||
$parts = explode(',', $this->string('accounts'));
|
||||
|
||||
@@ -58,8 +58,8 @@ class GenericRequest extends FormRequest
|
||||
public function getAll(): array
|
||||
{
|
||||
return [
|
||||
'start' => $this->date('start'),
|
||||
'end' => $this->date('end'),
|
||||
'start' => $this->getCarbonDate('start'),
|
||||
'end' => $this->getCarbonDate('end'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ class GenericRequest extends FormRequest
|
||||
*/
|
||||
public function getEnd(): Carbon
|
||||
{
|
||||
$date = $this->date('end');
|
||||
$date = $this->getCarbonDate('end');
|
||||
$date->endOfDay();
|
||||
|
||||
return $date;
|
||||
@@ -251,7 +251,7 @@ class GenericRequest extends FormRequest
|
||||
*/
|
||||
public function getStart(): Carbon
|
||||
{
|
||||
$date = $this->date('start');
|
||||
$date = $this->getCarbonDate('start');
|
||||
$date->startOfDay();
|
||||
|
||||
return $date;
|
||||
|
||||
@@ -70,7 +70,7 @@ class StoreRequest extends FormRequest
|
||||
'account_number' => $this->string('account_number'),
|
||||
'account_role' => $this->string('account_role'),
|
||||
'opening_balance' => $this->string('opening_balance'),
|
||||
'opening_balance_date' => $this->date('opening_balance_date'),
|
||||
'opening_balance_date' => $this->getCarbonDate('opening_balance_date'),
|
||||
'cc_type' => $this->string('credit_card_type'),
|
||||
'cc_monthly_payment_date' => $this->string('monthly_payment_date'),
|
||||
'notes' => $this->stringWithNewlines('notes'),
|
||||
@@ -82,7 +82,7 @@ class StoreRequest extends FormRequest
|
||||
|
||||
if ('liability' === $data['account_type_name'] || 'liabilities' === $data['account_type_name']) {
|
||||
$data['opening_balance'] = app('steam')->negative($this->string('liability_amount'));
|
||||
$data['opening_balance_date'] = $this->date('liability_start_date');
|
||||
$data['opening_balance_date'] = $this->getCarbonDate('liability_start_date');
|
||||
$data['account_type_name'] = $this->string('liability_type');
|
||||
$data['liability_direction'] = $this->string('liability_direction');
|
||||
$data['account_type_id'] = null;
|
||||
|
||||
@@ -44,8 +44,8 @@ class StoreRequest extends FormRequest
|
||||
public function getAll(): array
|
||||
{
|
||||
return [
|
||||
'start' => $this->date('start'),
|
||||
'end' => $this->date('end'),
|
||||
'start' => $this->getCarbonDate('start'),
|
||||
'end' => $this->getCarbonDate('end'),
|
||||
'amount' => $this->string('amount'),
|
||||
'currency_id' => $this->integer('currency_id'),
|
||||
'currency_code' => $this->string('currency_code'),
|
||||
|
||||
@@ -51,8 +51,8 @@ class StoreRequest extends FormRequest
|
||||
$data['account_id'] = $this->integer('account_id');
|
||||
$data['targetamount'] = $this->string('target_amount');
|
||||
$data['current_amount'] = $this->string('current_amount');
|
||||
$data['startdate'] = $this->date('start_date');
|
||||
$data['targetdate'] = $this->date('target_date');
|
||||
$data['startdate'] = $this->getCarbonDate('start_date');
|
||||
$data['targetdate'] = $this->getCarbonDate('target_date');
|
||||
$data['notes'] = $this->stringWithNewlines('notes');
|
||||
$data['object_group_id'] = $this->integer('object_group_id');
|
||||
$data['object_group_title'] = $this->string('object_group_title');
|
||||
|
||||
@@ -47,7 +47,7 @@ class StoreRequest extends FormRequest
|
||||
{
|
||||
$data = [
|
||||
'tag' => $this->string('tag'),
|
||||
'date' => $this->date('date'),
|
||||
'date' => $this->getCarbonDate('date'),
|
||||
'description' => $this->string('description'),
|
||||
'has_location' => true,
|
||||
];
|
||||
|
||||
@@ -211,7 +211,7 @@ class StoreRequest extends FormRequest
|
||||
// budget, category, bill and piggy
|
||||
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser],
|
||||
'transactions.*.budget_name' => ['between:1,255', 'nullable', new BelongsUser],
|
||||
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser],
|
||||
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser,'nullable'],
|
||||
'transactions.*.category_name' => 'between:1,255|nullable',
|
||||
'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser],
|
||||
'transactions.*.bill_name' => ['between:1,255', 'nullable', new BelongsUser],
|
||||
|
||||
@@ -62,7 +62,7 @@ class CronRequest extends FormRequest
|
||||
$data['force'] = $this->boolean('force');
|
||||
}
|
||||
if ($this->has('date')) {
|
||||
$data['date'] = $this->date('date');
|
||||
$data['date'] = $this->getCarbonDate('date');
|
||||
}
|
||||
|
||||
return $data;
|
||||
|
||||
@@ -154,6 +154,7 @@ class ExportData extends Command
|
||||
/**
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
* @throws Exception
|
||||
*/
|
||||
private function parseOptions(): array
|
||||
{
|
||||
@@ -201,12 +202,17 @@ class ExportData extends Command
|
||||
$error = true;
|
||||
}
|
||||
}
|
||||
if(null === $this->option($field)) {
|
||||
Log::info(sprintf('No date given in field "%s"', $field));
|
||||
$error = true;
|
||||
}
|
||||
|
||||
if (true === $error && 'start' === $field) {
|
||||
$journal = $this->journalRepository->firstNull();
|
||||
$date = null === $journal ? Carbon::now()->subYear() : $journal->date;
|
||||
$date->startOfDay();
|
||||
}
|
||||
|
||||
if (true === $error && 'end' === $field) {
|
||||
$date = today(config('app.timezone'));
|
||||
$date->endOfDay();
|
||||
|
||||
@@ -74,8 +74,16 @@ class RestoreOAuthKeys extends Command
|
||||
}
|
||||
if ($this->keysInDatabase() && !$this->keysOnDrive()) {
|
||||
Log::debug('Keys are in DB and keys are not on the drive. Restore.');
|
||||
$this->restoreKeysFromDB();
|
||||
$this->line('Restored OAuth keys from database.');
|
||||
$result = $this->restoreKeysFromDB();
|
||||
if(true === $result) {
|
||||
$this->line('Restored OAuth keys from database.');
|
||||
|
||||
return;
|
||||
}
|
||||
Log::warning('Could not restore keys. Will create new ones.');
|
||||
$this->generateKeys();
|
||||
$this->storeKeysInDB();
|
||||
$this->line('Generated and stored new keys.');
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -124,8 +132,8 @@ class RestoreOAuthKeys extends Command
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function restoreKeysFromDB(): void
|
||||
private function restoreKeysFromDB(): bool
|
||||
{
|
||||
OAuthKeys::restoreKeysFromDB();
|
||||
return OAuthKeys::restoreKeysFromDB();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,11 +143,6 @@ class UpgradeDatabase extends Command
|
||||
$result = Artisan::output();
|
||||
echo $result;
|
||||
|
||||
$this->line('Now installing OAuth2 keys...');
|
||||
Artisan::call('passport:install');
|
||||
$result = Artisan::output();
|
||||
echo $result;
|
||||
|
||||
$this->line('Done!');
|
||||
}
|
||||
}
|
||||
|
||||
46
app/Events/ActuallyLoggedIn.php
Normal file
46
app/Events/ActuallyLoggedIn.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* ActuallyLoggedIn.php
|
||||
* Copyright (c) 2022 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class ActuallyLoggedIn
|
||||
*/
|
||||
class ActuallyLoggedIn extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public User $user;
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,7 @@ class GracefulNotFoundHandler extends ExceptionHandler
|
||||
return redirect(route('currencies.index'));
|
||||
case 'budgets.show':
|
||||
case 'budgets.edit':
|
||||
case 'budgets.show.limit':
|
||||
$request->session()->reflash();
|
||||
|
||||
return redirect(route('budgets.index'));
|
||||
|
||||
@@ -365,19 +365,29 @@ class TransactionJournalFactory
|
||||
$this->accountValidator->setTransactionType($transactionType);
|
||||
|
||||
// validate source account.
|
||||
$sourceId = $data['source_id'] ? (int)$data['source_id'] : null;
|
||||
$sourceName = $data['source_name'] ? (string)$data['source_name'] : null;
|
||||
$validSource = $this->accountValidator->validateSource($sourceId, $sourceName, null);
|
||||
$array = [
|
||||
'id' => $data['source_id'] ? (int)$data['source_id'] : null,
|
||||
'name' => $data['source_name'] ? (string)$data['source_name'] : null,
|
||||
'iban' => $data['source_iban'] ? (string)$data['source_iban'] : null,
|
||||
'number' => $data['source_number'] ? (string)$data['source_number'] : null,
|
||||
];
|
||||
$validSource = $this->accountValidator->validateSource($array);
|
||||
|
||||
// do something with result:
|
||||
if (false === $validSource) {
|
||||
throw new FireflyException(sprintf('Source: %s', $this->accountValidator->sourceError));
|
||||
}
|
||||
Log::debug('Source seems valid.');
|
||||
|
||||
// validate destination account
|
||||
$destinationId = $data['destination_id'] ? (int)$data['destination_id'] : null;
|
||||
$destinationName = $data['destination_name'] ? (string)$data['destination_name'] : null;
|
||||
$validDestination = $this->accountValidator->validateDestination($destinationId, $destinationName, null);
|
||||
$array = [
|
||||
'id' => $data['destination_id'] ? (int)$data['destination_id'] : null,
|
||||
'name' => $data['destination_name'] ? (string)$data['destination_name'] : null,
|
||||
'iban' => $data['destination_iban'] ? (string)$data['destination_iban'] : null,
|
||||
'number' => $data['destination_number'] ? (string)$data['destination_number'] : null,
|
||||
];
|
||||
|
||||
$validDestination = $this->accountValidator->validateDestination($array);
|
||||
// do something with result:
|
||||
if (false === $validDestination) {
|
||||
throw new FireflyException(sprintf('Destination: %s', $this->accountValidator->destError));
|
||||
|
||||
@@ -123,7 +123,7 @@ class UpdatedGroupEventHandler
|
||||
if (1 === $group->transactionJournals->count()) {
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('Correct inconsistent accounts in group #%d', $group->id));
|
||||
Log::debug(sprintf('Validating inconsistent accounts in group #%d', $group->id));
|
||||
// first journal:
|
||||
/** @var TransactionJournal $first */
|
||||
$first = $group->transactionJournals()
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use FireflyIII\Events\ActuallyLoggedIn;
|
||||
use FireflyIII\Events\DetectedNewIPAddress;
|
||||
use FireflyIII\Events\RegisteredUser;
|
||||
use FireflyIII\Events\RequestedNewPassword;
|
||||
@@ -116,9 +117,24 @@ class UserEventHandler
|
||||
*/
|
||||
public function createGroupMembership(RegisteredUser $event): bool
|
||||
{
|
||||
$user = $event->user;
|
||||
$user = $event->user;
|
||||
$groupExists = true;
|
||||
$groupTitle = $user->email;
|
||||
$index = 1;
|
||||
|
||||
// create a new group.
|
||||
$group = UserGroup::create(['title' => $user->email]);
|
||||
while (true === $groupExists) {
|
||||
$groupExists = UserGroup::where('title', $groupTitle)->count() > 0;
|
||||
if(false === $groupExists) {
|
||||
$group = UserGroup::create(['title' => $groupTitle]);
|
||||
break;
|
||||
}
|
||||
$groupTitle = sprintf('%s-%d', $user->email, $index);
|
||||
$index++;
|
||||
if($index > 99) {
|
||||
throw new FireflyException('Email address can no longer be used for registrations.');
|
||||
}
|
||||
}
|
||||
$role = UserRole::where('title', UserRole::OWNER)->first();
|
||||
if (null === $role) {
|
||||
throw new FireflyException('The user role is unexpectedly empty. Did you run all migrations?');
|
||||
@@ -317,12 +333,11 @@ class UserEventHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Login $event
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @param ActuallyLoggedIn $event
|
||||
*/
|
||||
public function storeUserIPAddress(Login $event): void
|
||||
public function storeUserIPAddress(ActuallyLoggedIn $event): void
|
||||
{
|
||||
Log::debug('Now in storeUserIPAddress');
|
||||
/** @var User $user */
|
||||
$user = $event->user;
|
||||
/** @var array $preference */
|
||||
|
||||
@@ -176,7 +176,9 @@ class AttachmentHelper implements AttachmentHelperInterface
|
||||
return false;
|
||||
}
|
||||
// is allowed? Save the file, without encryption.
|
||||
$this->uploadDisk->put($attachment->fileName(), $content);
|
||||
$parts = explode('/', $attachment->fileName());
|
||||
$file = $parts[count($parts) - 1];
|
||||
$this->uploadDisk->put($file, $content);
|
||||
|
||||
// update attachment.
|
||||
$attachment->md5 = md5_file($path);
|
||||
|
||||
@@ -119,4 +119,61 @@ trait TimeCollection
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function yearIs(string $year): GroupCollectorInterface
|
||||
{
|
||||
$this->query->whereYear('transaction_journals.date', '=', $year);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function monthIs(string $month): GroupCollectorInterface
|
||||
{
|
||||
$this->query->whereMonth('transaction_journals.date', '=', $month);
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
public function dayIs(string $day): GroupCollectorInterface
|
||||
{
|
||||
$this->query->whereDay('transaction_journals.date', '=', $day);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function yearBefore(string $year): GroupCollectorInterface
|
||||
{
|
||||
$this->query->whereYear('transaction_journals.date', '<=', $year);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function monthBefore(string $month): GroupCollectorInterface
|
||||
{
|
||||
$this->query->whereMonth('transaction_journals.date', '<=', $month);
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
public function dayBefore(string $day): GroupCollectorInterface
|
||||
{
|
||||
$this->query->whereDay('transaction_journals.date', '<=', $day);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function yearAfter(string $year): GroupCollectorInterface
|
||||
{
|
||||
$this->query->whereYear('transaction_journals.date', '>=', $year);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function monthAfter(string $month): GroupCollectorInterface
|
||||
{
|
||||
$this->query->whereMonth('transaction_journals.date', '>=', $month);
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
public function dayAfter(string $day): GroupCollectorInterface
|
||||
{
|
||||
$this->query->whereDay('transaction_journals.date', '>=', $day);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,6 +559,15 @@ class GroupCollector implements GroupCollectorInterface
|
||||
echo '</pre>';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function dumpQueryInLogs(): void
|
||||
{
|
||||
Log::debug($this->query->select($this->fields)->toSql()) ;
|
||||
Log::debug('Bindings',$this->query->getBindings());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a selected set of fields to arrays.
|
||||
*
|
||||
|
||||
@@ -565,4 +565,15 @@ interface GroupCollectorInterface
|
||||
*/
|
||||
public function withoutTags(): GroupCollectorInterface;
|
||||
|
||||
|
||||
public function yearIs(string $year): GroupCollectorInterface;
|
||||
public function monthIs(string $month): GroupCollectorInterface;
|
||||
public function dayIs(string $day): GroupCollectorInterface;
|
||||
public function yearBefore(string $year): GroupCollectorInterface;
|
||||
public function monthBefore(string $month): GroupCollectorInterface;
|
||||
public function dayBefore(string $day): GroupCollectorInterface;
|
||||
public function yearAfter(string $year): GroupCollectorInterface;
|
||||
public function monthAfter(string $month): GroupCollectorInterface;
|
||||
public function dayAfter(string $day): GroupCollectorInterface;
|
||||
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ class NetWorth implements NetWorthInterface
|
||||
|
||||
$netWorth = [];
|
||||
$result = [];
|
||||
Log::debug(sprintf('Now in getNetWorthByCurrency(%s)', $date->format('Y-m-d')));
|
||||
//Log::debug(sprintf('Now in getNetWorthByCurrency(%s)', $date->format('Y-m-d')));
|
||||
|
||||
// get default currency
|
||||
$default = app('amount')->getDefaultCurrencyByUser($this->user);
|
||||
@@ -90,16 +90,16 @@ class NetWorth implements NetWorthInterface
|
||||
// get the preferred currency for this account
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
Log::debug(sprintf('Now at account #%d: "%s"', $account->id, $account->name));
|
||||
//Log::debug(sprintf('Now at account #%d: "%s"', $account->id, $account->name));
|
||||
$currencyId = (int)$this->accountRepository->getMetaValue($account, 'currency_id');
|
||||
$currencyId = 0 === $currencyId ? $default->id : $currencyId;
|
||||
|
||||
Log::debug(sprintf('Currency ID is #%d', $currencyId));
|
||||
//Log::debug(sprintf('Currency ID is #%d', $currencyId));
|
||||
|
||||
// balance in array:
|
||||
$balance = $balances[$account->id] ?? '0';
|
||||
|
||||
Log::debug(sprintf('Balance is %s', $balance));
|
||||
//Log::debug(sprintf('Balance is %s', $balance));
|
||||
|
||||
// always subtract virtual balance.
|
||||
$virtualBalance = (string)$account->virtual_balance;
|
||||
@@ -107,14 +107,14 @@ class NetWorth implements NetWorthInterface
|
||||
$balance = bcsub($balance, $virtualBalance);
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Balance corrected to %s because of virtual balance (%s)', $balance, $virtualBalance));
|
||||
//Log::debug(sprintf('Balance corrected to %s because of virtual balance (%s)', $balance, $virtualBalance));
|
||||
|
||||
if (!array_key_exists($currencyId, $netWorth)) {
|
||||
$netWorth[$currencyId] = '0';
|
||||
}
|
||||
$netWorth[$currencyId] = bcadd($balance, $netWorth[$currencyId]);
|
||||
|
||||
Log::debug(sprintf('Total net worth for currency #%d is %s', $currencyId, $netWorth[$currencyId]));
|
||||
//Log::debug(sprintf('Total net worth for currency #%d is %s', $currencyId, $netWorth[$currencyId]));
|
||||
}
|
||||
ksort($netWorth);
|
||||
|
||||
|
||||
@@ -209,10 +209,10 @@ class PopupReport implements PopupReportInterface
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
|
||||
// set report accounts + the request accounts:
|
||||
$set = $attributes['accounts'] ?? new Collection;
|
||||
$set->push($account);
|
||||
//$set = $attributes['accounts'] ?? new Collection;
|
||||
//$set->push($account);
|
||||
|
||||
$collector->setBothAccounts($set)
|
||||
$collector->setDestinationAccounts(new Collection([$account]))
|
||||
->setRange($attributes['startDate'], $attributes['endDate'])
|
||||
->withAccountInformation()
|
||||
->withBudgetInformation()
|
||||
@@ -222,7 +222,6 @@ class PopupReport implements PopupReportInterface
|
||||
if (null !== $currency) {
|
||||
$collector->setCurrency($currency);
|
||||
}
|
||||
|
||||
return $collector->getExtractedJournals();
|
||||
}
|
||||
|
||||
|
||||
@@ -38,9 +38,16 @@ class Sha3SignatureGenerator implements SignatureGeneratorInterface
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function generate(WebhookMessage $message): string
|
||||
{
|
||||
// webhook is deleted
|
||||
if (null === $message->webhook) {
|
||||
throw new FireflyException('Part of a deleted webhook.');
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
$json = json_encode($message->message, JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException $e) {
|
||||
@@ -48,7 +55,7 @@ class Sha3SignatureGenerator implements SignatureGeneratorInterface
|
||||
Log::error(sprintf('JSON value: %s', $message->message));
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
throw new FireflyException('Could not generate JSON for SHA3 hash.', $e);
|
||||
throw new FireflyException('Could not generate JSON for SHA3 hash.', 0, $e);
|
||||
}
|
||||
|
||||
// signature v1 is generated using the following structure:
|
||||
|
||||
@@ -122,7 +122,7 @@ class EditController extends Controller
|
||||
}
|
||||
$request->session()->forget('accounts.edit.fromUpdate');
|
||||
|
||||
$openingBalanceAmount = app('steam')->positive((string)$repository->getOpeningBalanceAmount($account));
|
||||
$openingBalanceAmount = (string)$repository->getOpeningBalanceAmount($account);
|
||||
if ('0' === $openingBalanceAmount) {
|
||||
$openingBalanceAmount = '';
|
||||
}
|
||||
@@ -143,9 +143,9 @@ class EditController extends Controller
|
||||
'BIC' => $repository->getMetaValue($account, 'BIC'),
|
||||
'opening_balance_date' => $openingBalanceDate,
|
||||
'liability_type_id' => $account->account_type_id,
|
||||
'opening_balance' => $openingBalanceAmount,
|
||||
'opening_balance' => number_format((float)$openingBalanceAmount, $currency->decimal_places,'.',''),
|
||||
'liability_direction' => $this->repository->getMetaValue($account, 'liability_direction'),
|
||||
'virtual_balance' => $account->virtual_balance,
|
||||
'virtual_balance' => number_format((float)$account->virtual_balance, $currency->decimal_places,'.',''),
|
||||
'currency_id' => $currency->id,
|
||||
'include_net_worth' => $includeNetWorth,
|
||||
'interest' => $repository->getMetaValue($account, 'interest'),
|
||||
|
||||
@@ -101,7 +101,7 @@ class IndexController extends Controller
|
||||
|
||||
$accounts->each(
|
||||
function (Account $account) use ($activities, $startBalances, $endBalances) {
|
||||
$account->lastActivityDate = $this->isInArray($activities, $account->id);
|
||||
$account->lastActivityDate = $this->isInArrayDate($activities, $account->id);
|
||||
$account->startBalance = $this->isInArray($startBalances, $account->id);
|
||||
$account->endBalance = $this->isInArray($endBalances, $account->id);
|
||||
$account->difference = bcsub($account->endBalance, $account->startBalance);
|
||||
@@ -163,7 +163,7 @@ class IndexController extends Controller
|
||||
$accounts->each(
|
||||
function (Account $account) use ($activities, $startBalances, $endBalances) {
|
||||
// See reference nr. 68
|
||||
$account->lastActivityDate = $this->isInArray($activities, $account->id);
|
||||
$account->lastActivityDate = $this->isInArrayDate($activities, $account->id);
|
||||
$account->startBalance = $this->isInArray($startBalances, $account->id);
|
||||
$account->endBalance = $this->isInArray($endBalances, $account->id);
|
||||
$account->difference = bcsub($account->endBalance, $account->startBalance);
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace FireflyIII\Http\Controllers\Auth;
|
||||
use Adldap;
|
||||
use Cookie;
|
||||
use DB;
|
||||
use FireflyIII\Events\ActuallyLoggedIn;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Providers\RouteServiceProvider;
|
||||
@@ -119,6 +120,10 @@ class LoginController extends Controller
|
||||
|
||||
// if you just logged in, it can't be that you have a valid 2FA cookie.
|
||||
|
||||
// send a custom login event because laravel will also fire a login event if a "remember me"-cookie
|
||||
// restores the event.
|
||||
event(new ActuallyLoggedIn($this->guard()->user()));
|
||||
|
||||
return $this->sendLoginResponse($request);
|
||||
}
|
||||
Log::warning('Login attempt failed.');
|
||||
|
||||
@@ -181,7 +181,7 @@ class BudgetLimitController extends Controller
|
||||
return response()->json($array);
|
||||
}
|
||||
|
||||
return response()->json([]);
|
||||
return redirect(route('budgets.index'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -102,7 +102,8 @@ class EditController extends Controller
|
||||
'auto_budget_currency_id' => $hasOldInput ? (int)$request->old('auto_budget_currency_id') : $currency->id,
|
||||
];
|
||||
if ($autoBudget) {
|
||||
$preFilled['auto_budget_amount'] = $hasOldInput ? $request->old('auto_budget_amount') : $autoBudget->amount;
|
||||
$amount = $hasOldInput ? $request->old('auto_budget_amount') : $autoBudget->amount;
|
||||
$preFilled['auto_budget_amount'] = number_format((float)$amount, $autoBudget->transactionCurrency->decimal_places, '.', '');
|
||||
}
|
||||
|
||||
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||
|
||||
@@ -29,6 +29,7 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
@@ -210,10 +211,9 @@ class CurrencyController extends Controller
|
||||
* @param Request $request
|
||||
* @param TransactionCurrency $currency
|
||||
*
|
||||
* @return RedirectResponse|Redirector
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function disableCurrency(Request $request)
|
||||
public function disableCurrency(Request $request): JsonResponse
|
||||
{
|
||||
$currencyId = (int)$request->get('id');
|
||||
if ($currencyId > 0) {
|
||||
@@ -228,8 +228,7 @@ class CurrencyController extends Controller
|
||||
|
||||
$request->session()->flash('error', (string)trans('firefly.ask_site_owner', ['owner' => e(config('firefly.site_owner'))]));
|
||||
Log::channel('audit')->info(sprintf('Tried to disable currency %s but is not site owner.', $currency->code));
|
||||
|
||||
return redirect(route('currencies.index'));
|
||||
return response()->json([]);
|
||||
|
||||
}
|
||||
|
||||
@@ -240,8 +239,7 @@ class CurrencyController extends Controller
|
||||
|
||||
$request->session()->flash('error', $message);
|
||||
Log::channel('audit')->info(sprintf('Tried to disable currency %s but is in use.', $currency->code));
|
||||
|
||||
return redirect(route('currencies.index'));
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
$this->repository->disable($currency);
|
||||
@@ -267,7 +265,7 @@ class CurrencyController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
return redirect(route('currencies.index'));
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -144,6 +144,15 @@ class DebugController extends Controller
|
||||
$bcscale = bcscale();
|
||||
$layout = env('FIREFLY_III_LAYOUT');
|
||||
$tz = env('TZ');
|
||||
$buildNr = '(unknown)';
|
||||
$buildDate = '(unknown)';
|
||||
|
||||
if (file_exists('/var/www/counter-main.txt')) {
|
||||
$buildNr = file_get_contents('/var/www/counter-main.txt');
|
||||
}
|
||||
if (file_exists('/var/www/build-date-main.txt')) {
|
||||
$buildDate = file_get_contents('/var/www/build-date-main.txt');
|
||||
}
|
||||
|
||||
// expected + found DB version:
|
||||
$expectedDBversion = config('firefly.db_version');
|
||||
@@ -206,6 +215,8 @@ class DebugController extends Controller
|
||||
'drivers',
|
||||
'currentDriver',
|
||||
'loginProvider',
|
||||
'buildNr',
|
||||
'buildDate',
|
||||
'bcscale',
|
||||
'layout',
|
||||
'userAgent',
|
||||
|
||||
@@ -102,6 +102,9 @@ class HomeController extends Controller
|
||||
*/
|
||||
public function index(AccountRepositoryInterface $repository): mixed
|
||||
{
|
||||
if ('v3' === config('firefly.layout')) {
|
||||
return view('pwa');
|
||||
}
|
||||
$types = config('firefly.accountTypesByIdentifier.asset');
|
||||
$count = $repository->count($types);
|
||||
Log::channel('audit')->info('User visits homepage.');
|
||||
|
||||
@@ -28,6 +28,7 @@ use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\PiggyBankUpdateRequest;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
@@ -41,6 +42,7 @@ class EditController extends Controller
|
||||
{
|
||||
private AttachmentHelperInterface $attachments;
|
||||
private PiggyBankRepositoryInterface $piggyRepos;
|
||||
private AccountRepositoryInterface $accountRepository;
|
||||
|
||||
/**
|
||||
* PiggyBankController constructor.
|
||||
@@ -58,7 +60,7 @@ class EditController extends Controller
|
||||
|
||||
$this->attachments = app(AttachmentHelperInterface::class);
|
||||
$this->piggyRepos = app(PiggyBankRepositoryInterface::class);
|
||||
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
@@ -81,10 +83,11 @@ class EditController extends Controller
|
||||
// Flash some data to fill the form.
|
||||
$targetDate = $piggyBank->targetdate?->format('Y-m-d');
|
||||
$startDate = $piggyBank->startdate?->format('Y-m-d');
|
||||
$currency = $this->accountRepository->getAccountCurrency($piggyBank->account);
|
||||
|
||||
$preFilled = ['name' => $piggyBank->name,
|
||||
'account_id' => $piggyBank->account_id,
|
||||
'targetamount' => $piggyBank->targetamount,
|
||||
'targetamount' => number_format((float)$piggyBank->targetamount, $currency->decimal_places,'.',''),
|
||||
'targetdate' => $targetDate,
|
||||
'startdate' => $startDate,
|
||||
'object_group' => $piggyBank->objectGroups->first() ? $piggyBank->objectGroups->first()->title : '',
|
||||
|
||||
@@ -59,7 +59,6 @@ class ReportController extends Controller
|
||||
'category-entry' => $this->categoryEntry($attributes),
|
||||
'budget-entry' => $this->budgetEntry($attributes),
|
||||
};
|
||||
|
||||
return response()->json(['html' => $html]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,10 +648,9 @@ class CategoryController extends Controller
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return mixed|string
|
||||
* @throws JsonException
|
||||
* @return string
|
||||
*/
|
||||
public function operations(Collection $accounts, Carbon $start, Carbon $end)
|
||||
public function operations(Collection $accounts, Carbon $start, Carbon $end): string
|
||||
{
|
||||
// chart properties for cache:
|
||||
$cache = new CacheProperties;
|
||||
@@ -673,7 +672,7 @@ class CategoryController extends Controller
|
||||
|
||||
|
||||
try {
|
||||
$result = prefixView('reports.partials.categories', compact('report'))->render();
|
||||
$result = (string)prefixView('reports.partials.categories', compact('report'))->render();
|
||||
$cache->store($result);
|
||||
} catch (Throwable $e) { // @phpstan-ignore-line
|
||||
Log::error(sprintf('Could not render category::expenses: %s', $e->getMessage()));
|
||||
|
||||
@@ -34,6 +34,7 @@ use FireflyIII\Support\Http\Controllers\ModelInformation;
|
||||
use FireflyIII\Support\Http\Controllers\RuleManagement;
|
||||
use FireflyIII\Support\Search\SearchInterface;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Redirector;
|
||||
@@ -237,15 +238,17 @@ class CreateController extends Controller
|
||||
/**
|
||||
* @param Rule $rule
|
||||
*
|
||||
* @return RedirectResponse
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function duplicate(Rule $rule): RedirectResponse
|
||||
public function duplicate(Request $request): JsonResponse
|
||||
{
|
||||
$newRule = $this->ruleRepos->duplicate($rule);
|
||||
$ruleId = (int)$request->get('id');
|
||||
$rule = $this->ruleRepos->find($ruleId);
|
||||
if (null !== $rule) {
|
||||
$this->ruleRepos->duplicate($rule);
|
||||
}
|
||||
|
||||
session()->flash('success', trans('firefly.duplicated_rule', ['title' => $rule->title, 'newTitle' => $newRule->title]));
|
||||
|
||||
return redirect(route('rules.index'));
|
||||
return new JsonResponse(['OK']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,7 @@ use FireflyIII\Http\Requests\RuleGroupFormRequest;
|
||||
use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Redirector;
|
||||
@@ -62,24 +63,38 @@ class EditController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a rule group down.
|
||||
* Move a rule group in either direction.
|
||||
*
|
||||
* @param RuleGroup $ruleGroup
|
||||
* @param Request $request
|
||||
*
|
||||
* @return RedirectResponse|Redirector
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function down(RuleGroup $ruleGroup)
|
||||
public function moveGroup(Request $request): JsonResponse
|
||||
{
|
||||
$maxOrder = $this->repository->maxOrder();
|
||||
$order = (int)$ruleGroup->order;
|
||||
if ($order < $maxOrder) {
|
||||
$newOrder = $order + 1;
|
||||
$this->repository->setOrder($ruleGroup, $newOrder);
|
||||
$groupId = (int)$request->get('id');
|
||||
$ruleGroup= $this->repository->find($groupId);
|
||||
if(null !== $ruleGroup) {
|
||||
$direction = $request->get('direction');
|
||||
if('down' === $direction) {
|
||||
$maxOrder = $this->repository->maxOrder();
|
||||
$order = (int)$ruleGroup->order;
|
||||
if ($order < $maxOrder) {
|
||||
$newOrder = $order + 1;
|
||||
$this->repository->setOrder($ruleGroup, $newOrder);
|
||||
}
|
||||
}
|
||||
if('up' === $direction) {
|
||||
$order = (int)$ruleGroup->order;
|
||||
if ($order > 1) {
|
||||
$newOrder = $order - 1;
|
||||
$this->repository->setOrder($ruleGroup, $newOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirect(route('rules.index'));
|
||||
return new JsonResponse(['OK']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Edit a rule group.
|
||||
*
|
||||
@@ -106,25 +121,6 @@ class EditController extends Controller
|
||||
return prefixView('rules.rule-group.edit', compact('ruleGroup', 'subTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the rule group up.
|
||||
*
|
||||
* @param RuleGroup $ruleGroup
|
||||
*
|
||||
* @return RedirectResponse|Redirector
|
||||
*
|
||||
*/
|
||||
public function up(RuleGroup $ruleGroup)
|
||||
{
|
||||
$order = (int)$ruleGroup->order;
|
||||
if ($order > 1) {
|
||||
$newOrder = $order - 1;
|
||||
$this->repository->setOrder($ruleGroup, $newOrder);
|
||||
}
|
||||
|
||||
return redirect(route('rules.index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the rule group.
|
||||
*
|
||||
|
||||
@@ -336,8 +336,8 @@ class ConvertController extends Controller
|
||||
$sourceName = '' === $sourceName ? null : (string)$sourceName;
|
||||
$destinationId = '' === $destinationId || null === $destinationId ? null : (int)$destinationId;
|
||||
$destinationName = '' === $destinationName ? null : (string)$destinationName;
|
||||
$validSource = $validator->validateSource($sourceId, $sourceName, null);
|
||||
$validDestination = $validator->validateDestination($destinationId, $destinationName, null);
|
||||
$validSource = $validator->validateSource(['id' => $sourceId, 'name' => $sourceName,]);
|
||||
$validDestination = $validator->validateDestination(['id' => $destinationId, 'name' => $destinationName,]);
|
||||
|
||||
if (false === $validSource) {
|
||||
throw new FireflyException(sprintf(trans('firefly.convert_invalid_source'), $journal->id));
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace FireflyIII\Http\Controllers\Transaction;
|
||||
use FireflyIII\Events\StoredTransactionGroup;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
|
||||
use FireflyIII\Services\Internal\Update\GroupCloneService;
|
||||
@@ -109,9 +108,9 @@ class CreateController extends Controller
|
||||
$sourceId = (int)request()->get('source');
|
||||
$destinationId = (int)request()->get('destination');
|
||||
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$cash = $repository->getCashAccount();
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$cash = $accountRepository->getCashAccount();
|
||||
$preFilled = session()->has('preFilled') ? session('preFilled') : [];
|
||||
$subTitle = (string)trans(sprintf('breadcrumbs.create_%s', strtolower((string)$objectType)));
|
||||
$subTitleIcon = 'fa-plus';
|
||||
|
||||
@@ -30,6 +30,7 @@ use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Illuminate\View\View;
|
||||
use Log;
|
||||
@@ -157,9 +158,10 @@ class LinkController extends Controller
|
||||
*
|
||||
* @return RedirectResponse|Redirector
|
||||
*/
|
||||
public function switchLink(TransactionJournalLink $link)
|
||||
public function switchLink(Request $request)
|
||||
{
|
||||
$this->repository->switchLink($link);
|
||||
$linkId = (int)$request->get('id');
|
||||
$this->repository->switchLinkById($linkId);
|
||||
|
||||
return redirect(app('steam')->getSafePreviousUrl());
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ class MassController extends Controller
|
||||
|
||||
// reverse amounts
|
||||
foreach ($journals as $index => $journal) {
|
||||
$journals[$index]['amount'] = app('steam')->positive($journal['amount']);
|
||||
$journals[$index]['amount'] = number_format((float) app('steam')->positive($journal['amount']), $journal['currency_decimal_places'],'.','');
|
||||
$journals[$index]['foreign_amount'] = null === $journal['foreign_amount'] ?
|
||||
null : app('steam')->positive($journal['foreign_amount']);
|
||||
}
|
||||
|
||||
@@ -46,16 +46,11 @@ class StartFireflySession extends StartSession
|
||||
$safeUrl = app('steam')->getSafeUrl($url, route('index'));
|
||||
|
||||
if ($url !== $safeUrl) {
|
||||
//Log::debug(sprintf('storeCurrentUrl: converted "%s" to "%s", so will not use it.', $url, $safeUrl));
|
||||
return;
|
||||
}
|
||||
|
||||
if ('GET' === $request->method() && !$request->ajax()) {
|
||||
//Log::debug(sprintf('storeCurrentUrl: Redirect is now "%s".', $safeUrl));
|
||||
$session->setPreviousUrl($safeUrl);
|
||||
|
||||
return;
|
||||
}
|
||||
//Log::debug(sprintf('storeCurrentUrl: Refuse to set "%s" as current URL.', $safeUrl));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,6 @@ use Illuminate\Contracts\Config\Repository;
|
||||
*/
|
||||
class TrustProxies extends Middleware
|
||||
{
|
||||
/** @var int The headers to check. */
|
||||
//protected $headers = Request::HEADER_X_FORWARDED_ALL;
|
||||
|
||||
/**
|
||||
* TrustProxies constructor.
|
||||
*
|
||||
@@ -42,11 +39,7 @@ class TrustProxies extends Middleware
|
||||
*/
|
||||
public function __construct(Repository $config)
|
||||
{
|
||||
$trustedProxies = (string)config('firefly.trusted_proxies');
|
||||
$this->proxies = explode(',', $trustedProxies);
|
||||
if ('**' === $trustedProxies) {
|
||||
$this->proxies = '**';
|
||||
}
|
||||
$this->proxies = (string)config('firefly.trusted_proxies');
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class AccountFormRequest extends FormRequest
|
||||
'account_number' => $this->string('account_number'),
|
||||
'account_role' => $this->string('account_role'),
|
||||
'opening_balance' => $this->string('opening_balance'),
|
||||
'opening_balance_date' => $this->date('opening_balance_date'),
|
||||
'opening_balance_date' => $this->getCarbonDate('opening_balance_date'),
|
||||
'cc_type' => $this->string('cc_type'),
|
||||
'cc_monthly_payment_date' => $this->string('cc_monthly_payment_date'),
|
||||
'notes' => $this->stringWithNewlines('notes'),
|
||||
|
||||
@@ -46,7 +46,7 @@ class BillStoreRequest extends FormRequest
|
||||
'currency_id' => $this->integer('transaction_currency_id'),
|
||||
'currency_code' => '',
|
||||
'amount_max' => $this->string('amount_max'),
|
||||
'date' => $this->date('date'),
|
||||
'date' => $this->getCarbonDate('date'),
|
||||
'repeat_freq' => $this->string('repeat_freq'),
|
||||
'skip' => $this->integer('skip'),
|
||||
'notes' => $this->stringWithNewlines('notes'),
|
||||
@@ -69,7 +69,7 @@ class BillStoreRequest extends FormRequest
|
||||
'transaction_currency_id' => 'required|exists:transaction_currencies,id',
|
||||
'date' => 'required|date',
|
||||
'repeat_freq' => 'required|in:weekly,monthly,quarterly,half-year,yearly',
|
||||
'skip' => 'required|between:0,31',
|
||||
'skip' => 'required|integer|gte:0|lte:31',
|
||||
'active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class BillUpdateRequest extends FormRequest
|
||||
'currency_id' => $this->integer('transaction_currency_id'),
|
||||
'currency_code' => '',
|
||||
'amount_max' => $this->string('amount_max'),
|
||||
'date' => $this->date('date'),
|
||||
'date' => $this->getCarbonDate('date'),
|
||||
'repeat_freq' => $this->string('repeat_freq'),
|
||||
'skip' => $this->integer('skip'),
|
||||
'notes' => $this->stringWithNewlines('notes'),
|
||||
@@ -73,7 +73,7 @@ class BillUpdateRequest extends FormRequest
|
||||
'transaction_currency_id' => 'required|exists:transaction_currencies,id',
|
||||
'date' => 'required|date',
|
||||
'repeat_freq' => 'required|in:weekly,monthly,quarterly,half-year,yearly',
|
||||
'skip' => 'required|between:0,31',
|
||||
'skip' => 'required|integer|gte:0|lte:31',
|
||||
'active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class BudgetFormStoreRequest extends FormRequest
|
||||
return [
|
||||
'name' => 'required|between:1,100|uniqueObjectForUser:budgets,name',
|
||||
'active' => 'numeric|between:0,1',
|
||||
'auto_budget_type' => 'numeric|between:0,2',
|
||||
'auto_budget_type' => 'numeric|integer|gte:0|lte:2',
|
||||
'auto_budget_currency_id' => 'exists:transaction_currencies,id',
|
||||
'auto_budget_amount' => 'min:0|max:1000000000|required_if:auto_budget_type,1|required_if:auto_budget_type,2',
|
||||
'auto_budget_period' => 'in:daily,weekly,monthly,quarterly,half_year,yearly',
|
||||
|
||||
@@ -73,7 +73,7 @@ class BudgetFormUpdateRequest extends FormRequest
|
||||
return [
|
||||
'name' => $nameRule,
|
||||
'active' => 'numeric|between:0,1',
|
||||
'auto_budget_type' => 'numeric|between:0,2',
|
||||
'auto_budget_type' => 'numeric|integer|gte:0|lte:31',
|
||||
'auto_budget_currency_id' => 'exists:transaction_currencies,id',
|
||||
'auto_budget_amount' => 'min:0|max:1000000000|required_if:auto_budget_type,1|required_if:auto_budget_type,2',
|
||||
'auto_budget_period' => 'in:daily,weekly,monthly,quarterly,half_year,yearly',
|
||||
|
||||
@@ -42,10 +42,10 @@ class PiggyBankStoreRequest extends FormRequest
|
||||
{
|
||||
return [
|
||||
'name' => $this->string('name'),
|
||||
'startdate' => $this->date('startdate'),
|
||||
'startdate' => $this->getCarbonDate('startdate'),
|
||||
'account_id' => $this->integer('account_id'),
|
||||
'targetamount' => $this->string('targetamount'),
|
||||
'targetdate' => $this->date('targetdate'),
|
||||
'targetdate' => $this->getCarbonDate('targetdate'),
|
||||
'notes' => $this->stringWithNewlines('notes'),
|
||||
'object_group_title' => $this->string('object_group'),
|
||||
];
|
||||
|
||||
@@ -43,10 +43,10 @@ class PiggyBankUpdateRequest extends FormRequest
|
||||
{
|
||||
return [
|
||||
'name' => $this->string('name'),
|
||||
'startdate' => $this->date('startdate'),
|
||||
'startdate' => $this->getCarbonDate('startdate'),
|
||||
'account_id' => $this->integer('account_id'),
|
||||
'targetamount' => $this->string('targetamount'),
|
||||
'targetdate' => $this->date('targetdate'),
|
||||
'targetdate' => $this->getCarbonDate('targetdate'),
|
||||
'notes' => $this->stringWithNewlines('notes'),
|
||||
'object_group_title' => $this->string('object_group'),
|
||||
];
|
||||
|
||||
@@ -48,8 +48,8 @@ class ReconciliationStoreRequest extends FormRequest
|
||||
$transactions = [];
|
||||
}
|
||||
$data = [
|
||||
'start' => $this->date('start'),
|
||||
'end' => $this->date('end'),
|
||||
'start' => $this->getCarbonDate('start'),
|
||||
'end' => $this->getCarbonDate('end'),
|
||||
'start_balance' => $this->string('startBalance'),
|
||||
'end_balance' => $this->string('endBalance'),
|
||||
'difference' => $this->string('difference'),
|
||||
|
||||
@@ -59,8 +59,8 @@ class RecurrenceFormRequest extends FormRequest
|
||||
'type' => $this->string('transaction_type'),
|
||||
'title' => $this->string('title'),
|
||||
'description' => $this->string('recurring_description'),
|
||||
'first_date' => $this->date('first_date'),
|
||||
'repeat_until' => $this->date('repeat_until'),
|
||||
'first_date' => $this->getCarbonDate('first_date'),
|
||||
'repeat_until' => $this->getCarbonDate('repeat_until'),
|
||||
'nr_of_repetitions' => $this->integer('repetitions'),
|
||||
'apply_rules' => $this->boolean('apply_rules'),
|
||||
'active' => $this->boolean('active'),
|
||||
@@ -192,7 +192,7 @@ class RecurrenceFormRequest extends FormRequest
|
||||
'title' => 'required|between:1,255|uniqueObjectForUser:recurrences,title',
|
||||
'first_date' => 'required|date|after:' . $today->format('Y-m-d'),
|
||||
'repetition_type' => ['required', new ValidRecurrenceRepetitionValue, new ValidRecurrenceRepetitionType, 'between:1,20'],
|
||||
'skip' => 'required|numeric|between:0,31',
|
||||
'skip' => 'required|numeric|integer|gte:0|lte:31',
|
||||
|
||||
// optional for recurrence:
|
||||
'recurring_description' => 'between:0,65000',
|
||||
@@ -295,7 +295,7 @@ class RecurrenceFormRequest extends FormRequest
|
||||
*/
|
||||
public function validateAccountInformation(Validator $validator): void
|
||||
{
|
||||
Log::debug('Now in validateAccountInformation()');
|
||||
Log::debug('Now in validateAccountInformation (RecurrenceFormRequest)()');
|
||||
/** @var AccountValidator $accountValidator */
|
||||
$accountValidator = app(AccountValidator::class);
|
||||
$data = $validator->getData();
|
||||
@@ -326,7 +326,7 @@ class RecurrenceFormRequest extends FormRequest
|
||||
break;
|
||||
}
|
||||
// validate source account.
|
||||
$validSource = $accountValidator->validateSource($sourceId, null, null);
|
||||
$validSource = $accountValidator->validateSource(['id' => $sourceId,]);
|
||||
|
||||
// do something with result:
|
||||
if (false === $validSource) {
|
||||
@@ -338,7 +338,7 @@ class RecurrenceFormRequest extends FormRequest
|
||||
}
|
||||
|
||||
// validate destination account
|
||||
$validDestination = $accountValidator->validateDestination($destinationId, null, null);
|
||||
$validDestination = $accountValidator->validateDestination(['id' => $destinationId,]);
|
||||
// do something with result:
|
||||
if (false === $validDestination) {
|
||||
$message = (string)trans('validation.generic_invalid_destination');
|
||||
|
||||
@@ -45,7 +45,7 @@ class TagFormRequest extends FormRequest
|
||||
{
|
||||
$data = [
|
||||
'tag' => $this->string('tag'),
|
||||
'date' => $this->date('date'),
|
||||
'date' => $this->getCarbonDate('date'),
|
||||
'description' => $this->string('description'),
|
||||
];
|
||||
|
||||
|
||||
@@ -431,7 +431,7 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
'user' => $recurrence->user_id,
|
||||
'currency_id' => (int)$transaction->transaction_currency_id,
|
||||
'currency_code' => null,
|
||||
'description' => $recurrence->recurrenceTransactions()->first()->description,
|
||||
'description' => $transactions->first()->description,
|
||||
'amount' => $transaction->amount,
|
||||
'budget_id' => $this->repository->getBudget($transaction),
|
||||
'budget_name' => null,
|
||||
@@ -452,7 +452,7 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
'tags' => $this->repository->getTags($transaction),
|
||||
'piggy_bank_id' => $this->repository->getPiggyBank($transaction),
|
||||
'piggy_bank_name' => null,
|
||||
'bill_id' => null,
|
||||
'bill_id' => $this->repository->getBillId($transaction),
|
||||
'bill_name' => null,
|
||||
'recurrence_total' => $total,
|
||||
'recurrence_count' => $count,
|
||||
|
||||
@@ -68,13 +68,13 @@ class MailError extends Job implements ShouldQueue
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$email = config('firefly.site_owner');
|
||||
$email = (string) config('firefly.site_owner');
|
||||
$args = $this->exception;
|
||||
$args['loggedIn'] = $this->userData['id'] > 0;
|
||||
$args['user'] = $this->userData;
|
||||
$args['ip'] = $this->ipAddress;
|
||||
$args['token'] = config('firefly.ipinfo_token');
|
||||
if ($this->attempts() < 3) {
|
||||
if ($this->attempts() < 3 && strlen($email) > 0) {
|
||||
try {
|
||||
Mail::send(
|
||||
['emails.error-html', 'emails.error-text'],
|
||||
|
||||
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Jobs;
|
||||
|
||||
use Log;
|
||||
use FireflyIII\Models\WebhookMessage;
|
||||
use FireflyIII\Services\Webhook\WebhookSenderInterface;
|
||||
use Illuminate\Bus\Queueable;
|
||||
@@ -58,6 +59,7 @@ class SendWebhookMessage implements ShouldQueue
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
Log::debug(sprintf('Now handling webhook message #%d', $this->message->id));
|
||||
// send job!
|
||||
$sender = app(WebhookSenderInterface::class);
|
||||
$sender->setMessage($this->message);
|
||||
|
||||
@@ -25,7 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Ldap;
|
||||
|
||||
use FireflyIII\User as DatabaseUser;
|
||||
use LdapRecord\Models\OpenLDAP\User as LdapUser;
|
||||
use LdapRecord\Models\Entry;
|
||||
|
||||
/**
|
||||
* Class AttributeHandler
|
||||
@@ -33,12 +33,12 @@ use LdapRecord\Models\OpenLDAP\User as LdapUser;
|
||||
class AttributeHandler
|
||||
{
|
||||
/**
|
||||
* @param LdapUser $ldap
|
||||
* @param Entry $ldapUser
|
||||
* @param DatabaseUser $database
|
||||
*/
|
||||
public function handle(LdapUser $ldap, DatabaseUser $database)
|
||||
public function handle(Entry $ldapUser, DatabaseUser $database)
|
||||
{
|
||||
$database->email = $ldap->getFirstAttribute('mail');
|
||||
$database->email = $ldapUser->getFirstAttribute('mail');
|
||||
$database->save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,32 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* UserDefinedRule.php
|
||||
* Copyright (c) 2022 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\Ldap\Rules;
|
||||
|
||||
use LdapRecord\Laravel\Auth\Rule;
|
||||
use LdapRecord\Models\ActiveDirectory\Group;
|
||||
use LdapRecord\Models\Attributes\DistinguishedName;
|
||||
use LdapRecord\Query\ObjectNotFoundException;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
@@ -16,22 +38,51 @@ class UserDefinedRule extends Rule
|
||||
* Check if the rule passes validation.
|
||||
*
|
||||
* @return bool
|
||||
* @throws ObjectNotFoundException
|
||||
*/
|
||||
public function isValid()
|
||||
{
|
||||
// LDAP_GROUP_FILTER
|
||||
$groupFilter = config('ldap.group_filter');
|
||||
Log::debug(sprintf('UserDefinedRule with group filter "%s"', $groupFilter));
|
||||
if (null !== $groupFilter && '' !== (string)$groupFilter) {
|
||||
Log::debug('Group filter is not empty, will now apply it.');
|
||||
$administrators = Group::find($groupFilter);
|
||||
$result = $this->user->groups()->recursive()->exists($administrators);
|
||||
Log::debug(sprintf('Search result is %s.', var_export($result, true)));
|
||||
$extraFilter = config('ldap.extra_filter');
|
||||
Log::debug(sprintf('UserDefinedRule with extra filter "%s"', $extraFilter));
|
||||
|
||||
return $result;
|
||||
if (empty($extraFilter)) {
|
||||
Log::debug('Extra filter is empty, return true.');
|
||||
|
||||
return true;
|
||||
}
|
||||
Log::debug('Group filter is empty or NULL, so will return true.');
|
||||
Log::debug('Extra filter is not empty, continue.');
|
||||
|
||||
return true;
|
||||
// group class:
|
||||
// use ;
|
||||
$openLDAP = class_exists(\LdapRecord\Models\OpenLDAP\Group::class) ? \LdapRecord\Models\OpenLDAP\Group::class : '';
|
||||
$activeDirectory = class_exists(\LdapRecord\Models\ActiveDirectory\Group::class) ? \LdapRecord\Models\ActiveDirectory\Group::class : '';
|
||||
$groupClass = config('ldap.dialect') === 'OpenLDAP' ? $openLDAP : $activeDirectory;
|
||||
|
||||
Log::debug(sprintf('Will use dialect group class "%s"', $groupClass));
|
||||
|
||||
|
||||
// We've been given an invalid group filter. We will assume the
|
||||
// developer is using some group ANR attribute, and attempt
|
||||
// to check the user's membership with the resulting group.
|
||||
if (!DistinguishedName::isValid($extraFilter)) {
|
||||
Log::debug('UserDefinedRule: Is not valid DN');
|
||||
|
||||
return $this->user->groups()->recursive()->exists($groupClass::findByAnrOrFail($extraFilter));
|
||||
}
|
||||
|
||||
$head = strtolower(DistinguishedName::make($extraFilter)->head());
|
||||
Log::debug(sprintf('UserDefinedRule: Head is "%s"', $head));
|
||||
// If the head of the DN we've been given is an OU, we will assume
|
||||
// the developer is looking to filter users based on hierarchy.
|
||||
// Otherwise, we'll attempt locating a group by the given
|
||||
// group filter and checking the users group membership.
|
||||
if ('ou' === $head) {
|
||||
Log::debug('UserDefinedRule: Will return if user is a descendant of.');
|
||||
|
||||
return $this->user->isDescendantOf($extraFilter);
|
||||
}
|
||||
Log::debug('UserDefinedRule: Will return if user exists in group.');
|
||||
|
||||
return $this->user->groups()->recursive()->exists($groupClass::findOrFail($extraFilter));
|
||||
}
|
||||
}
|
||||
|
||||
49
app/Ldap/Scopes/UserDefinedScope.php
Normal file
49
app/Ldap/Scopes/UserDefinedScope.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* UserDefinedScope.php
|
||||
* Copyright (c) 2022 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\Ldap\Scopes;
|
||||
|
||||
use LdapRecord\Models\Model;
|
||||
use LdapRecord\Models\Scope;
|
||||
use LdapRecord\Query\Model\Builder;
|
||||
use Log;
|
||||
|
||||
|
||||
/**
|
||||
* Class UserDefinedScope
|
||||
*/
|
||||
class UserDefinedScope implements Scope
|
||||
{
|
||||
/**
|
||||
* Apply the scope to the given query.
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param Model $model
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function apply(Builder $query, Model $model)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Providers;
|
||||
|
||||
use Exception;
|
||||
use FireflyIII\Events\ActuallyLoggedIn;
|
||||
use FireflyIII\Events\AdminRequestedTestMessage;
|
||||
use FireflyIII\Events\DestroyedTransactionGroup;
|
||||
use FireflyIII\Events\DetectedNewIPAddress;
|
||||
@@ -74,6 +75,8 @@ class EventServiceProvider extends ServiceProvider
|
||||
Login::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@checkSingleUserIsAdmin',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@demoUserBackToEnglish',
|
||||
],
|
||||
ActuallyLoggedIn::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@storeUserIPAddress',
|
||||
],
|
||||
DetectedNewIPAddress::class => [
|
||||
|
||||
@@ -494,10 +494,8 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
public function getOpeningBalanceGroup(Account $account): ?TransactionGroup
|
||||
{
|
||||
$journal = $this->getOpeningBalance($account);
|
||||
$group = null;
|
||||
$group = $journal?->transactionGroup;
|
||||
|
||||
return $group;
|
||||
return $journal?->transactionGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -637,11 +635,9 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
*/
|
||||
public function oldestJournalDate(Account $account): ?Carbon
|
||||
{
|
||||
$result = null;
|
||||
$journal = $this->oldestJournal($account);
|
||||
$result = $journal?->date;
|
||||
|
||||
return $result;
|
||||
return $journal?->date;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -251,7 +251,7 @@ class BillRepository implements BillRepositoryInterface
|
||||
$journalIds = $set->pluck('id')->toArray();
|
||||
$amount = (string)Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount');
|
||||
$sum = bcadd($sum, $amount);
|
||||
Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $amount, $sum));
|
||||
//Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $amount, $sum));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ class BillRepository implements BillRepositoryInterface
|
||||
$amount = (string)Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount');
|
||||
$return[$currencyId] = $return[$currencyId] ?? '0';
|
||||
$return[$currencyId] = bcadd($amount, $return[$currencyId]);
|
||||
Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f (currency %d)', $amount, $return[$currencyId], $currencyId));
|
||||
//Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f (currency %d)', $amount, $return[$currencyId], $currencyId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,18 +302,18 @@ class BillRepository implements BillRepositoryInterface
|
||||
$sum = '0';
|
||||
/** @var Bill $bill */
|
||||
foreach ($bills as $bill) {
|
||||
Log::debug(sprintf('Now at bill #%d (%s)', $bill->id, $bill->name));
|
||||
//Log::debug(sprintf('Now at bill #%d (%s)', $bill->id, $bill->name));
|
||||
$dates = $this->getPayDatesInRange($bill, $start, $end);
|
||||
$count = $bill->transactionJournals()->after($start)->before($end)->count();
|
||||
$total = $dates->count() - $count;
|
||||
|
||||
Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total));
|
||||
//Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total));
|
||||
|
||||
if ($total > 0) {
|
||||
$average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2');
|
||||
$multi = bcmul($average, (string)$total);
|
||||
$sum = bcadd($sum, $multi);
|
||||
Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $multi, $sum));
|
||||
//Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $multi, $sum));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,20 +334,20 @@ class BillRepository implements BillRepositoryInterface
|
||||
$return = [];
|
||||
/** @var Bill $bill */
|
||||
foreach ($bills as $bill) {
|
||||
Log::debug(sprintf('Now at bill #%d (%s)', $bill->id, $bill->name));
|
||||
//Log::debug(sprintf('Now at bill #%d (%s)', $bill->id, $bill->name));
|
||||
$dates = $this->getPayDatesInRange($bill, $start, $end);
|
||||
$count = $bill->transactionJournals()->after($start)->before($end)->count();
|
||||
$total = $dates->count() - $count;
|
||||
$currencyId = (int)$bill->transaction_currency_id;
|
||||
|
||||
Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total));
|
||||
//Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total));
|
||||
|
||||
if ($total > 0) {
|
||||
$average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2');
|
||||
$multi = bcmul($average, (string)$total);
|
||||
$return[$currencyId] = $return[$currencyId] ?? '0';
|
||||
$return[$currencyId] = bcadd($return[$currencyId], $multi);
|
||||
Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f (for currency %d)', $multi, $return[$currencyId], $currencyId));
|
||||
//Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f (for currency %d)', $multi, $return[$currencyId], $currencyId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,7 +481,7 @@ class BillRepository implements BillRepositoryInterface
|
||||
//Log::debug(sprintf('First currentstart is %s', $currentStart->format('Y-m-d')));
|
||||
|
||||
while ($currentStart <= $end) {
|
||||
Log::debug(sprintf('Currentstart is now %s.', $currentStart->format('Y-m-d')));
|
||||
//Log::debug(sprintf('Currentstart is now %s.', $currentStart->format('Y-m-d')));
|
||||
$nextExpectedMatch = $this->nextDateMatch($bill, $currentStart);
|
||||
//Log::debug(sprintf('Next Date match after %s is %s', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d')));
|
||||
if ($nextExpectedMatch > $end) {// If nextExpectedMatch is after end, we continue
|
||||
|
||||
@@ -35,8 +35,7 @@ use Illuminate\Support\Collection;
|
||||
*/
|
||||
class OperationsRepository implements OperationsRepositoryInterface
|
||||
{
|
||||
/** @var User */
|
||||
private $user;
|
||||
private User $user;
|
||||
|
||||
/**
|
||||
* This method returns a list of all the withdrawal transaction journals (as arrays) set in that period
|
||||
@@ -340,4 +339,136 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
{
|
||||
return $this->user->categories()->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function listTransferredIn(Carbon $start, Carbon $end, Collection $accounts, ?Collection $categories = null): array
|
||||
{
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::TRANSFER])
|
||||
->setDestinationAccounts($accounts)->excludeSourceAccounts($accounts);
|
||||
if (null !== $categories && $categories->count() > 0) {
|
||||
$collector->setCategories($categories);
|
||||
}
|
||||
if (null === $categories || (null !== $categories && 0 === $categories->count())) {
|
||||
$collector->setCategories($this->getCategories());
|
||||
}
|
||||
$collector->withCategoryInformation()->withAccountInformation()->withBudgetInformation();
|
||||
$journals = $collector->getExtractedJournals();
|
||||
$array = [];
|
||||
|
||||
foreach ($journals as $journal) {
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$categoryId = (int)$journal['category_id'];
|
||||
$categoryName = (string)$journal['category_name'];
|
||||
|
||||
// catch "no category" entries.
|
||||
if (0 === $categoryId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// info about the currency:
|
||||
$array[$currencyId] = $array[$currencyId] ?? [
|
||||
'categories' => [],
|
||||
'currency_id' => $currencyId,
|
||||
'currency_name' => $journal['currency_name'],
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
'currency_decimal_places' => $journal['currency_decimal_places'],
|
||||
];
|
||||
|
||||
// info about the categories:
|
||||
$array[$currencyId]['categories'][$categoryId] = $array[$currencyId]['categories'][$categoryId] ?? [
|
||||
'id' => $categoryId,
|
||||
'name' => $categoryName,
|
||||
'transaction_journals' => [],
|
||||
];
|
||||
|
||||
// add journal to array:
|
||||
// only a subset of the fields.
|
||||
$journalId = (int)$journal['transaction_journal_id'];
|
||||
$array[$currencyId]['categories'][$categoryId]['transaction_journals'][$journalId] = [
|
||||
'amount' => app('steam')->positive($journal['amount']),
|
||||
'date' => $journal['date'],
|
||||
'source_account_id' => $journal['source_account_id'],
|
||||
'category_name' => $journal['category_name'],
|
||||
'source_account_name' => $journal['source_account_name'],
|
||||
'destination_account_id' => $journal['destination_account_id'],
|
||||
'destination_account_name' => $journal['destination_account_name'],
|
||||
'description' => $journal['description'],
|
||||
'transaction_group_id' => $journal['transaction_group_id'],
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function listTransferredOut(Carbon $start, Carbon $end, Collection $accounts, ?Collection $categories = null): array
|
||||
{
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::TRANSFER])
|
||||
->setSourceAccounts($accounts)->excludeDestinationAccounts($accounts);
|
||||
if (null !== $categories && $categories->count() > 0) {
|
||||
$collector->setCategories($categories);
|
||||
}
|
||||
if (null === $categories || (null !== $categories && 0 === $categories->count())) {
|
||||
$collector->setCategories($this->getCategories());
|
||||
}
|
||||
$collector->withCategoryInformation()->withAccountInformation()->withBudgetInformation();
|
||||
$journals = $collector->getExtractedJournals();
|
||||
$array = [];
|
||||
|
||||
foreach ($journals as $journal) {
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$categoryId = (int)$journal['category_id'];
|
||||
$categoryName = (string)$journal['category_name'];
|
||||
|
||||
// catch "no category" entries.
|
||||
if (0 === $categoryId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// info about the currency:
|
||||
$array[$currencyId] = $array[$currencyId] ?? [
|
||||
'categories' => [],
|
||||
'currency_id' => $currencyId,
|
||||
'currency_name' => $journal['currency_name'],
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
'currency_decimal_places' => $journal['currency_decimal_places'],
|
||||
];
|
||||
|
||||
// info about the categories:
|
||||
$array[$currencyId]['categories'][$categoryId] = $array[$currencyId]['categories'][$categoryId] ?? [
|
||||
'id' => $categoryId,
|
||||
'name' => $categoryName,
|
||||
'transaction_journals' => [],
|
||||
];
|
||||
|
||||
// add journal to array:
|
||||
// only a subset of the fields.
|
||||
$journalId = (int)$journal['transaction_journal_id'];
|
||||
$array[$currencyId]['categories'][$categoryId]['transaction_journals'][$journalId] = [
|
||||
'amount' => app('steam')->negative($journal['amount']),
|
||||
'date' => $journal['date'],
|
||||
'source_account_id' => $journal['source_account_id'],
|
||||
'category_name' => $journal['category_name'],
|
||||
'source_account_name' => $journal['source_account_name'],
|
||||
'destination_account_id' => $journal['destination_account_id'],
|
||||
'destination_account_name' => $journal['destination_account_name'],
|
||||
'description' => $journal['description'],
|
||||
'transaction_group_id' => $journal['transaction_group_id'],
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,36 @@ interface OperationsRepositoryInterface
|
||||
*/
|
||||
public function listExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $categories = null): array;
|
||||
|
||||
/**
|
||||
* This method returns a list of all the transfer transaction journals (as arrays) set in that period
|
||||
* which have the specified category set to them, transferred INTO the listed accounts.
|
||||
* It excludes any transfers between the listed accounts.
|
||||
* It's grouped per currency, with as few details in the array as possible. Amounts are always negative.
|
||||
*
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
* @param Collection|null $categories
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listTransferredIn(Carbon $start, Carbon $end, Collection $accounts, ?Collection $categories = null): array;
|
||||
|
||||
/**
|
||||
* This method returns a list of all the transfer transaction journals (as arrays) set in that period
|
||||
* which have the specified category set to them, transferred FROM the listed accounts.
|
||||
* It excludes any transfers between the listed accounts.
|
||||
* It's grouped per currency, with as few details in the array as possible. Amounts are always negative.
|
||||
*
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
* @param Collection|null $categories
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listTransferredOut(Carbon $start, Carbon $end, Collection $accounts, ?Collection $categories = null): array;
|
||||
|
||||
/**
|
||||
* This method returns a list of all the deposit transaction journals (as arrays) set in that period
|
||||
* which have the specified category set to them. It's grouped per currency, with as few details in the array
|
||||
|
||||
@@ -389,4 +389,18 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface
|
||||
|
||||
return TransactionJournalLink::whereDestinationId($two->id)->whereSourceId($one->id)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function switchLinkById(int $linkId): bool
|
||||
{
|
||||
/** @var TransactionJournalLink $link */
|
||||
$link = TransactionJournalLink::find($linkId);
|
||||
if (null !== $link && $link->source->user->id === $this->user->id) {
|
||||
$this->switchLink($link);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +154,13 @@ interface LinkTypeRepositoryInterface
|
||||
*/
|
||||
public function switchLink(TransactionJournalLink $link): bool;
|
||||
|
||||
/**
|
||||
* @param int $linkId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function switchLinkById(int $linkId): bool;
|
||||
|
||||
/**
|
||||
* @param LinkType $linkType
|
||||
* @param array $data
|
||||
|
||||
@@ -613,4 +613,20 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getBillId(RecurrenceTransaction $recTransaction): ?int
|
||||
{
|
||||
$return = null;
|
||||
/** @var RecurrenceTransactionMeta $meta */
|
||||
foreach ($recTransaction->recurrenceTransactionMeta as $meta) {
|
||||
if ('bill_id' === $meta->name) {
|
||||
$return = (int)$meta->value;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,15 @@ interface RecurringRepositoryInterface
|
||||
*/
|
||||
public function getCategoryId(RecurrenceTransaction $recTransaction): ?int;
|
||||
|
||||
/**
|
||||
* Get the category from a recurring transaction transaction.
|
||||
*
|
||||
* @param RecurrenceTransaction $recTransaction
|
||||
*
|
||||
* @return null|int
|
||||
*/
|
||||
public function getBillId(RecurrenceTransaction $recTransaction): ?int;
|
||||
|
||||
/**
|
||||
* Get the category from a recurring transaction transaction.
|
||||
*
|
||||
|
||||
@@ -329,10 +329,8 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
|
||||
*/
|
||||
public function resetOrder(): bool
|
||||
{
|
||||
$this->user->ruleGroups()->where('active', false)->update(['order' => 0]);
|
||||
$set = $this->user
|
||||
->ruleGroups()
|
||||
->where('active', true)
|
||||
->whereNull('deleted_at')
|
||||
->orderBy('order', 'ASC')
|
||||
->orderBy('title', 'DESC')
|
||||
@@ -363,7 +361,6 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
|
||||
{
|
||||
$set = $ruleGroup->rules()
|
||||
->orderBy('order', 'ASC')
|
||||
->where('active', true)
|
||||
->orderBy('title', 'DESC')
|
||||
->orderBy('updated_at', 'DESC')
|
||||
->get(['rules.*']);
|
||||
|
||||
@@ -484,4 +484,16 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function countAttachments(int $journalId): int
|
||||
{
|
||||
/** @var TransactionJournal $journal */
|
||||
$journal = $this->user->transactionJournals()->find($journalId);
|
||||
|
||||
return $journal->attachments()->count();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,13 @@ interface TransactionGroupRepositoryInterface
|
||||
*/
|
||||
public function getLocation(int $journalId): ?Location;
|
||||
|
||||
/**
|
||||
* @param int $journalId
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function countAttachments(int $journalId): int;
|
||||
|
||||
/**
|
||||
* Return object with all found meta field things as Carbon objects.
|
||||
*
|
||||
|
||||
@@ -26,6 +26,7 @@ use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\Role;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -164,7 +165,10 @@ class UserRepository implements UserRepositoryInterface
|
||||
public function destroy(User $user): bool
|
||||
{
|
||||
Log::debug(sprintf('Calling delete() on user %d', $user->id));
|
||||
|
||||
$user->groupMemberships()->delete();
|
||||
$user->delete();
|
||||
$this->deleteEmptyGroups();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -396,4 +400,20 @@ class UserRepository implements UserRepositoryInterface
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function deleteEmptyGroups(): void
|
||||
{
|
||||
$groups = UserGroup::get();
|
||||
/** @var UserGroup $group */
|
||||
foreach ($groups as $group) {
|
||||
$count = $group->groupMemberships()->count();
|
||||
if (0 === $count) {
|
||||
Log::info(sprintf('Deleted empty group #%d ("%s")', $group->id, $group->title));
|
||||
$group->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@ interface UserRepositoryInterface
|
||||
*/
|
||||
public function all(): Collection;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function deleteEmptyGroups(): void;
|
||||
|
||||
/**
|
||||
* Gives a user a role.
|
||||
*
|
||||
|
||||
@@ -60,14 +60,14 @@ class IsTransferAccount implements Rule
|
||||
$validator->setTransactionType(TransactionType::TRANSFER);
|
||||
$validator->setUser(auth()->user());
|
||||
|
||||
$validAccount = $validator->validateSource(null, (string)$value, null);
|
||||
$validAccount = $validator->validateSource(['name' => (string)$value,]);
|
||||
if (true === $validAccount) {
|
||||
Log::debug('Found account based on name. Return true.');
|
||||
|
||||
// found by name, use repos to return.
|
||||
return true;
|
||||
}
|
||||
$validAccount = $validator->validateSource((int)$value, null, null);
|
||||
$validAccount = $validator->validateSource(['id' => (int)$value,]);
|
||||
Log::debug(sprintf('Search by id (%d), result is %s.', (int)$value, var_export($validAccount, true)));
|
||||
|
||||
return false !== $validAccount;
|
||||
|
||||
@@ -530,6 +530,7 @@ trait AccountServiceTrait
|
||||
if (null === $obGroup) {
|
||||
return $this->createOBGroupV2($account, $openingBalance, $openingBalanceDate);
|
||||
}
|
||||
Log::debug('Update OB group');
|
||||
|
||||
// if exists, update:
|
||||
$currency = $this->accountRepository->getAccountCurrency($account);
|
||||
@@ -547,6 +548,7 @@ trait AccountServiceTrait
|
||||
|
||||
// if amount is negative:
|
||||
if (1 === bccomp('0', $openingBalance)) {
|
||||
Log::debug('Amount is negative.');
|
||||
// account transaction loses money:
|
||||
$accountTransaction->amount = app('steam')->negative($openingBalance);
|
||||
$accountTransaction->transaction_currency_id = $currency->id;
|
||||
@@ -556,6 +558,7 @@ trait AccountServiceTrait
|
||||
$obTransaction->transaction_currency_id = $currency->id;
|
||||
}
|
||||
if (-1 === bccomp('0', $openingBalance)) {
|
||||
Log::debug('Amount is positive.');
|
||||
// account gains money:
|
||||
$accountTransaction->amount = app('steam')->positive($openingBalance);
|
||||
$accountTransaction->transaction_currency_id = $currency->id;
|
||||
|
||||
@@ -63,9 +63,6 @@ trait JournalServiceTrait
|
||||
// some debug logging:
|
||||
Log::debug(sprintf('Now in getAccount(%s)', $direction), $data);
|
||||
|
||||
// final result:
|
||||
$result = null;
|
||||
|
||||
// expected type of source account, in order of preference
|
||||
/** @var array $array */
|
||||
$array = config('firefly.expected_source_types');
|
||||
@@ -76,12 +73,17 @@ trait JournalServiceTrait
|
||||
$message = 'Based on the fact that the transaction is a %s, the %s account should be in: %s. Direction is %s.';
|
||||
Log::debug(sprintf($message, $transactionType, $direction, implode(', ', $expectedTypes[$transactionType] ?? ['UNKNOWN']), $direction));
|
||||
|
||||
Log::debug('Now searching by ID');
|
||||
$result = $this->findAccountById($data, $expectedTypes[$transactionType]);
|
||||
$result = $this->findAccountByName($result, $data, $expectedTypes[$transactionType]);
|
||||
Log::debug('If nothing is found, searching by IBAN');
|
||||
$result = $this->findAccountByIban($result, $data, $expectedTypes[$transactionType]);
|
||||
Log::debug('If nothing is found, searching by number');
|
||||
$result = $this->findAccountByNumber($result, $data, $expectedTypes[$transactionType]);
|
||||
Log::debug('If nothing is found, searching by name');
|
||||
$result = $this->findAccountByName($result, $data, $expectedTypes[$transactionType]);
|
||||
Log::debug('If nothing is found, create it.');
|
||||
$result = $this->createAccount($result, $data, $expectedTypes[$transactionType][0]);
|
||||
|
||||
Log::debug('If cant be created, return cash account.');
|
||||
return $this->getCashAccount($result, $data, $expectedTypes[$transactionType]);
|
||||
}
|
||||
|
||||
@@ -118,7 +120,6 @@ trait JournalServiceTrait
|
||||
{
|
||||
// second attempt, find by name.
|
||||
if (null === $account && null !== $data['name']) {
|
||||
Log::debug('Found nothing by account ID.');
|
||||
// find by preferred type.
|
||||
$source = $this->accountRepository->findByName($data['name'], [$types[0]]);
|
||||
// or any expected type.
|
||||
@@ -204,7 +205,7 @@ trait JournalServiceTrait
|
||||
if (null !== $account) {
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Was also given %s account #%d ("%s") so will simply return that.',
|
||||
'Was given %s account #%d ("%s") so will simply return that.',
|
||||
$account->accountType->type, $account->id, $account->name
|
||||
|
||||
)
|
||||
|
||||
@@ -136,11 +136,12 @@ trait RecurringTransactionTrait
|
||||
$validator = app(AccountValidator::class);
|
||||
$validator->setUser($recurrence->user);
|
||||
$validator->setTransactionType($recurrence->transactionType->type);
|
||||
if (!$validator->validateSource($source->id, null, null)) {
|
||||
|
||||
if (!$validator->validateSource(['id' => $source->id])) {
|
||||
throw new FireflyException(sprintf('Source invalid: %s', $validator->sourceError));
|
||||
}
|
||||
|
||||
if (!$validator->validateDestination($destination->id, null, null)) {
|
||||
if (!$validator->validateDestination(['id' => $destination->id])) {
|
||||
throw new FireflyException(sprintf('Destination invalid: %s', $validator->destError));
|
||||
}
|
||||
if (array_key_exists('foreign_amount', $array) && '' === (string)$array['foreign_amount']) {
|
||||
|
||||
@@ -292,7 +292,6 @@ class AccountUpdateService
|
||||
*/
|
||||
private function updateOpeningBalance(Account $account, array $data): void
|
||||
{
|
||||
|
||||
// has valid initial balance (IB) data?
|
||||
$type = $account->accountType;
|
||||
if (in_array($type->type, $this->canHaveOpeningBalance, true)) {
|
||||
|
||||
@@ -215,7 +215,7 @@ class JournalUpdateService
|
||||
$validator->setTransactionType($expectedType);
|
||||
$validator->setUser($this->transactionJournal->user);
|
||||
|
||||
$result = $validator->validateSource($sourceId, $sourceName, null);
|
||||
$result = $validator->validateSource(['id' =>$sourceId]);
|
||||
Log::debug(sprintf('hasValidSourceAccount(%d, "%s") will return %s', $sourceId, $sourceName, var_export($result, true)));
|
||||
|
||||
// See reference nr. 95
|
||||
@@ -309,7 +309,7 @@ class JournalUpdateService
|
||||
$validator->setTransactionType($expectedType);
|
||||
$validator->setUser($this->transactionJournal->user);
|
||||
$validator->source = $this->getValidSourceAccount();
|
||||
$result = $validator->validateDestination($destId, $destName, null);
|
||||
$result = $validator->validateDestination(['id' => $destId]);
|
||||
Log::debug(sprintf('hasValidDestinationAccount(%d, "%s") will return %s', $destId, $destName, var_export($result, true)));
|
||||
|
||||
// See reference nr. 96
|
||||
|
||||
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Webhook;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Webhook\SignatureGeneratorInterface;
|
||||
use FireflyIII\Models\WebhookAttempt;
|
||||
use FireflyIII\Models\WebhookMessage;
|
||||
@@ -55,7 +56,24 @@ class StandardWebhookSender implements WebhookSenderInterface
|
||||
// have the signature generator generate a signature. If it fails, the error thrown will
|
||||
// end up in send() to be caught.
|
||||
$signatureGenerator = app(SignatureGeneratorInterface::class);
|
||||
$signature = $signatureGenerator->generate($this->message);
|
||||
|
||||
try {
|
||||
$signature = $signatureGenerator->generate($this->message);
|
||||
} catch(FireflyException $e) {
|
||||
Log::error('Did not send message because of a Firefly III Exception.');
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
$attempt = new WebhookAttempt;
|
||||
$attempt->webhookMessage()->associate($this->message);
|
||||
$attempt->status_code = 0;
|
||||
$attempt->logs = sprintf('Exception: %s', $e->getMessage());
|
||||
$attempt->save();
|
||||
$this->message->errored = true;
|
||||
$this->message->sent = false;
|
||||
$this->message->save();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Trying to send webhook message #%d', $this->message->id));
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\Http\Controllers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* Trait BasicDataSupport
|
||||
*
|
||||
@@ -30,15 +32,28 @@ namespace FireflyIII\Support\Http\Controllers;
|
||||
trait BasicDataSupport
|
||||
{
|
||||
/**
|
||||
* Find the ID in a given array. Return '0' of not there (amount).
|
||||
* Find the ID in a given array. Return '0' if not there (amount).
|
||||
*
|
||||
* @param array $array
|
||||
* @param int $entryId
|
||||
*
|
||||
* @return null|mixed
|
||||
*/
|
||||
protected function isInArray(array $array, int $entryId) // helper for data (math, calculations)
|
||||
protected function isInArray(array $array, int $entryId)
|
||||
{
|
||||
return $array[$entryId] ?? '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the ID in a given array. Return null if not there (amount).
|
||||
*
|
||||
* @param array $array
|
||||
* @param int $entryId
|
||||
*
|
||||
* @return null|Carbon
|
||||
*/
|
||||
protected function isInArrayDate(array $array, int $entryId): ?Carbon
|
||||
{
|
||||
return $array[$entryId] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,17 +98,17 @@ class ParseDateString
|
||||
return $this->parseRelativeDate($date);
|
||||
}
|
||||
if ('xxxx-xx-xx' === strtolower($date)) {
|
||||
throw new FireflyException(sprintf('[a]Not a recognised date format: "%s"', $date));
|
||||
throw new FireflyException(sprintf('[a] Not a recognised date format: "%s"', $date));
|
||||
}
|
||||
// can't do a partial year:
|
||||
$substrCount = substr_count(substr($date, 0, 4), 'x', 0);
|
||||
if (10 === strlen($date) && $substrCount > 0 && $substrCount < 4) {
|
||||
throw new FireflyException(sprintf('[b]Not a recognised date format: "%s"', $date));
|
||||
throw new FireflyException(sprintf('[b] Not a recognised date format: "%s"', $date));
|
||||
}
|
||||
|
||||
// maybe a date range
|
||||
if (10 === strlen($date) && (str_contains($date, 'xx') || str_contains($date, 'xxxx'))) {
|
||||
Log::debug(sprintf('[c]Detected a date range ("%s"), return a fake date.', $date));
|
||||
Log::debug(sprintf('[c] Detected a date range ("%s"), return a fake date.', $date));
|
||||
// very lazy way to parse the date without parsing it, because this specific function
|
||||
// cant handle date ranges.
|
||||
return new Carbon('1984-09-17');
|
||||
@@ -211,34 +211,39 @@ class ParseDateString
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
* @param Carbon $journalDate
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function parseRange(string $date, Carbon $journalDate): array
|
||||
public function parseRange(string $date): array
|
||||
{
|
||||
// several types of range can be submitted
|
||||
$result = [
|
||||
'exact' => new Carbon('1984-09-17'),
|
||||
];
|
||||
switch (true) {
|
||||
default:
|
||||
break;
|
||||
case $this->isDayRange($date):
|
||||
return $this->parseDayRange($date, $journalDate);
|
||||
$result = $this->parseDayRange($date);
|
||||
break;
|
||||
case $this->isMonthRange($date):
|
||||
return $this->parseMonthRange($date, $journalDate);
|
||||
$result = $this->parseMonthRange($date);
|
||||
break;
|
||||
case $this->isYearRange($date):
|
||||
return $this->parseYearRange($date, $journalDate);
|
||||
$result = $this->parseYearRange($date);
|
||||
break;
|
||||
case $this->isMonthDayRange($date):
|
||||
return $this->parseMonthDayRange($date, $journalDate);
|
||||
$result = $this->parseMonthDayRange($date);
|
||||
break;
|
||||
case $this->isDayYearRange($date):
|
||||
return $this->parseDayYearRange($date, $journalDate);
|
||||
$result = $this->parseDayYearRange($date);
|
||||
break;
|
||||
case $this->isMonthYearRange($date):
|
||||
return $this->parseMonthYearRange($date, $journalDate);
|
||||
$result = $this->parseMonthYearRange($date);
|
||||
break;
|
||||
}
|
||||
|
||||
return [
|
||||
'start' => new Carbon('1984-09-17'),
|
||||
'end' => new Carbon('1984-09-17'),
|
||||
];
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,23 +266,18 @@ class ParseDateString
|
||||
}
|
||||
|
||||
/**
|
||||
* format of string is xxxx-xx-DD
|
||||
*
|
||||
* @param string $date
|
||||
* @param Carbon $journalDate
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseDayRange(string $date, Carbon $journalDate): array
|
||||
protected function parseDayRange(string $date): array
|
||||
{
|
||||
// format of string is xxxx-xx-DD
|
||||
$validDate = str_replace(['xxxx'], [$journalDate->year], $date);
|
||||
$validDate = str_replace(['xx'], [$journalDate->format('m')], $validDate);
|
||||
Log::debug(sprintf('parseDayRange: Parsed "%s" into "%s"', $date, $validDate));
|
||||
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfDay();
|
||||
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfDay();
|
||||
$parts = explode('-', $date);
|
||||
|
||||
return [
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
'day' => $parts[2],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -301,29 +301,19 @@ class ParseDateString
|
||||
}
|
||||
|
||||
/**
|
||||
* format of string is xxxx-MM-xx
|
||||
*
|
||||
* @param string $date
|
||||
* @param Carbon $journalDate
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseMonthRange(string $date, Carbon $journalDate): array
|
||||
protected function parseMonthRange(string $date): array
|
||||
{
|
||||
// because 31 would turn February into March unexpectedly and the exact day is irrelevant here.
|
||||
$day = $journalDate->format('d');
|
||||
if ((int)$day > 28) {
|
||||
$day = '28';
|
||||
}
|
||||
|
||||
// format of string is xxxx-MM-xx
|
||||
$validDate = str_replace(['xxxx'], [$journalDate->year], $date);
|
||||
$validDate = str_replace(['xx'], [$day], $validDate);
|
||||
Log::debug(sprintf('parseMonthRange: Parsed "%s" into "%s"', $date, $validDate));
|
||||
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfMonth();
|
||||
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfMonth();
|
||||
Log::debug(sprintf('parseMonthRange: Parsed "%s".', $date));
|
||||
$parts = explode('-', $date);
|
||||
|
||||
return [
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
'month' => $parts[1],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -347,24 +337,19 @@ class ParseDateString
|
||||
}
|
||||
|
||||
/**
|
||||
* format of string is YYYY-xx-xx
|
||||
*
|
||||
* @param string $date
|
||||
* @param Carbon $journalDate
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseYearRange(string $date, Carbon $journalDate): array
|
||||
protected function parseYearRange(string $date): array
|
||||
{
|
||||
// format of string is YYYY-xx-xx
|
||||
// kind of a convulted way of replacing variables but I'm lazy.
|
||||
$validDate = str_replace(['xx-xx'], [sprintf('%s-xx', $journalDate->format('m'))], $date);
|
||||
$validDate = str_replace(['xx'], [$journalDate->format('d')], $validDate);
|
||||
Log::debug(sprintf('parseYearRange: Parsed "%s" into "%s"', $date, $validDate));
|
||||
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfYear();
|
||||
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfYear();
|
||||
Log::debug(sprintf('parseYearRange: Parsed "%s"', $date));
|
||||
$parts = explode('-', $date);
|
||||
|
||||
return [
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
'year' => $parts[0],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -388,23 +373,20 @@ class ParseDateString
|
||||
}
|
||||
|
||||
/**
|
||||
* format of string is xxxx-MM-DD
|
||||
*
|
||||
* @param string $date
|
||||
* @param Carbon $journalDate
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parseMonthDayRange(string $date, Carbon $journalDate): array
|
||||
private function parseMonthDayRange(string $date): array
|
||||
{
|
||||
// Any year.
|
||||
// format of string is xxxx-MM-DD
|
||||
$validDate = str_replace(['xxxx'], [$journalDate->year], $date);
|
||||
Log::debug(sprintf('parseMonthDayRange: Parsed "%s" into "%s"', $date, $validDate));
|
||||
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfDay();
|
||||
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfDay();
|
||||
Log::debug(sprintf('parseMonthDayRange: Parsed "%s".', $date));
|
||||
$parts = explode('-', $date);
|
||||
|
||||
return [
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
'month' => $parts[1],
|
||||
'day' => $parts[2],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -428,23 +410,20 @@ class ParseDateString
|
||||
}
|
||||
|
||||
/**
|
||||
* format of string is YYYY-xx-DD
|
||||
*
|
||||
* @param string $date
|
||||
* @param Carbon $journalDate
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parseDayYearRange(string $date, Carbon $journalDate): array
|
||||
private function parseDayYearRange(string $date): array
|
||||
{
|
||||
// Any year.
|
||||
// format of string is YYYY-xx-DD
|
||||
$validDate = str_replace(['xx'], [$journalDate->format('m')], $date);
|
||||
Log::debug(sprintf('parseDayYearRange: Parsed "%s" into "%s"', $date, $validDate));
|
||||
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfDay();
|
||||
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfDay();
|
||||
Log::debug(sprintf('parseDayYearRange: Parsed "%s".', $date));
|
||||
$parts = explode('-', $date);
|
||||
|
||||
return [
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
'year' => $parts[0],
|
||||
'day' => $parts[2],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -468,28 +447,20 @@ class ParseDateString
|
||||
}
|
||||
|
||||
/**
|
||||
* format of string is YYYY-MM-xx
|
||||
*
|
||||
* @param string $date
|
||||
* @param Carbon $journalDate
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseMonthYearRange(string $date, Carbon $journalDate): array
|
||||
protected function parseMonthYearRange(string $date): array
|
||||
{
|
||||
// because 31 would turn February into March unexpectedly and the exact day is irrelevant here.
|
||||
$day = $journalDate->format('d');
|
||||
if ((int)$day > 28) {
|
||||
$day = '28';
|
||||
}
|
||||
|
||||
// format of string is YYYY-MM-xx
|
||||
$validDate = str_replace(['xx'], [$day], $date);
|
||||
Log::debug(sprintf('parseMonthYearRange: Parsed "%s" into "%s"', $date, $validDate));
|
||||
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfMonth();
|
||||
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfMonth();
|
||||
Log::debug(sprintf('parseMonthYearRange: Parsed "%s".', $date));
|
||||
$parts = explode('-', $date);
|
||||
|
||||
return [
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
'year' => $parts[0],
|
||||
'month' => $parts[1],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@ class BudgetReportGenerator
|
||||
'budget_limits' => [],
|
||||
];
|
||||
|
||||
$noBudget = $this->nbRepository->sumExpenses($this->start, $this->end);
|
||||
$noBudget = $this->nbRepository->sumExpenses($this->start, $this->end, $this->accounts);
|
||||
foreach ($noBudget as $noBudgetEntry) {
|
||||
|
||||
// currency information:
|
||||
|
||||
@@ -66,6 +66,11 @@ class CategoryReportGenerator
|
||||
{
|
||||
$earnedWith = $this->opsRepository->listIncome($this->start, $this->end, $this->accounts);
|
||||
$spentWith = $this->opsRepository->listExpenses($this->start, $this->end, $this->accounts);
|
||||
|
||||
// also transferred out and transferred into these accounts in this category:
|
||||
$transferredIn = $this->opsRepository->listTransferredIn($this->start, $this->end, $this->accounts);
|
||||
$transferredOut = $this->opsRepository->listTransferredOut($this->start, $this->end, $this->accounts);
|
||||
|
||||
$earnedWithout = $this->noCatRepository->listIncome($this->start, $this->end, $this->accounts);
|
||||
$spentWithout = $this->noCatRepository->listExpenses($this->start, $this->end, $this->accounts);
|
||||
|
||||
@@ -75,7 +80,7 @@ class CategoryReportGenerator
|
||||
];
|
||||
|
||||
// needs four for-each loops.
|
||||
foreach ([$earnedWith, $spentWith, $earnedWithout, $spentWithout] as $data) {
|
||||
foreach ([$earnedWith, $spentWith, $earnedWithout, $spentWithout, $transferredIn, $transferredOut] as $data) {
|
||||
$this->processOpsArray($data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ trait ConvertsDataTypes
|
||||
*
|
||||
* @return Carbon|null
|
||||
*/
|
||||
protected function date(string $field): ?Carbon
|
||||
protected function getCarbonDate(string $field): ?Carbon
|
||||
{
|
||||
$result = null;
|
||||
try {
|
||||
|
||||
@@ -156,7 +156,7 @@ class OperatorQuerySearch implements SearchInterface
|
||||
$parser = new QueryParser();
|
||||
try {
|
||||
$query1 = $parser->parse($query);
|
||||
} catch (TypeError|LogicException $e) {
|
||||
} catch (TypeError | LogicException $e) {
|
||||
Log::error($e->getMessage());
|
||||
Log::error(sprintf('Could not parse search: "%s".', $query));
|
||||
throw new FireflyException('Invalid search value. See the logs.', 0, $e);
|
||||
@@ -245,6 +245,7 @@ class OperatorQuerySearch implements SearchInterface
|
||||
private function handleSearchNode(Node $searchNode): void
|
||||
{
|
||||
$class = get_class($searchNode);
|
||||
Log::debug(sprintf('Now in handleSearchNode(%s)', $class));
|
||||
switch ($class) {
|
||||
default:
|
||||
Log::error(sprintf('Cannot handle node %s', $class));
|
||||
@@ -252,7 +253,7 @@ class OperatorQuerySearch implements SearchInterface
|
||||
case Subquery::class:
|
||||
// loop all notes in subquery:
|
||||
foreach ($searchNode->getNodes() as $subNode) { // @phpstan-ignore-line
|
||||
$this->handleSearchNode($subNode); // lets hope its not too recursive!
|
||||
$this->handleSearchNode($subNode); // let's hope it's not too recursive!
|
||||
}
|
||||
break;
|
||||
case Word::class:
|
||||
@@ -282,13 +283,15 @@ class OperatorQuerySearch implements SearchInterface
|
||||
'value' => (string)$value,
|
||||
]
|
||||
);
|
||||
} else {
|
||||
Log::debug(sprintf('Added operator type "%s"', $operator));
|
||||
}
|
||||
if (!in_array($operator, $this->validOperators, true)) {
|
||||
Log::debug(sprintf('Added INVALID operator type "%s"', $operator));
|
||||
$this->invalidOperators[] = [
|
||||
'type' => $operator,
|
||||
'value' => (string)$value,
|
||||
];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -302,7 +305,7 @@ class OperatorQuerySearch implements SearchInterface
|
||||
*/
|
||||
private function updateCollector(string $operator, string $value): bool
|
||||
{
|
||||
Log::debug(sprintf('updateCollector("%s", "%s")', $operator, $value));
|
||||
Log::debug(sprintf('Now in updateCollector("%s", "%s")', $operator, $value));
|
||||
|
||||
// check if alias, replace if necessary:
|
||||
$operator = self::getRootOperator($operator);
|
||||
@@ -603,48 +606,15 @@ class OperatorQuerySearch implements SearchInterface
|
||||
//
|
||||
case 'date_is':
|
||||
$range = $this->parseDateRange($value);
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Set "%s" using collector with value "%s" (%s - %s)', $operator, $value, $range['start']->format('Y-m-d'),
|
||||
$range['end']->format('Y-m-d')
|
||||
)
|
||||
);
|
||||
$this->collector->setRange($range['start'], $range['end']);
|
||||
|
||||
// add to operators manually:
|
||||
$this->operators->push(['type' => 'date_before', 'value' => $range['start']->format('Y-m-d'),]);
|
||||
$this->operators->push(['type' => 'date_after', 'value' => $range['end']->format('Y-m-d'),]);
|
||||
|
||||
$this->setExactDateParams($range);
|
||||
return false;
|
||||
case 'date_before':
|
||||
Log::debug(sprintf('Value for date_before is "%s"', $value));
|
||||
$range = $this->parseDateRange($value);
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Set "%s" using collector with value "%s" (%s - %s)', $operator, $value, $range['start']->format('Y-m-d'),
|
||||
$range['end']->format('Y-m-d')
|
||||
)
|
||||
);
|
||||
|
||||
// add to operators manually:
|
||||
$this->operators->push(['type' => 'date_before', 'value' => $range['start']->format('Y-m-d'),]);
|
||||
$this->collector->setBefore($range['start']);
|
||||
|
||||
$this->setDateBeforeParams($range);
|
||||
return false;
|
||||
case 'date_after':
|
||||
Log::debug(sprintf('Value for date_after is "%s"', $value));
|
||||
$range = $this->parseDateRange($value);
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Set "%s" using collector with value "%s" (%s - %s)', $operator, $value, $range['start']->format('Y-m-d'),
|
||||
$range['end']->format('Y-m-d')
|
||||
)
|
||||
);
|
||||
|
||||
// add to operators manually:
|
||||
$this->operators->push(['type' => 'date_after', 'value' => $range['end']->format('Y-m-d'),]);
|
||||
$this->collector->setAfter($range['end']);
|
||||
|
||||
$this->setDateAfterParams($range);
|
||||
return false;
|
||||
case 'created_on':
|
||||
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
|
||||
@@ -864,13 +834,12 @@ class OperatorQuerySearch implements SearchInterface
|
||||
{
|
||||
$parser = new ParseDateString;
|
||||
if ($parser->isDateRange($value)) {
|
||||
return $parser->parseRange($value, $this->date);
|
||||
return $parser->parseRange($value);
|
||||
}
|
||||
$parsedDate = $parser->parseDate($value);
|
||||
|
||||
return [
|
||||
'start' => $parsedDate,
|
||||
'end' => $parsedDate,
|
||||
'exact' => $parsedDate,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -881,4 +850,119 @@ class OperatorQuerySearch implements SearchInterface
|
||||
{
|
||||
return $this->words;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $range
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function setExactDateParams(array $range): void
|
||||
{
|
||||
/**
|
||||
* @var string $key
|
||||
* @var Carbon|string $value
|
||||
*/
|
||||
foreach ($range as $key => $value) {
|
||||
switch ($key) {
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot handle key "%s" in setExactParameters()', $key));
|
||||
case 'exact':
|
||||
Log::debug(sprintf('Set date_is_exact value "%s"', $value->format('Y-m-d')));
|
||||
$this->collector->setRange($value, $value);
|
||||
$this->operators->push(['type' => 'date_is', 'value' => $value->format('Y-m-d'),]);
|
||||
break;
|
||||
case 'year':
|
||||
Log::debug(sprintf('Set date_is_exact YEAR value "%s"', $value));
|
||||
$this->collector->yearIs($value);
|
||||
$this->operators->push(['type' => 'date_is_year', 'value' => $value,]);
|
||||
break;
|
||||
case 'month':
|
||||
Log::debug(sprintf('Set date_is_exact MONTH value "%s"', $value));
|
||||
$this->collector->monthIs($value);
|
||||
$this->operators->push(['type' => 'date_is_month', 'value' => $value,]);
|
||||
break;
|
||||
case 'day':
|
||||
Log::debug(sprintf('Set date_is_exact DAY value "%s"', $value));
|
||||
$this->collector->dayIs($value);
|
||||
$this->operators->push(['type' => 'date_is_day', 'value' => $value,]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $range
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function setDateBeforeParams(array $range): void
|
||||
{
|
||||
/**
|
||||
* @var string $key
|
||||
* @var Carbon|string $value
|
||||
*/
|
||||
foreach ($range as $key => $value) {
|
||||
switch ($key) {
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot handle key "%s" in setDateBeforeParams()', $key));
|
||||
case 'exact':
|
||||
$this->collector->setBefore($value);
|
||||
$this->operators->push(['type' => 'date_before', 'value' => $value->format('Y-m-d'),]);
|
||||
break;
|
||||
case 'year':
|
||||
Log::debug(sprintf('Set date_is_before YEAR value "%s"', $value));
|
||||
$this->collector->yearBefore($value);
|
||||
$this->operators->push(['type' => 'date_before_year', 'value' => $value,]);
|
||||
break;
|
||||
case 'month':
|
||||
Log::debug(sprintf('Set date_is_before MONTH value "%s"', $value));
|
||||
$this->collector->monthBefore($value);
|
||||
$this->operators->push(['type' => 'date_before_month', 'value' => $value,]);
|
||||
break;
|
||||
case 'day':
|
||||
Log::debug(sprintf('Set date_is_before DAY value "%s"', $value));
|
||||
$this->collector->dayBefore($value);
|
||||
$this->operators->push(['type' => 'date_before_day', 'value' => $value,]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $range
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function setDateAfterParams(array $range)
|
||||
{
|
||||
/**
|
||||
* @var string $key
|
||||
* @var Carbon|string $value
|
||||
*/
|
||||
foreach ($range as $key => $value) {
|
||||
switch ($key) {
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot handle key "%s" in setDateAfterParams()', $key));
|
||||
case 'exact':
|
||||
$this->collector->setAfter($value);
|
||||
$this->operators->push(['type' => 'date_after', 'value' => $value->format('Y-m-d'),]);
|
||||
break;
|
||||
case 'year':
|
||||
Log::debug(sprintf('Set date_is_after YEAR value "%s"', $value));
|
||||
$this->collector->yearAfter($value);
|
||||
$this->operators->push(['type' => 'date_after_year', 'value' => $value,]);
|
||||
break;
|
||||
case 'month':
|
||||
Log::debug(sprintf('Set date_is_after MONTH value "%s"', $value));
|
||||
$this->collector->monthAfter($value);
|
||||
$this->operators->push(['type' => 'date_after_month', 'value' => $value,]);
|
||||
break;
|
||||
case 'day':
|
||||
Log::debug(sprintf('Set date_is_after DAY value "%s"', $value));
|
||||
$this->collector->dayAfter($value);
|
||||
$this->operators->push(['type' => 'date_after_day', 'value' => $value,]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,7 +406,7 @@ class Steam
|
||||
->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) AS max_date')]); // @phpstan-ignore-line
|
||||
|
||||
foreach ($set as $entry) {
|
||||
$date = new Carbon($entry->max_date, 'UTC');
|
||||
$date = new Carbon($entry->max_date, config('app.timezone'));
|
||||
$date->setTimezone(config('app.timezone'));
|
||||
$list[(int)$entry->account_id] = $date;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,12 @@ namespace FireflyIII\Support\System;
|
||||
|
||||
use Artisan;
|
||||
use Crypt;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Laravel\Passport\Console\KeysCommand;
|
||||
use Log;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
/**
|
||||
* Class OAuthKeys
|
||||
@@ -62,7 +67,23 @@ class OAuthKeys
|
||||
*/
|
||||
public static function keysInDatabase(): bool
|
||||
{
|
||||
return app('fireflyconfig')->has(self::PRIVATE_KEY) && app('fireflyconfig')->has(self::PUBLIC_KEY);
|
||||
$privateKey = '';
|
||||
$publicKey = '';
|
||||
// better check if keys are in the database:
|
||||
if (app('fireflyconfig')->has(self::PRIVATE_KEY) && app('fireflyconfig')->has(self::PUBLIC_KEY)) {
|
||||
try {
|
||||
$privateKey = (string)app('fireflyconfig')->get(self::PRIVATE_KEY)?->data;
|
||||
$publicKey = (string)app('fireflyconfig')->get(self::PUBLIC_KEY)?->data;
|
||||
} catch (ContainerExceptionInterface | NotFoundExceptionInterface | FireflyException $e) {
|
||||
Log::error(sprintf('Could not validate keysInDatabase(): %s', $e->getMessage()));
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
if ('' !== $privateKey && '' !== $publicKey) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,16 +118,30 @@ class OAuthKeys
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function restoreKeysFromDB(): void
|
||||
public static function restoreKeysFromDB(): bool
|
||||
{
|
||||
$privateContent = Crypt::decrypt(app('fireflyconfig')->get(self::PRIVATE_KEY)->data);
|
||||
$publicContent = Crypt::decrypt(app('fireflyconfig')->get(self::PUBLIC_KEY)->data);
|
||||
$privateKey = (string)app('fireflyconfig')->get(self::PRIVATE_KEY)?->data;
|
||||
$publicKey = (string)app('fireflyconfig')->get(self::PUBLIC_KEY)?->data;
|
||||
try {
|
||||
$privateContent = Crypt::decrypt($privateKey);
|
||||
$publicContent = Crypt::decrypt($publicKey);
|
||||
} catch(DecryptException $e) {
|
||||
Log::error('Could not decrypt pub/private keypair.');
|
||||
Log::error($e->getMessage());
|
||||
|
||||
// delete config vars from DB:
|
||||
app('fireflyconfig')->delete(self::PRIVATE_KEY);
|
||||
app('fireflyconfig')->delete(self::PUBLIC_KEY);
|
||||
|
||||
return false;
|
||||
}
|
||||
$private = storage_path('oauth-private.key');
|
||||
$public = storage_path('oauth-public.key');
|
||||
file_put_contents($private, $privateContent);
|
||||
file_put_contents($public, $publicContent);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user