mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-08-21 21:02:32 +00:00
Compare commits
1330 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
aea6575358 | ||
|
d0054e657e | ||
|
d2e312e1c3 | ||
|
8678f9af65 | ||
|
cca29bcdfe | ||
|
40f2659b91 | ||
|
d306183c95 | ||
|
a34d888024 | ||
|
2932689c53 | ||
|
b0b8a8cc25 | ||
|
ce8056424f | ||
|
1567a0181b | ||
|
b60ad97150 | ||
|
cc93e4442b | ||
|
1f4ec8211e | ||
|
3a9cbcc565 | ||
|
fe6cce7495 | ||
|
f98e24e443 | ||
|
4ffbc0932c | ||
|
9eec12a4de | ||
|
40d1011c37 | ||
|
aa4682af6a | ||
|
47bea97c96 | ||
|
78b76d0f65 | ||
|
3a70a85283 | ||
|
aa7e6166c1 | ||
|
08dcd9407b | ||
|
2c263d6e82 | ||
|
ca9a4e44b4 | ||
|
a1b74eaa8e | ||
|
a3732a65c9 | ||
|
f6a162def1 | ||
|
7e96f212c6 | ||
|
67c4ae5d49 | ||
|
c0ce65e71f | ||
|
77cc04d9b3 | ||
|
5c8ed110f5 | ||
|
3104ee1263 | ||
|
76ed261441 | ||
|
0b36facec3 | ||
|
2a4f6d6f1f | ||
|
ffe9eaaae5 | ||
|
2a2c6335a5 | ||
|
8b36c17e30 | ||
|
dae3c4ccc7 | ||
|
0514258290 | ||
|
c5fdeacc87 | ||
|
b11ea13b1d | ||
|
9823603a0c | ||
|
366a618d00 | ||
|
dee681ed2a | ||
|
8c116862e2 | ||
|
9a86b907d7 | ||
|
b70861df30 | ||
|
f8a1e9b6d5 | ||
|
aaac47ebfc | ||
|
fd1feadc67 | ||
|
1fc54f5207 | ||
|
04efdf75db | ||
|
eee4909755 | ||
|
c8bf7e9c28 | ||
|
07e2c1000c | ||
|
e9afd21375 | ||
|
b1ea6f5595 | ||
|
69b454da7d | ||
|
c19c7c680b | ||
|
12ceecd57b | ||
|
75f64225ee | ||
|
f157eff2c3 | ||
|
95a185ec39 | ||
|
7963e511b9 | ||
|
2608e1ae74 | ||
|
13a6dd56a3 | ||
|
a7969dd4b4 | ||
|
b80b026246 | ||
|
a1a93d9bbb | ||
|
12df3d7b8e | ||
|
e564a4a0d3 | ||
|
b2a9128b2b | ||
|
866bcbf702 | ||
|
6d538dc568 | ||
|
c5737c7fc9 | ||
|
c60c0a93a4 | ||
|
ed7119bae5 | ||
|
c9cb310a2f | ||
|
fdfe18a952 | ||
|
5631daa173 | ||
|
38aa22fa5f | ||
|
eab7d400e5 | ||
|
29e6fba089 | ||
|
979cb605f5 | ||
|
be4db5983d | ||
|
aabcbaa665 | ||
|
dc18db5a17 | ||
|
fb140b1379 | ||
|
97be088716 | ||
|
3e39deec36 | ||
|
6f55049fb6 | ||
|
066d114dfe | ||
|
acb8bd7244 | ||
|
d75a1583cc | ||
|
c553f9eee0 | ||
|
117059077f | ||
|
c3555bae1f | ||
|
2c7ab07b41 | ||
|
12f31ffb81 | ||
|
3cb4cc88e8 | ||
|
82ea17d2eb | ||
|
61ac8adc7d | ||
|
7681a2a544 | ||
|
5a5bce953c | ||
|
1263fd7b98 | ||
|
c3ea62aa44 | ||
|
82ebf92b06 | ||
|
d99adb515a | ||
|
5ca239e177 | ||
|
b525893c39 | ||
|
7cdc159a50 | ||
|
0f441aef42 | ||
|
6497dc1701 | ||
|
bbe13487d3 | ||
|
8fc6f82a04 | ||
|
e791215bb8 | ||
|
db83d541b4 | ||
|
08bc3e7596 | ||
|
9c47c6173f | ||
|
1458300768 | ||
|
1752ea527d | ||
|
acea869a98 | ||
|
91426b9491 | ||
|
0491623313 | ||
|
91e96aa4b9 | ||
|
c099572fd8 | ||
|
7d481efff9 | ||
|
a210112dc6 | ||
|
04e4eea735 | ||
|
3e0fb03a0b | ||
|
17d042d4a2 | ||
|
a54883ab91 | ||
|
dfb3f81b69 | ||
|
ea1d543795 | ||
|
c917d2b1c4 | ||
|
96419fb359 | ||
|
0b0e0054b3 | ||
|
1199a44f92 | ||
|
1a81cd45f2 | ||
|
6134372101 | ||
|
4ec830a2e3 | ||
|
73df3040ca | ||
|
0dc5282898 | ||
|
fafa0bfeef | ||
|
e7e512e2f8 | ||
|
467571d3bb | ||
|
11b01f0cd8 | ||
|
f069ee1b0f | ||
|
e51ff88671 | ||
|
ee68f1df3e | ||
|
456ec4ee0a | ||
|
9c17594c34 | ||
|
2dfc37c1d8 | ||
|
1d36221607 | ||
|
388c886552 | ||
|
fde2dd1c92 | ||
|
42898846c6 | ||
|
a475c99987 | ||
|
ac0e2c9885 | ||
|
260ef1a07e | ||
|
9d59a9b83e | ||
|
c5434aba13 | ||
|
dd33b96c12 | ||
|
349e449cb8 | ||
|
0524ca0297 | ||
|
7bdaf8bac3 | ||
|
1dd62a1934 | ||
|
0a4c4729cf | ||
|
daf4c2ad49 | ||
|
2e60ab8763 | ||
|
62d58afb68 | ||
|
93cd7310f4 | ||
|
1b8f4f7735 | ||
|
d446c1c5e7 | ||
|
d5bfe682fe | ||
|
47fc3398e0 | ||
|
62bd6d13d1 | ||
|
9f1ea0807b | ||
|
9c5b639986 | ||
|
ec73607974 | ||
|
e442eb6ce2 | ||
|
a7e5072c8e | ||
|
db9c0600a8 | ||
|
cea1673058 | ||
|
3f93941dd9 | ||
|
4fa33c892a | ||
|
fb3230845d | ||
|
37250cbde3 | ||
|
3424ec1c27 | ||
|
fe9adba7fb | ||
|
b1135cee60 | ||
|
d44347d7d6 | ||
|
f04a28215e | ||
|
7b54d93c52 | ||
|
a61999baff | ||
|
fbeb4b60a3 | ||
|
91b7c307b6 | ||
|
92239322f9 | ||
|
128f9cc9eb | ||
|
bbab89d884 | ||
|
6fd040d420 | ||
|
c8cec91a93 | ||
|
38bb074751 | ||
|
337088b567 | ||
|
1727273d98 | ||
|
adeb569d84 | ||
|
dd8142e26a | ||
|
50df395f72 | ||
|
519ca4b2af | ||
|
f54b4c3abc | ||
|
43a66fd378 | ||
|
f4938cfed7 | ||
|
6badf101d7 | ||
|
3d27f22d2e | ||
|
acac5f8ea2 | ||
|
1d1b3afab5 | ||
|
ad2c6390a6 | ||
|
c16bcf7b57 | ||
|
7ecf5804bc | ||
|
b43e197dde | ||
|
48bd80d4c4 | ||
|
fc67c9a476 | ||
|
ca861e2a5d | ||
|
a626f71b9f | ||
|
d0f1509a9e | ||
|
64194da2f2 | ||
|
f4ac589eab | ||
|
96b2184d27 | ||
|
e196ff7281 | ||
|
bc05afeabb | ||
|
5e900736f8 | ||
|
761e9effae | ||
|
e0f1211896 | ||
|
fc5494e7f4 | ||
|
45a05111e6 | ||
|
afe9d109b3 | ||
|
768c7e53b4 | ||
|
a20d9fa8f1 | ||
|
d60ae63153 | ||
|
1a1c85b268 | ||
|
dd3ddaa838 | ||
|
56fcf73ce1 | ||
|
c9e7018490 | ||
|
bc6a34bb47 | ||
|
fcf350b06b | ||
|
6c393fb8e0 | ||
|
d7ef5df8c3 | ||
|
e250ffbd18 | ||
|
4b5f500dc1 | ||
|
6d69832711 | ||
|
233305ecb4 | ||
|
78f9fbae40 | ||
|
be46aa77f2 | ||
|
e741ba2f65 | ||
|
3203f7e1b7 | ||
|
f4aa982247 | ||
|
27c1f1d0d5 | ||
|
e291e32a29 | ||
|
9072388dd0 | ||
|
5f4da0f6a4 | ||
|
0583aa53a9 | ||
|
1d3ace5f21 | ||
|
3e8fe70915 | ||
|
bf8ce5471e | ||
|
262f8a33c6 | ||
|
4f934e54cf | ||
|
158bf06efb | ||
|
436017baee | ||
|
05e55696bb | ||
|
f825b5923c | ||
|
9a9893236e | ||
|
0ee843988f | ||
|
adf7e10879 | ||
|
59e449c806 | ||
|
cc05bfa181 | ||
|
b8c7aa68b4 | ||
|
6e80b2f4c8 | ||
|
ce205ab413 | ||
|
626a9aac4b | ||
|
73d85ca8c6 | ||
|
96795fe197 | ||
|
5cacf15322 | ||
|
590ce971bf | ||
|
7d5e93650b | ||
|
561dc57c03 | ||
|
625d626b25 | ||
|
dcfec9bd89 | ||
|
f1ff681b06 | ||
|
9bc9aa0bb0 | ||
|
24ea20f769 | ||
|
1faa520168 | ||
|
7e30d761c5 | ||
|
750aa80b7c | ||
|
861941353b | ||
|
3fda7aa1e5 | ||
|
6f886e6eaa | ||
|
9223f3ef71 | ||
|
6dbf4a3e49 | ||
|
3b9b74b5ea | ||
|
418a6ab5ad | ||
|
2268155994 | ||
|
b219283d66 | ||
|
f077adaefd | ||
|
8823666aa9 | ||
|
ff9146ab91 | ||
|
c40be5299c | ||
|
dcb2f45124 | ||
|
f6322440cf | ||
|
21e4108fd8 | ||
|
8e33688268 | ||
|
a3fb2717cb | ||
|
8017cf9d34 | ||
|
8fe304e65b | ||
|
28485e461e | ||
|
bf270745f0 | ||
|
d1e60cd546 | ||
|
834db4d02f | ||
|
2f8d75eb73 | ||
|
53eef3fda7 | ||
|
0bcc010de8 | ||
|
b43bf35e98 | ||
|
769023e36c | ||
|
2bf47f6d58 | ||
|
a3e1821ca0 | ||
|
dc1754bca7 | ||
|
8879d76dd9 | ||
|
cec99b83a0 | ||
|
498873c208 | ||
|
80c8f131c8 | ||
|
7344bd9054 | ||
|
44b4e146eb | ||
|
e330eafc91 | ||
|
b9e7541cf6 | ||
|
0d595d576a | ||
|
ab602c98d9 | ||
|
a0722fe022 | ||
|
4454c0b911 | ||
|
ae61064029 | ||
|
21168b8a49 | ||
|
077a3fb5a8 | ||
|
8266cdba66 | ||
|
aa0d3fd54c | ||
|
86080ad871 | ||
|
6b8144e8af | ||
|
10ab561fbb | ||
|
5d3606c12d | ||
|
e8d74bd735 | ||
|
7c791d4fbf | ||
|
ee2fba415a | ||
|
81bef28607 | ||
|
d46ba4325f | ||
|
e044670c55 | ||
|
66c13f35e7 | ||
|
f2cb675267 | ||
|
6f9b69a032 | ||
|
3a3eb4e84f | ||
|
c81955d84a | ||
|
0543733e3d | ||
|
336fb5f5a6 | ||
|
6d592d45b0 | ||
|
45d2467772 | ||
|
643248f9e4 | ||
|
94b18798ed | ||
|
4a18178a86 | ||
|
7bdde6822b | ||
|
a118588fe7 | ||
|
88bc81b9f5 | ||
|
1af68843b0 | ||
|
f704ade86b | ||
|
2dff8aec69 | ||
|
aae26c5da9 | ||
|
2dbdcf73ed | ||
|
9960b063e7 | ||
|
13834a276e | ||
|
a50945ad53 | ||
|
068b9e388f | ||
|
6158288295 | ||
|
b9fc298a0a | ||
|
a697b0735f | ||
|
6b82a8b29a | ||
|
158377a522 | ||
|
3d32b8eb59 | ||
|
9bc53c6644 | ||
|
a1b9baae30 | ||
|
6b6132b91b | ||
|
fdf26905c6 | ||
|
8f3731dabe | ||
|
e3521692ca | ||
|
dc8df37f5a | ||
|
141ae05cd6 | ||
|
584f4fb286 | ||
|
fdec8ab6a2 | ||
|
3f79ffecaf | ||
|
5ee071fdc7 | ||
|
88e0985776 | ||
|
a942313f85 | ||
|
f2446d46aa | ||
|
78c8680300 | ||
|
1cb979404b | ||
|
44a017db99 | ||
|
69a67ca977 | ||
|
dd579cc19b | ||
|
0346b09cb0 | ||
|
eb15d2bbd8 | ||
|
6a20f3113a | ||
|
b4e8bb1e0f | ||
|
83754960a6 | ||
|
892f262261 | ||
|
0bccf0d734 | ||
|
826dce324f | ||
|
d9dad4387e | ||
|
62c4a9a7fc | ||
|
386b069c83 | ||
|
6a48c354b7 | ||
|
ff0b1d0ec9 | ||
|
ceaba5a11c | ||
|
560bd6a92b | ||
|
05103c0676 | ||
|
88ea2aad28 | ||
|
55cd717df4 | ||
|
812e3d4a74 | ||
|
5946e4b9b5 | ||
|
ef54e0a845 | ||
|
20597c5ee1 | ||
|
057e76acd0 | ||
|
3582d79530 | ||
|
1c72e742cf | ||
|
2ac582cd18 | ||
|
e36302315c | ||
|
e48d14e14a | ||
|
608aa5c31e | ||
|
533398115d | ||
|
a8d69f850a | ||
|
98cb74e00c | ||
|
49c5c9ba15 | ||
|
64979fd941 | ||
|
dccafee383 | ||
|
0444ad5221 | ||
|
693c6fb71b | ||
|
d5bcaf42ac | ||
|
b002635d1b | ||
|
19e3a10a28 | ||
|
1d87da7745 | ||
|
0840883546 | ||
|
9d283b85e2 | ||
|
f031b6fa3f | ||
|
4b03bc92c0 | ||
|
487c81eeb2 | ||
|
26f71400e9 | ||
|
daeb06ede8 | ||
|
fe9705d33e | ||
|
25ea1c8f5f | ||
|
80a2fd485e | ||
|
18f8b102c3 | ||
|
b99c8dd32a | ||
|
52f1d96cfb | ||
|
c143d54c97 | ||
|
ac3881779c | ||
|
65e0700f2c | ||
|
733cee5c8c | ||
|
6b09466819 | ||
|
b29c0f9d74 | ||
|
5d239b896c | ||
|
52770a970b | ||
|
bbf923b849 | ||
|
d735c5d10b | ||
|
2417224991 | ||
|
d515bda868 | ||
|
8e332119e3 | ||
|
5ac3dccb37 | ||
|
e9354e1b6a | ||
|
8700393e61 | ||
|
8d76eaf633 | ||
|
99b6eb3cd9 | ||
|
36ffa46441 | ||
|
d110fea710 | ||
|
09bd94d44e | ||
|
3b8b45d12d | ||
|
39045fe2fc | ||
|
b7e7c82bd7 | ||
|
473eeaa206 | ||
|
36b93d6d2b | ||
|
f24feb62c8 | ||
|
69b82a5043 | ||
|
547db2f27b | ||
|
6b6599eb79 | ||
|
fcad295809 | ||
|
9a1e3701a3 | ||
|
ba3b82cc87 | ||
|
c768949034 | ||
|
4749c854bc | ||
|
3887231051 | ||
|
3078adac2d | ||
|
ec40d91f85 | ||
|
399cb3bc3f | ||
|
fe2bba2c40 | ||
|
2bbdd76ee1 | ||
|
a8ace43470 | ||
|
9c5c5b56ef | ||
|
109b9000d2 | ||
|
2194cd9da1 | ||
|
ac2834954c | ||
|
136043bb23 | ||
|
459c0fc136 | ||
|
22ab01c9ff | ||
|
cb7466247c | ||
|
a87b1fca1b | ||
|
0d4b971bd0 | ||
|
2b6fe7cae7 | ||
|
beb1ac4c9d | ||
|
923cdcde16 | ||
|
4ff4ca642c | ||
|
a968525d3f | ||
|
f73a23db97 | ||
|
4437cb67b9 | ||
|
abfb47674c | ||
|
f4deaf78e7 | ||
|
38f588da1d | ||
|
d7be6fc9f6 | ||
|
dd4b640424 | ||
|
ffbe400e33 | ||
|
533ea67278 | ||
|
9ad0bea76d | ||
|
7d6bc09f18 | ||
|
40a8a5a8e7 | ||
|
47c0a2cf03 | ||
|
2c787dd96d | ||
|
de340130b8 | ||
|
abc71e4cd6 | ||
|
444c2af5b0 | ||
|
07277b7282 | ||
|
41f1bb1dbd | ||
|
ee337b9476 | ||
|
1cbe63e293 | ||
|
eae3bf4d0e | ||
|
7502497827 | ||
|
a66541fd04 | ||
|
522d99477c | ||
|
3a4869205d | ||
|
5ae49e8e3e | ||
|
915321db61 | ||
|
bcd5b580cb | ||
|
70226a24c9 | ||
|
dd4995e02b | ||
|
46d3b70154 | ||
|
540c03670b | ||
|
8037e4a4c4 | ||
|
15a13be329 | ||
|
9509825552 | ||
|
e46ef2f92c | ||
|
dc21afe14a | ||
|
7667c0fa4a | ||
|
7f747cd4ef | ||
|
1e3693101b | ||
|
d31640734c | ||
|
ce3478041a | ||
|
ad2752a62c | ||
|
d29ebdbf26 | ||
|
491b3547b5 | ||
|
71cb8fe038 | ||
|
816b291ed3 | ||
|
b9f6119c68 | ||
|
c87d7458fc | ||
|
aed5ef3fba | ||
|
523824225e | ||
|
2a2a18f378 | ||
|
c29fb13941 | ||
|
30549da044 | ||
|
e81775fbce | ||
|
e7520a82c4 | ||
|
cf0e089616 | ||
|
df8207bd9f | ||
|
d65d555d63 | ||
|
159920296a | ||
|
bfd7e009cc | ||
|
682f9283a6 | ||
|
bbe8a97945 | ||
|
f93d11643f | ||
|
78bae33433 | ||
|
7cab89a291 | ||
|
95e91b9af8 | ||
|
5e08461385 | ||
|
632d95f1fb | ||
|
edd1b25330 | ||
|
ffaf48dda7 | ||
|
0e465ade48 | ||
|
ab80803f0f | ||
|
88d3db4dc8 | ||
|
da4dbb7319 | ||
|
808b405dd6 | ||
|
a429aaa6fd | ||
|
8db9641480 | ||
|
8dec769d64 | ||
|
06ad377729 | ||
|
569f736831 | ||
|
7702ed027b | ||
|
7c068afe05 | ||
|
211d136b7a | ||
|
434e0653fe | ||
|
cb134a7e13 | ||
|
08db90f606 | ||
|
964d552d28 | ||
|
40d3d9906a | ||
|
122467799f | ||
|
b1ca56a296 | ||
|
cb3504642c | ||
|
a8f10bbb09 | ||
|
b75849d4ab | ||
|
0c88bea772 | ||
|
7aaddf2a60 | ||
|
35d6db5e73 | ||
|
6642e93581 | ||
|
4aaacf8e2e | ||
|
0a8e6e9f07 | ||
|
062272a6e8 | ||
|
d610302e79 | ||
|
69846bfb18 | ||
|
c4bca4c214 | ||
|
14a8c686c0 | ||
|
c6cc750166 | ||
|
3cce84dfa7 | ||
|
c8f4c6bd49 | ||
|
17501c6cad | ||
|
aa64e7947e | ||
|
9adfbf1668 | ||
|
c6f2d4a0c4 | ||
|
4b7fc7b80b | ||
|
5c30146ee0 | ||
|
28af10444d | ||
|
a6f0faa058 | ||
|
b6227e633c | ||
|
47dd8610c6 | ||
|
6ffdbcafc5 | ||
|
d89f45d534 | ||
|
f014828423 | ||
|
f26c672421 | ||
|
b1b9804aca | ||
|
5c2c947785 | ||
|
1319cb2e4e | ||
|
1ecf3d04d9 | ||
|
b8d4776671 | ||
|
43b829bf02 | ||
|
6c150f9d06 | ||
|
bf818e1ede | ||
|
4022b6b9cd | ||
|
ab5ff6446e | ||
|
ef4bc0ca0c | ||
|
07e31d6745 | ||
|
e0bcbb0396 | ||
|
6a5bdebb7c | ||
|
bfcc1d8f03 | ||
|
eed6d6c49d | ||
|
cf05970ccf | ||
|
fa6e82dd25 | ||
|
16aaa0b5a6 | ||
|
70e146aedd | ||
|
338d7587b2 | ||
|
50837af607 | ||
|
989e931edf | ||
|
9238efbd3a | ||
|
ea2af2378d | ||
|
094ddfcf5f | ||
|
7329098ed8 | ||
|
0f229e4d7b | ||
|
b08eccd076 | ||
|
4e6e7c0562 | ||
|
eb331104df | ||
|
d6b664375e | ||
|
90c4f449e8 | ||
|
d5e8f5810d | ||
|
6215ec8cf6 | ||
|
f484785395 | ||
|
4eb93dd139 | ||
|
2da3f74057 | ||
|
4b2abb6f25 | ||
|
c38429d3a3 | ||
|
d78a5d1225 | ||
|
907ff89609 | ||
|
7758dcaaba | ||
|
ed6da918e1 | ||
|
30373db89c | ||
|
7e032fdbb3 | ||
|
d232a7abbb | ||
|
6b6b9ae242 | ||
|
241bcce6c1 | ||
|
f490d9ebe8 | ||
|
fb09843e5a | ||
|
ab95698181 | ||
|
28b3134ae2 | ||
|
ec47c9cd00 | ||
|
5e062908a5 | ||
|
d0cefd7005 | ||
|
c1276e789a | ||
|
7ac4cd4665 | ||
|
1797bf8108 | ||
|
8c32e619cb | ||
|
a0d800bbb9 | ||
|
0f276f10ee | ||
|
473c6aad84 | ||
|
f9b60fef85 | ||
|
f7ea140018 | ||
|
0287416aa5 | ||
|
0dfcfbb97e | ||
|
d82c386402 | ||
|
3c3a32b1fa | ||
|
8e2f78226a | ||
|
617360c631 | ||
|
0f2bd34a62 | ||
|
23a028f011 | ||
|
6c8da850a7 | ||
|
1bf5ae2d3d | ||
|
37a25625de | ||
|
206887bc71 | ||
|
1e8551da4a | ||
|
6dd88d25dd | ||
|
81089e989b | ||
|
8a4355e786 | ||
|
20947bc8c1 | ||
|
da0436c60e | ||
|
04af43a9fa | ||
|
9a2d87eb42 | ||
|
cb66925f22 | ||
|
c7b6688250 | ||
|
129dacbfe2 | ||
|
568ef92f79 | ||
|
75671dc982 | ||
|
4d55dbea6c | ||
|
67b7f90c26 | ||
|
3efd112d8b | ||
|
df5830b5f9 | ||
|
a6d8b985b2 | ||
|
e76a6dc00f | ||
|
77ee046b3f | ||
|
464f86eb1d | ||
|
b9bbd2872e | ||
|
7a70b8234c | ||
|
e6bf9faa3e | ||
|
ede7b1b35c | ||
|
6c095d8efa | ||
|
95bb8fa6ec | ||
|
bbe8fff1e6 | ||
|
5dc4cf4c17 | ||
|
5b135ada24 | ||
|
65b3dab916 | ||
|
e2643ead1a | ||
|
0dcfddc169 | ||
|
b36589b2e6 | ||
|
57bea64ad4 | ||
|
5b5bbd29c3 | ||
|
e80822f1c5 | ||
|
c7978150c4 | ||
|
70ed0af475 | ||
|
57d196fdce | ||
|
8dd61e1f86 | ||
|
d36801c589 | ||
|
9f62df36dc | ||
|
aa66cfaae7 | ||
|
e36abd2dfb | ||
|
9d5a021b1e | ||
|
5ddad51911 | ||
|
ef5a950ecb | ||
|
8419cfae2b | ||
|
289e144521 | ||
|
97ab06ea22 | ||
|
3d06fba918 | ||
|
f9aa33315a | ||
|
8eb6092f91 | ||
|
06a50a5e45 | ||
|
4759713d9d | ||
|
7061730abc | ||
|
f62dee262c | ||
|
df3a4d0d45 | ||
|
33d1fc085b | ||
|
c5eb3923c1 | ||
|
596dd03bb4 | ||
|
d72bdcfe9b | ||
|
0c3174c2b5 | ||
|
6abb219499 | ||
|
eea1ddc288 | ||
|
42cbb7b723 | ||
|
a06097b012 | ||
|
d7b8115724 | ||
|
c7703cd70c | ||
|
8dec3717eb | ||
|
c0673b10b5 | ||
|
91c3f2fb2d | ||
|
f83afd82d1 | ||
|
cf2cda6694 | ||
|
455c6e0d39 | ||
|
72b7900ce2 | ||
|
dba3d89027 | ||
|
8e4a480f05 | ||
|
7fe2ed81e2 | ||
|
58db856b9d | ||
|
62a854c2ea | ||
|
acb15e5209 | ||
|
ebf4395bc9 | ||
|
8af45e8091 | ||
|
32d6951c34 | ||
|
a172762558 | ||
|
7abedd176f | ||
|
f63be8a47d | ||
|
86aa27d92c | ||
|
972480269a | ||
|
b829e5c0f6 | ||
|
fe4b3c6f36 | ||
|
cad3921c95 | ||
|
24a84de463 | ||
|
3abf949e3f | ||
|
b561d86410 | ||
|
59ff1ff856 | ||
|
88a911f6b7 | ||
|
55aa3f1198 | ||
|
a6f78aad73 | ||
|
54bfbf174b | ||
|
be6757c290 | ||
|
dda2080d01 | ||
|
8ba63338fc | ||
|
8ebb39b8d5 | ||
|
0f230834a1 | ||
|
2dc41df0c8 | ||
|
995b879a28 | ||
|
b72e8559f6 | ||
|
733b5c62af | ||
|
b192b5366a | ||
|
14aa261aca | ||
|
14a218e319 | ||
|
4ac5f25279 | ||
|
c37ade8f5b | ||
|
1a179c7a45 | ||
|
237983cecf | ||
|
c1985b2fa2 | ||
|
fc13df9a09 | ||
|
8389b87556 | ||
|
b77a783f94 | ||
|
cdffd4a995 | ||
|
e75d17ed24 | ||
|
087b4d5c7f | ||
|
7ce631d529 | ||
|
829d3680d9 | ||
|
dbad6e4e8c | ||
|
7453f89827 | ||
|
3d639c7d45 | ||
|
9348316b12 | ||
|
d7d874d48c | ||
|
7aec367a3c | ||
|
6a29c64f08 | ||
|
0ee825212e | ||
|
8dfd2b9b07 | ||
|
343909f87a | ||
|
5e85d5ef32 | ||
|
1b14d7aa2d | ||
|
139282a39e | ||
|
e9724d8b41 | ||
|
adc9f92125 | ||
|
1c93153d83 | ||
|
c73a32f76d | ||
|
068af6b0d5 | ||
|
01a2d5e017 | ||
|
ca9f0fde9b | ||
|
b16a9053b4 | ||
|
5e6e7ed152 | ||
|
93d1c06892 | ||
|
e22745d1ff | ||
|
3308bc8a0f | ||
|
f94193ad53 | ||
|
6f5eb45144 | ||
|
9bfa7acdee | ||
|
e38b259094 | ||
|
394e92d538 | ||
|
02ee9451d8 | ||
|
093fe22ab5 | ||
|
2bb4d7c8b9 | ||
|
ea2732b38c | ||
|
6bb3cf5719 | ||
|
54e277e773 | ||
|
dec29b3e22 | ||
|
9974f71f3e | ||
|
25256601a6 | ||
|
d3e9fe37eb | ||
|
9ccbebdc4f | ||
|
73ddd239aa | ||
|
598eec219f | ||
|
f2f6239b59 | ||
|
ce53cdd4a4 | ||
|
4a97a6403d | ||
|
6ec397b934 | ||
|
23be3afc9d | ||
|
bf6b12cf57 | ||
|
3936251363 | ||
|
b50d97c3b7 | ||
|
ef29d4efe8 | ||
|
92ddf9bb2d | ||
|
875d5d50b8 | ||
|
83fa13ea4b | ||
|
8b24716372 | ||
|
670ac4a34f | ||
|
82eb923689 | ||
|
18aeda4713 | ||
|
a388493030 | ||
|
c3f8573950 | ||
|
36778bb87e | ||
|
1a89e379a4 | ||
|
0aae349816 | ||
|
434291b592 | ||
|
668ceda86c | ||
|
e3ef729adf | ||
|
baafba1774 | ||
|
2ccf82749f | ||
|
5663fd386a | ||
|
6d1ebf3952 | ||
|
0ea28c646a | ||
|
fab5dc64e9 | ||
|
a5013ecbc3 | ||
|
5f82e947b2 | ||
|
d00b9515de | ||
|
eebbbdd0dc | ||
|
46adb7644a | ||
|
49e7b4f4ea | ||
|
55b1c533cf | ||
|
f1a7f30167 | ||
|
9f5c2b74eb | ||
|
78b44af7a2 | ||
|
a8442ca0bb | ||
|
926b1ae28b | ||
|
109c1ce245 | ||
|
637ccf1b2a | ||
|
a428aedf14 | ||
|
2747049986 | ||
|
9a52fcf66d | ||
|
a2b2e01e39 | ||
|
be48a2ed91 | ||
|
245db56771 | ||
|
ba4616371b | ||
|
62a3bbbcf5 | ||
|
235c7a3bb8 | ||
|
9a1219c70c | ||
|
5e692e10bc | ||
|
22b6916ac2 | ||
|
e628ec6ff0 | ||
|
863f812dbb | ||
|
21dbf0f14b | ||
|
9c392461e8 | ||
|
5b9178b18d | ||
|
7f4a80f17f | ||
|
2798837450 | ||
|
381ca70517 | ||
|
fe2beaf96a | ||
|
c962ec34c8 | ||
|
ff390fcb7c | ||
|
35ab4a5ff4 | ||
|
cac498fd9e | ||
|
06fcf8f079 | ||
|
5dad1fe2af | ||
|
81ae860134 | ||
|
91558049d9 | ||
|
aa78948c90 | ||
|
7cbe0768f2 | ||
|
05be2fe25a | ||
|
59f46b8265 | ||
|
c5cdd748fc | ||
|
6fcbe5a37f | ||
|
4d595c1380 | ||
|
7684e966fc | ||
|
40639dfa37 | ||
|
5769d0121e | ||
|
1d6f3fc57f | ||
|
2609f3425b | ||
|
bd924b993b | ||
|
c4fe9a6a51 | ||
|
4694e31e35 | ||
|
ca0f09c8f7 | ||
|
684c9773c9 | ||
|
df443aa34c | ||
|
33e381b5da | ||
|
4b5d363f55 | ||
|
4c3dbc6deb | ||
|
519ad64e1d | ||
|
93068659e5 | ||
|
4b46a3d298 | ||
|
3e64028e29 | ||
|
9b17715175 | ||
|
7d8876f03c | ||
|
b955486f14 | ||
|
6068cfbd70 | ||
|
9cda8c8bcf | ||
|
b7522288b5 | ||
|
18bc91734f | ||
|
ef21ac3d5a | ||
|
08af0aab75 | ||
|
a29292e018 | ||
|
ebb37c09e5 | ||
|
6666d1a2f4 | ||
|
431bcf20ea | ||
|
7b3ef0e3ab | ||
|
34894fb76b | ||
|
e5afcbd013 | ||
|
253a98143c | ||
|
59a7feafef | ||
|
0b2c3d7ca8 | ||
|
bd8afc67dd | ||
|
08e5b018b8 | ||
|
3d58a0c0f3 | ||
|
56f7ca388d | ||
|
ddf0ee9972 | ||
|
298e6d38a0 | ||
|
7bb5d243a0 | ||
|
207b0194c2 | ||
|
f1081e058c | ||
|
7974319c73 | ||
|
e84b37fc66 | ||
|
4219edc089 | ||
|
244dcc0465 | ||
|
5e423a8ede | ||
|
fe24c8971f | ||
|
2410c767f8 | ||
|
a785fd27ec | ||
|
5d3df4579e | ||
|
3182256580 | ||
|
84b5c3c789 | ||
|
4a1a70fa46 | ||
|
fe457d149c | ||
|
9a29747bbf | ||
|
c04eb6dc2a | ||
|
5a9a6a4680 | ||
|
1633994fbd | ||
|
f9c85d4d81 | ||
|
ec636c95a1 | ||
|
2915fae942 | ||
|
f9b5468481 | ||
|
287f37eba5 | ||
|
26740668da | ||
|
bdd72f0d30 | ||
|
7e79f25949 | ||
|
2d917e166c | ||
|
ad4a811d0a | ||
|
ab1aa97af4 | ||
|
018941c5b3 | ||
|
cc1439fb7b | ||
|
f684a2900b | ||
|
83b721a322 | ||
|
a879528ed8 | ||
|
accbff3ccb | ||
|
0375f77b73 | ||
|
9d01162f42 | ||
|
3436dc1564 | ||
|
089293079f | ||
|
00e2cd7c04 | ||
|
f5abb933b0 | ||
|
848386bbaf | ||
|
5562a3c2ae | ||
|
9e8738660b | ||
|
178772d50d | ||
|
8d7f29c7c9 | ||
|
255f87f1c3 | ||
|
975354d081 | ||
|
ec6890ced1 | ||
|
290efb4b62 | ||
|
ab3ea76244 | ||
|
21d07aef15 | ||
|
6031cd02b9 | ||
|
2cdb4cab12 | ||
|
679217f2ef | ||
|
3aacc0a258 | ||
|
254cf6cc5b | ||
|
c06095259a | ||
|
30fd98c8d7 | ||
|
3a410f04fe | ||
|
4a0a58cdc1 | ||
|
c834f93539 | ||
|
35bdb84e57 | ||
|
36a3a09fcc | ||
|
2c07b490e8 | ||
|
5790311cbd | ||
|
fde3322117 | ||
|
5664e133d1 | ||
|
a1dc38a83f | ||
|
df974f93ee | ||
|
3dde64cdac | ||
|
6b4887df0f | ||
|
ca8ef3961e | ||
|
114e0e5b1e | ||
|
c7a247cbba | ||
|
3a641a7020 | ||
|
0977859bf5 | ||
|
a644253ee9 | ||
|
3403356be8 | ||
|
4a9ec0d0d4 | ||
|
250c5c0745 | ||
|
120b5880b1 | ||
|
18eff73795 | ||
|
89b9dbb1b4 | ||
|
dde4b3ab93 | ||
|
b1cc655ba5 | ||
|
5645a12d5b | ||
|
ce1a935c06 | ||
|
58b90ad6c9 | ||
|
4740e63e76 | ||
|
319932c734 | ||
|
7cf415288a | ||
|
fe67c1db8f | ||
|
09afbd89b8 | ||
|
824f7d827c | ||
|
3a8ca56f95 | ||
|
137e3fd083 | ||
|
1285944b1f | ||
|
cd8e9deab7 | ||
|
de424eac64 | ||
|
d106a09766 | ||
|
963e8ccd2c | ||
|
3474b8f2d0 | ||
|
e27b449ae9 | ||
|
7656818456 | ||
|
d214be1215 | ||
|
c8678c3ee5 | ||
|
108ee40c3f | ||
|
de51d205dc | ||
|
1f76246edc | ||
|
f91974b766 | ||
|
df85936145 | ||
|
f39aa0f52a | ||
|
180812394b | ||
|
291fa9597f | ||
|
6c4d8c25fc | ||
|
bf4b32a8e9 | ||
|
2e9dac7678 | ||
|
369314c1cb | ||
|
6ab86898af | ||
|
f3aa65d219 | ||
|
edf4f19de9 | ||
|
91eaaae6d7 | ||
|
ca0ae5a165 | ||
|
86d788d294 | ||
|
e345cea9be | ||
|
779f7841c2 | ||
|
15fdca46b8 | ||
|
97e099c70f | ||
|
d5a58bb763 | ||
|
2774df0d30 | ||
|
93a1799a4b | ||
|
f65f051c7c | ||
|
c46952dd4e | ||
|
1dad6b6118 | ||
|
e964621f2c | ||
|
46ae62f693 | ||
|
7d578f5852 | ||
|
467f257ad3 | ||
|
55186c0a49 | ||
|
1a0a4f7112 | ||
|
658290ae80 | ||
|
cb9f29c6fc | ||
|
d8f4955292 | ||
|
9803932324 | ||
|
a3a416b5e2 | ||
|
1453a318fe | ||
|
9ffa01d318 | ||
|
29b779c873 | ||
|
598307f676 | ||
|
aa9d546bf5 | ||
|
f2ef245eca | ||
|
458606381e | ||
|
244f174dc9 | ||
|
8a577f3197 | ||
|
2f1b24cc14 | ||
|
d5f836db7a | ||
|
ffd7c36cb2 | ||
|
dba543d539 | ||
|
9753c0f8eb | ||
|
c50b4d3d47 | ||
|
907f886cf0 | ||
|
218a2d2004 | ||
|
0e0f2c6833 | ||
|
e59090d3b6 | ||
|
0404be8bef | ||
|
337895cbaa | ||
|
743547096e | ||
|
81fb3df45e | ||
|
12624cab5b | ||
|
7d62ea88d2 | ||
|
abcc277430 | ||
|
4c4c9426ee | ||
|
88fdeb2bf2 | ||
|
f9ab868022 | ||
|
148d2cec8f | ||
|
5869f157f6 | ||
|
69a2902161 | ||
|
c19b048249 | ||
|
cd2c8acdb2 | ||
|
7d0d1c764f | ||
|
b5acf1d529 | ||
|
72b08384ad | ||
|
f51e48f282 | ||
|
2e6d1f3642 | ||
|
716af4ed93 | ||
|
31de86c9eb | ||
|
04a2cd1f1f | ||
|
bc0ef11a8c | ||
|
90c8420a4c | ||
|
657d5e0d74 | ||
|
03fa4d957c | ||
|
8d4530f1f2 | ||
|
6d15c503c3 | ||
|
75ddb34398 | ||
|
6fbfe6fb72 | ||
|
db500e911c | ||
|
c0c5ced6ad | ||
|
726270f8bc | ||
|
c3908450a0 | ||
|
f7ba05f465 | ||
|
dea7f7d5d6 | ||
|
ed7a0a2b9d | ||
|
ac6b4db0f8 | ||
|
b5d659c13c | ||
|
919cb5d1f2 | ||
|
e504d3cc35 | ||
|
1d67939e76 | ||
|
5d1e90d29c | ||
|
1678eba9cc | ||
|
cc142b2ba1 | ||
|
48cbffba14 | ||
|
d909bb1b25 | ||
|
3313e66fc2 | ||
|
085892a4c8 | ||
|
817e059230 | ||
|
7ce8891246 | ||
|
439b2589f9 | ||
|
e7b5cf66d2 | ||
|
b29fabf76c | ||
|
5d837c3ee4 | ||
|
4c0396ad1c | ||
|
97b62fce79 | ||
|
e904a38735 | ||
|
ae72187aed | ||
|
e4ebeefa61 | ||
|
417373ba70 | ||
|
36f9ded08e | ||
|
858925b8c8 | ||
|
d36c96fba9 | ||
|
2be6fb329e | ||
|
da56363ef9 | ||
|
90d73228f3 | ||
|
1c76fcd26b | ||
|
c26c4ddf15 | ||
|
537bdb62da | ||
|
b2ac2bd97a | ||
|
aee6ea56b5 | ||
|
ff5506c842 | ||
|
94c5340fbf | ||
|
0e11026b60 | ||
|
1d8e953ebc | ||
|
8708fba4bc | ||
|
bc1294ae61 | ||
|
308f05101e | ||
|
f368739303 | ||
|
1bca1b921b | ||
|
3e513e92b1 | ||
|
194fe178c0 | ||
|
5d10a19bfa | ||
|
8f52a68526 | ||
|
c67d0d59bb | ||
|
4477064f17 | ||
|
9dbe24b37c | ||
|
46a85295e8 | ||
|
63170324a8 | ||
|
4fd157b5f4 | ||
|
e5406a0ea3 | ||
|
9b64ba21fd | ||
|
18fcf07971 | ||
|
d3404c6570 | ||
|
1878b5287b | ||
|
7f74545586 | ||
|
a920894a8f | ||
|
2c912456ce | ||
|
ebda475972 | ||
|
05aace84e1 | ||
|
1cfa1faccc | ||
|
1caa393974 | ||
|
ef357ab6e5 | ||
|
c39c5492ea | ||
|
1a5d54f74f | ||
|
13dc6c7dfb | ||
|
4c7dee69c2 | ||
|
dc9cf7689d | ||
|
778a408c6c | ||
|
2b7f3061d0 | ||
|
92903e1ec3 | ||
|
c5a621010e | ||
|
0b5b636578 | ||
|
8fcdb91ba3 | ||
|
f67d5f1197 | ||
|
6e0e6203cc | ||
|
2694ce4148 | ||
|
7c02b032f6 | ||
|
deb7754cb9 | ||
|
624f3c60bd | ||
|
8a8e792faa | ||
|
bc836011bc | ||
|
107eedfb49 | ||
|
61a7dcda23 | ||
|
873ca4f438 | ||
|
29221c2901 | ||
|
eac9613df7 | ||
|
184d5d25a6 | ||
|
ae33411566 | ||
|
479ebcc3fa | ||
|
1ae572cf30 | ||
|
ac6e16688d | ||
|
69159e0271 | ||
|
cae35d6a5a | ||
|
df9b65e296 | ||
|
fb0b34a6a0 | ||
|
db3b822aef | ||
|
f4994ef151 | ||
|
8e27291417 | ||
|
aae9ad78e5 | ||
|
fb3efbfc66 | ||
|
0f8a66609a | ||
|
b3bb8c386f | ||
|
b3b5e0e155 | ||
|
99a0bf1286 | ||
|
650f0ee752 | ||
|
cd373791ac | ||
|
7b03b0c5fc |
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
||||
# Ignore composer specific files and vendor folder
|
||||
composer.phar
|
||||
composer.lock
|
||||
vendor
|
@@ -1,7 +1,6 @@
|
||||
APP_ENV=${FF_APP_ENV}
|
||||
APP_DEBUG=false
|
||||
APP_FORCE_SSL=false
|
||||
APP_FORCE_ROOT=
|
||||
APP_NAME=FireflyIII
|
||||
APP_KEY=${FF_APP_KEY}
|
||||
APP_LOG=daily
|
||||
APP_LOG_LEVEL=warning
|
||||
@@ -28,7 +27,7 @@ REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_DRIVER=smtp
|
||||
MAIL_HOST=mailtrap.io
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
MAIL_FROM=changeme@example.com
|
||||
MAIL_USERNAME=null
|
||||
@@ -41,6 +40,8 @@ SHOW_INCOMPLETE_TRANSLATIONS=false
|
||||
|
||||
CACHE_PREFIX=firefly
|
||||
|
||||
EXCHANGE_RATE_SERVICE=fixerio
|
||||
|
||||
GOOGLE_MAPS_API_KEY=
|
||||
ANALYTICS_ID=
|
||||
SITE_OWNER=mail@example.com
|
||||
@@ -48,7 +49,7 @@ USE_ENCRYPTION=true
|
||||
|
||||
PUSHER_KEY=
|
||||
PUSHER_SECRET=
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_ID=
|
||||
|
||||
DEMO_USERNAME=
|
||||
DEMO_PASSWORD=
|
||||
|
7
.env.example
Executable file → Normal file
7
.env.example
Executable file → Normal file
@@ -1,7 +1,6 @@
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_FORCE_SSL=false
|
||||
APP_FORCE_ROOT=
|
||||
APP_NAME=FireflyIII
|
||||
APP_KEY=SomeRandomStringOf32CharsExactly
|
||||
APP_LOG=daily
|
||||
APP_LOG_LEVEL=warning
|
||||
@@ -28,7 +27,7 @@ REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_DRIVER=smtp
|
||||
MAIL_HOST=mailtrap.io
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
MAIL_FROM=changeme@example.com
|
||||
MAIL_USERNAME=null
|
||||
@@ -50,7 +49,7 @@ USE_ENCRYPTION=true
|
||||
|
||||
PUSHER_KEY=
|
||||
PUSHER_SECRET=
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_ID=
|
||||
|
||||
DEMO_USERNAME=
|
||||
DEMO_PASSWORD=
|
||||
|
@@ -1,7 +1,6 @@
|
||||
APP_ENV=production
|
||||
APP_DEBUG=true
|
||||
APP_FORCE_SSL=false
|
||||
APP_FORCE_ROOT=
|
||||
APP_NAME=FireflyIII
|
||||
APP_KEY=SomeRandomStringOf32CharsExactly
|
||||
APP_LOG=syslog
|
||||
APP_LOG_LEVEL=debug
|
||||
@@ -28,7 +27,7 @@ REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_DRIVER=smtp
|
||||
MAIL_HOST=mailtrap.io
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
MAIL_FROM=changeme@example.com
|
||||
MAIL_USERNAME=null
|
||||
@@ -41,6 +40,8 @@ SHOW_INCOMPLETE_TRANSLATIONS=false
|
||||
|
||||
CACHE_PREFIX=firefly
|
||||
|
||||
EXCHANGE_RATE_SERVICE=fixerio
|
||||
|
||||
GOOGLE_MAPS_API_KEY=
|
||||
ANALYTICS_ID=
|
||||
SITE_OWNER=mail@example.com
|
||||
@@ -48,8 +49,7 @@ USE_ENCRYPTION=true
|
||||
|
||||
PUSHER_KEY=
|
||||
PUSHER_SECRET=
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_ID=
|
||||
|
||||
DEMO_USERNAME=
|
||||
DEMO_PASSWORD=
|
||||
|
||||
|
21
.env.testing
Executable file → Normal file
21
.env.testing
Executable file → Normal file
@@ -1,7 +1,6 @@
|
||||
APP_ENV=testing
|
||||
APP_DEBUG=true
|
||||
APP_FORCE_SSL=false
|
||||
APP_FORCE_ROOT=
|
||||
APP_NAME=FireflyIII
|
||||
APP_KEY=TestTestTestTestTestTestTestTest
|
||||
APP_LOG=daily
|
||||
APP_LOG_LEVEL=debug
|
||||
@@ -11,7 +10,7 @@ DB_CONNECTION=sqlite
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=homestead
|
||||
DB_PASSWORD=secret
|
||||
DB_PASSWORD=
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=file
|
||||
@@ -26,8 +25,8 @@ REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_DRIVER=log
|
||||
MAIL_HOST=mailtrap.io
|
||||
MAIL_DRIVER=smtp
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
MAIL_FROM=changeme@example.com
|
||||
MAIL_USERNAME=null
|
||||
@@ -38,9 +37,19 @@ SEND_REGISTRATION_MAIL=true
|
||||
SEND_ERROR_MESSAGE=true
|
||||
SHOW_INCOMPLETE_TRANSLATIONS=false
|
||||
|
||||
CACHE_PREFIX=firefly
|
||||
|
||||
EXCHANGE_RATE_SERVICE=fixerio
|
||||
|
||||
GOOGLE_MAPS_API_KEY=
|
||||
ANALYTICS_ID=
|
||||
SITE_OWNER=mail@example.com
|
||||
USE_ENCRYPTION=true
|
||||
|
||||
PUSHER_KEY=
|
||||
PUSHER_SECRET=
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_ID=
|
||||
|
||||
DEMO_USERNAME=
|
||||
DEMO_PASSWORD=
|
||||
|
||||
|
2
.gitattributes
vendored
Executable file → Normal file
2
.gitattributes
vendored
Executable file → Normal file
@@ -1,3 +1,5 @@
|
||||
* text=auto
|
||||
*.css linguist-vendored
|
||||
*.scss linguist-vendored
|
||||
*.js linguist-vendored
|
||||
CHANGELOG.md export-ignore
|
||||
|
6
.gitignore
vendored
Executable file → Normal file
6
.gitignore
vendored
Executable file → Normal file
@@ -1,8 +1,14 @@
|
||||
/node_modules
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/vendor
|
||||
/.vagrant
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
.env
|
||||
public/google*.html
|
||||
report.html
|
||||
composer.phar
|
||||
|
@@ -1,6 +1,5 @@
|
||||
language: php
|
||||
php:
|
||||
- 7.0
|
||||
- 7.1
|
||||
|
||||
cache:
|
||||
|
117
CHANGELOG.md
117
CHANGELOG.md
@@ -2,11 +2,126 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [4.6.6] - 2015-05-25
|
||||
### Added
|
||||
- #826, reported by @pkoziol.
|
||||
- #855, by @ms32035
|
||||
- #786, by @SmilingWorlock
|
||||
- #875, by @gavu
|
||||
- #834, by @gavu (and others)
|
||||
|
||||
|
||||
### Changed
|
||||
- Upgraded to Laravel 5.5
|
||||
- Add version parameter to CSS and JS files
|
||||
- #823, #824 fixed Docker config by @DieBauer
|
||||
|
||||
### Fixed
|
||||
- #830
|
||||
- #822, reported by @gazben
|
||||
- #827, reported by @pkoziol
|
||||
- #835, reported by @gavu
|
||||
- #836, reported by @pkoziol
|
||||
- #838, reported by @gavu
|
||||
- #839, reported by @gavu
|
||||
- #843, reported by @gavu
|
||||
- #837, reported by @gavu
|
||||
- #845, reported by @gavu
|
||||
- #846, reported by @gavu
|
||||
- #848, reported by @gavu
|
||||
- #854, reported by @gavu
|
||||
- #866, reported by @pkoziol
|
||||
- #847, reported by @gavu
|
||||
- #853, reported by @gavu
|
||||
- #857, reported by @pkoziol
|
||||
- #865, reported by @simonsmiley
|
||||
- #826, reported by @pkoziol
|
||||
- #856, reported by @ms32035
|
||||
- #860, reported by @gavu
|
||||
- #861, reported by @gavu
|
||||
- #870, reported by @gavu
|
||||
|
||||
## [4.6.5] - 2017-09-09
|
||||
|
||||
### Added
|
||||
- #616, The ability to link transactions
|
||||
- #763, as suggested by @tannie
|
||||
- #770, as suggested by @skibbipl
|
||||
- #780, as suggested by @skibbipl
|
||||
- #784, as suggested by @SmilingWorlock
|
||||
- Lots of code for future support of automated Bunq imports
|
||||
|
||||
### Changed
|
||||
- Rewrote the export routine
|
||||
- #782, as suggested by @NiceGuyIT
|
||||
- #800, as suggested by @jleeong
|
||||
|
||||
### Fixed
|
||||
- #724, reported by @skibbipl
|
||||
- #738, reported by @skibbipl
|
||||
- #760, reported by @leander091
|
||||
- #764, reported by @tannie
|
||||
- #792, reported by @jleeong
|
||||
- #793, reported by @nicoschreiner
|
||||
- #797, reported by @leander091
|
||||
- #801, reported by @pkoziol
|
||||
- #803, reported by @pkoziol
|
||||
- #805, reported by @pkoziol
|
||||
- #806, reported by @pkoziol
|
||||
- #807, reported by @pkoziol
|
||||
- #808, reported by @pkoziol
|
||||
- #809, reported by @pkoziol
|
||||
- #814, reported by @nicoschreiner
|
||||
- #818, reported by @gavu
|
||||
- #819, reported by @DieBauer
|
||||
- #820, reported by @simonsmiley
|
||||
- Various other fixes
|
||||
|
||||
|
||||
## [4.6.4] - 2017-08-13
|
||||
### Added
|
||||
- PHP7.1 support
|
||||
- Routine to decrypt attachments from the command line, for issue #671
|
||||
- A routine that can check if your password has been stolen in the past.
|
||||
- Split transaction shows amount left to be split
|
||||
|
||||
|
||||
### Changed
|
||||
- Importer can (potentially) handle new import routines such as banks.
|
||||
- Importer can fall back from JSON errors
|
||||
|
||||
### Deprecated
|
||||
- Initial release.
|
||||
|
||||
### Removed
|
||||
- PHP7.0 support
|
||||
- Support for extended tag modes
|
||||
- Remove "time jumps" to non-empty periods
|
||||
|
||||
|
||||
### Fixed
|
||||
- #717, reported by @NiceGuyIT
|
||||
- #718, reported by @wtercato
|
||||
- #722, reported by @simonsmiley
|
||||
- #648, reported by @skibbipl
|
||||
- #730, reported by @ragnarkarlsson
|
||||
- #733, reported by @xpfgsyb
|
||||
- #735, reported by @kristophr
|
||||
- #739, reported by @skibbipl
|
||||
- #515, reported by @schwalberich
|
||||
- #743, reported by @simonsmiley
|
||||
- #746, reported by @tannie
|
||||
- #747, reported by @tannie
|
||||
|
||||
### Security
|
||||
- Initial release.
|
||||
|
||||
|
||||
|
||||
## [4.6.3.1] - 2017-07-23
|
||||
### Fixed
|
||||
- Hotfix to close issue #715
|
||||
|
||||
|
||||
## [4.6.3] - 2017-07-23
|
||||
|
||||
This will be the last release to support PHP 7.0.
|
||||
|
33
Dockerfile
33
Dockerfile
@@ -23,28 +23,31 @@ RUN docker-php-ext-install -j$(nproc) curl gd intl json mcrypt readline tidy zip
|
||||
# Generate locales supported by firefly
|
||||
RUN echo "en_US.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
|
||||
|
||||
COPY ./docker/apache2.conf /etc/apache2/apache2.conf
|
||||
|
||||
# Enable apache mod rewrite..
|
||||
RUN a2enmod rewrite
|
||||
|
||||
# Enable apache mod ssl..
|
||||
RUN a2enmod ssl
|
||||
|
||||
# Setup the Composer installer
|
||||
RUN curl -o /tmp/composer-setup.php https://getcomposer.org/installer && \
|
||||
curl -o /tmp/composer-setup.sig https://composer.github.io/installer.sig && \
|
||||
php -r "if (hash('SHA384', file_get_contents('/tmp/composer-setup.php')) !== trim(file_get_contents('/tmp/composer-setup.sig'))) { unlink('/tmp/composer-setup.php'); echo 'Invalid installer' . PHP_EOL; exit(1); }" && \
|
||||
chmod +x /tmp/composer-setup.php && \
|
||||
php /tmp/composer-setup.php && \
|
||||
mv composer.phar /usr/local/bin/composer && \
|
||||
rm -f /tmp/composer-setup.{php,sig}
|
||||
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||
|
||||
ADD . /var/www/firefly-iii
|
||||
RUN chown -R www-data:www-data /var/www/
|
||||
ADD docker/apache-firefly.conf /etc/apache2/sites-available/000-default.conf
|
||||
# Copy Apache Configs
|
||||
COPY ./docker/apache-firefly.conf /etc/apache2/sites-available/000-default.conf
|
||||
|
||||
USER www-data
|
||||
ENV FIREFLY_PATH /var/www/firefly-iii
|
||||
|
||||
WORKDIR /var/www/firefly-iii
|
||||
WORKDIR $FIREFLY_PATH
|
||||
|
||||
RUN composer install --no-scripts --no-dev
|
||||
# The working directory
|
||||
COPY . $FIREFLY_PATH
|
||||
|
||||
USER root
|
||||
RUN chown -R www-data:www-data /var/www && chmod -R 775 $FIREFLY_PATH/storage
|
||||
|
||||
ENTRYPOINT ["/var/www/firefly-iii/docker/entrypoint.sh"]
|
||||
RUN composer install --prefer-dist --no-dev --no-scripts
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT ["docker/entrypoint.sh"]
|
||||
|
23
README.md
23
README.md
@@ -1,26 +1,36 @@
|
||||
# Firefly III: A personal finances manager
|
||||
|
||||
[](https://secure.php.net/downloads.php) [](https://packagist.org/packages/grumpydictator/firefly-iii) [](https://creativecommons.org/licenses/by-sa/4.0/) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA)
|
||||
[](https://secure.php.net/downloads.php) [](https://packagist.org/packages/grumpydictator/firefly-iii) [](https://creativecommons.org/licenses/by-sa/4.0/) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA)
|
||||
|
||||
[](https://i.nder.be/h2b37243) [](https://i.nder.be/hv70pbwc)
|
||||
|
||||
[](https://i.nder.be/ccn0u2mp) [](https://i.nder.be/gm8hbh7z)
|
||||
|
||||
"Firefly III" is a financial manager. It can help you keep track of expenses, income, budgets and everything in between. It even supports credit cards, shared household accounts and savings accounts! It's pretty fancy. You should use it to save and organise money.
|
||||
"Firefly III" is a financial manager for your personal finances.
|
||||
It can help you keep track of your expenses and income.
|
||||
Firefly III supports the use of budgets. You can categorize and tag your transactions.
|
||||
It also supports credit cards, shared household accounts and savings accounts.
|
||||
There are many financial reports available.
|
||||
|
||||
## Try it out!
|
||||
|
||||
[](https://heroku.com/deploy?template=https://github.com/firefly-iii/firefly-iii/tree/master)
|
||||
|
||||
Firefly III can be run on Heroku. Register for a free Heroku account and instantly run Firefly III on your very own cloud instance. There is also a [demo site](https://firefly-iii.nder.be) with an example financial administration already present.
|
||||
Firefly III can be run on Heroku.
|
||||
Register for a free Heroku account and instantly run Firefly III on your very own cloud instance.
|
||||
There is also a [demo site](https://firefly-iii.nder.be) with an example financial administration already present.
|
||||
|
||||
## Getting started
|
||||
|
||||
To install Firefly III, you'll need a web server (preferrably on Linux) and access to the command line. Then, please read the [installation guide](https://firefly-iii.github.io/using-installing.html).
|
||||
To install Firefly III, you'll need a web server (preferrably on Linux) and access to the command line.
|
||||
Then, please read the [installation guide](https://firefly-iii.github.io/using-installing.html).
|
||||
At the moment, installation is fairly complex. I hope to improve this in the future. If you need support, please open a ticket.
|
||||
|
||||
## More about Firefly III
|
||||
|
||||
Personal financial management is pretty difficult, and everybody has their own approach to it. Some people make budgets, other people limit their cashflow by throwing away their credit cards, others try to increase their current cashflow. There are tons of ways to save and earn money.
|
||||
Personal financial management is pretty difficult, and everybody has their own approach to it.
|
||||
Some people make budgets, other people limit their cashflow by throwing away their credit cards,
|
||||
others try to increase their current cashflow. There are tons of ways to save and earn money.
|
||||
|
||||
Firefly works on the principle that if you know where you're money is going, you can stop it from going there.
|
||||
|
||||
@@ -31,7 +41,8 @@ Firefly works on the principle that if you know where you're money is going, you
|
||||
- Firefly has lots of features without being fancy or bloated.
|
||||
- If you feel you're missing something you can just ask me and I'll add it!
|
||||
|
||||
Firefly is pretty awesome. [You can read more about Firefly III, and its features, on the Github Pages](https://firefly-iii.github.io/).
|
||||
Firefly III has become pretty awesome over the years! (but please excuse me for bragging, it's just that I'm proud of it).
|
||||
[You can read more about Firefly III, and its features, on the Github Pages](https://firefly-iii.github.io/).
|
||||
|
||||
### Contributing
|
||||
|
||||
|
143
app/Console/Commands/CreateExport.php
Normal file
143
app/Console/Commands/CreateExport.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
/**
|
||||
* CreateExport.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Export\ProcessorInterface;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use Illuminate\Console\Command;
|
||||
use Storage;
|
||||
|
||||
|
||||
/**
|
||||
* Class CreateExport
|
||||
*
|
||||
* Generates export from the command line.
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class CreateExport extends Command
|
||||
{
|
||||
use VerifiesAccessToken;
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Use this command to create a new import. Your user ID can be found on the /profile page.';
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature
|
||||
= 'firefly:create-export
|
||||
{--user= : The user ID that the import should import for.}
|
||||
{--token= : The user\'s access token.}
|
||||
{--with_attachments : Include user\'s attachments?}
|
||||
{--with_uploads : Include user\'s uploads?}';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five its fine.
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (!$this->verifyAccessToken()) {
|
||||
$this->error('Invalid access token.');
|
||||
|
||||
return;
|
||||
}
|
||||
$this->line('Full export is running...');
|
||||
// make repositories
|
||||
/** @var UserRepositoryInterface $userRepository */
|
||||
$userRepository = app(UserRepositoryInterface::class);
|
||||
/** @var ExportJobRepositoryInterface $jobRepository */
|
||||
$jobRepository = app(ExportJobRepositoryInterface::class);
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
/** @var JournalRepositoryInterface $journalRepository */
|
||||
$journalRepository = app(JournalRepositoryInterface::class);
|
||||
|
||||
// set user
|
||||
$user = $userRepository->find(intval($this->option('user')));
|
||||
$jobRepository->setUser($user);
|
||||
$journalRepository->setUser($user);
|
||||
$accountRepository->setUser($user);
|
||||
|
||||
// first date
|
||||
$firstJournal = $journalRepository->first();
|
||||
$first = new Carbon;
|
||||
if (!is_null($firstJournal->id)) {
|
||||
$first = $firstJournal->date;
|
||||
}
|
||||
|
||||
// create job and settings.
|
||||
$job = $jobRepository->create();
|
||||
$settings = [
|
||||
'accounts' => $accountRepository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]),
|
||||
'startDate' => $first,
|
||||
'endDate' => new Carbon,
|
||||
'exportFormat' => 'csv',
|
||||
'includeAttachments' => $this->option('with_attachments'),
|
||||
'includeOldUploads' => $this->option('with_uploads'),
|
||||
'job' => $job,
|
||||
];
|
||||
|
||||
|
||||
/** @var ProcessorInterface $processor */
|
||||
$processor = app(ProcessorInterface::class);
|
||||
$processor->setSettings($settings);
|
||||
|
||||
$processor->collectJournals();
|
||||
$processor->convertJournals();
|
||||
$processor->exportJournals();
|
||||
if ($settings['includeAttachments']) {
|
||||
$processor->collectAttachments();
|
||||
}
|
||||
|
||||
if ($settings['includeOldUploads']) {
|
||||
$processor->collectOldUploads();
|
||||
}
|
||||
|
||||
$processor->createZipFile();
|
||||
$disk = Storage::disk('export');
|
||||
$fileName = sprintf('export-%s.zip', date('Y-m-d_H-i-s'));
|
||||
$disk->move($job->key . '.zip', $fileName);
|
||||
|
||||
$this->line('The export has finished! You can find the ZIP file in this location:');
|
||||
$this->line(storage_path(sprintf('export/%s', $fileName)));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
@@ -30,6 +30,7 @@ use Monolog\Formatter\LineFormatter;
|
||||
*/
|
||||
class CreateImport extends Command
|
||||
{
|
||||
use VerifiesAccessToken;
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
@@ -42,7 +43,13 @@ class CreateImport extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly:create-import {file} {configuration} {--user=1} {--type=csv} {--start}';
|
||||
protected $signature = 'firefly:create-import
|
||||
{file : The file to import.}
|
||||
{configuration : The configuration file to use for the import/}
|
||||
{--type=csv : The file type of the import.}
|
||||
{--user= : The user ID that the import should import for.}
|
||||
{--token= : The user\'s access token.}
|
||||
{--start : Starts the job immediately.}';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
@@ -54,11 +61,18 @@ class CreateImport extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the command.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) // cannot be helped
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five exactly.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (!$this->verifyAccessToken()) {
|
||||
$this->error('Invalid access token.');
|
||||
|
||||
return;
|
||||
}
|
||||
/** @var UserRepositoryInterface $userRepository */
|
||||
$userRepository = app(UserRepositoryInterface::class);
|
||||
$file = $this->argument('file');
|
||||
@@ -133,6 +147,8 @@ class CreateImport extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify user inserts correct arguments.
|
||||
*
|
||||
* @return bool
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five exactly.
|
||||
*/
|
||||
@@ -146,12 +162,12 @@ class CreateImport extends Command
|
||||
$cwd = getcwd();
|
||||
$validTypes = array_keys(config('firefly.import_formats'));
|
||||
$type = strtolower($this->option('type'));
|
||||
|
||||
if (is_null($user->id)) {
|
||||
$this->error(sprintf('There is no user with ID %d.', $this->option('user')));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!in_array($type, $validTypes)) {
|
||||
$this->error(sprintf('Cannot import file of type "%s"', $type));
|
||||
|
||||
|
108
app/Console/Commands/DecryptAttachment.php
Normal file
108
app/Console/Commands/DecryptAttachment.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/**
|
||||
* DecryptAttachment.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
|
||||
use Illuminate\Console\Command;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class DecryptAttachment
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class DecryptAttachment extends Command
|
||||
{
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Decrypts an attachment and dumps the content in a file in the given directory.';
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature
|
||||
= 'firefly:decrypt-attachment {id:The ID of the attachment.} {name:The file name of the attachment.}
|
||||
{directory:Where the file must be stored.}';
|
||||
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five its fine.
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
/** @var AttachmentRepositoryInterface $repository */
|
||||
$repository = app(AttachmentRepositoryInterface::class);
|
||||
$attachmentId = intval($this->argument('id'));
|
||||
$attachment = $repository->findWithoutUser($attachmentId);
|
||||
$attachmentName = trim($this->argument('name'));
|
||||
$storagePath = realpath(trim($this->argument('directory')));
|
||||
if (is_null($attachment->id)) {
|
||||
$this->error(sprintf('No attachment with id #%d', $attachmentId));
|
||||
Log::error(sprintf('DecryptAttachment: No attachment with id #%d', $attachmentId));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($attachmentName !== $attachment->filename) {
|
||||
$this->error('File name does not match.');
|
||||
Log::error('DecryptAttachment: File name does not match.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_dir($storagePath)) {
|
||||
$this->error(sprintf('Path "%s" is not a directory.', $storagePath));
|
||||
Log::error(sprintf('DecryptAttachment: Path "%s" is not a directory.', $storagePath));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_writable($storagePath)) {
|
||||
$this->error(sprintf('Path "%s" is not writable.', $storagePath));
|
||||
Log::error(sprintf('DecryptAttachment: Path "%s" is not writable.', $storagePath));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fullPath = $storagePath . DIRECTORY_SEPARATOR . $attachment->filename;
|
||||
$content = $repository->getContent($attachment);
|
||||
$this->line(sprintf('Going to write content for attachment #%d into file "%s"', $attachment->id, $fullPath));
|
||||
$result = file_put_contents($fullPath, $content);
|
||||
if ($result === false) {
|
||||
$this->error('Could not write to file.');
|
||||
|
||||
return;
|
||||
}
|
||||
$this->info(sprintf('%d bytes written. Exiting now..', $result));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
@@ -51,7 +51,7 @@ class Import extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Run the import routine.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
@@ -91,6 +91,8 @@ class Import extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if job is valid to be imported.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return bool
|
||||
|
@@ -20,24 +20,25 @@ use FireflyIII\Models\AccountMeta;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\LimitRepetition;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Preferences;
|
||||
use Schema;
|
||||
use Steam;
|
||||
|
||||
/**
|
||||
* Class UpgradeDatabase
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) // it just touches a lot of things.
|
||||
*
|
||||
* Upgrade user database.
|
||||
*
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) // it just touches a lot of things.
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class UpgradeDatabase extends Command
|
||||
@@ -71,76 +72,26 @@ class UpgradeDatabase extends Command
|
||||
{
|
||||
$this->setTransactionIdentifier();
|
||||
$this->migrateRepetitions();
|
||||
$this->repairPiggyBanks();
|
||||
$this->updateAccountCurrencies();
|
||||
$this->updateJournalCurrencies();
|
||||
$this->currencyInfoToTransactions();
|
||||
$this->verifyCurrencyInfo();
|
||||
$this->line('Updating currency information..');
|
||||
$this->updateTransferCurrencies();
|
||||
$this->updateOtherCurrencies();
|
||||
$this->info('Firefly III database is up to date.');
|
||||
|
||||
return;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the currency id info to the transaction instead of the journal.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // cannot be helped.
|
||||
* Migrate budget repetitions to new format where the end date is in the budget limit as well,
|
||||
* making the limit_repetition table obsolete.
|
||||
*/
|
||||
private function currencyInfoToTransactions()
|
||||
public function migrateRepetitions(): void
|
||||
{
|
||||
$count = 0;
|
||||
$set = TransactionJournal::with('transactions')->get();
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($set as $journal) {
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($journal->transactions as $transaction) {
|
||||
if (is_null($transaction->transaction_currency_id)) {
|
||||
$transaction->transaction_currency_id = $journal->transaction_currency_id;
|
||||
$transaction->save();
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
// read and use the foreign amounts when present.
|
||||
if ($journal->hasMeta('foreign_amount')) {
|
||||
$amount = Steam::positive($journal->getMeta('foreign_amount'));
|
||||
|
||||
// update both transactions:
|
||||
foreach ($journal->transactions as $transaction) {
|
||||
$transaction->foreign_amount = $amount;
|
||||
if (bccomp($transaction->amount, '0') === -1) {
|
||||
// update with negative amount:
|
||||
$transaction->foreign_amount = bcmul($amount, '-1');
|
||||
}
|
||||
// set foreign currency id:
|
||||
$transaction->foreign_currency_id = intval($journal->getMeta('foreign_currency_id'));
|
||||
$transaction->save();
|
||||
}
|
||||
$journal->deleteMeta('foreign_amount');
|
||||
$journal->deleteMeta('foreign_currency_id');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->line(sprintf('Updated currency information for %d transactions', $count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate budget repetitions to new format.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 5.
|
||||
*/
|
||||
private function migrateRepetitions()
|
||||
{
|
||||
if (!Schema::hasTable('budget_limits')) {
|
||||
return;
|
||||
}
|
||||
// get all budget limits with end_date NULL
|
||||
$set = BudgetLimit::whereNull('end_date')->get();
|
||||
if ($set->count() > 0) {
|
||||
$this->line(sprintf('Found %d budget limit(s) to update', $set->count()));
|
||||
}
|
||||
/** @var BudgetLimit $budgetLimit */
|
||||
foreach ($set as $budgetLimit) {
|
||||
// get limit repetition (should be just one):
|
||||
/** @var LimitRepetition $repetition */
|
||||
$repetition = $budgetLimit->limitrepetitions()->first();
|
||||
if (!is_null($repetition)) {
|
||||
@@ -150,59 +101,30 @@ class UpgradeDatabase extends Command
|
||||
$repetition->delete();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure there are only transfers linked to piggy bank events.
|
||||
* This method gives all transactions which are part of a split journal (so more than 2) a sort of "order" so they are easier
|
||||
* to easier to match to their counterpart. When a journal is split, it has two or three transactions: -3, -4 and -5 for example.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // cannot be helped.
|
||||
* In the database this is reflected as 6 transactions: -3/+3, -4/+4, -5/+5.
|
||||
*
|
||||
* When either of these are the same amount, FF3 can't keep them apart: +3/-3, +3/-3, +3/-3. This happens more often than you would
|
||||
* think. So each set gets a number (1,2,3) to keep them apart.
|
||||
*/
|
||||
private function repairPiggyBanks()
|
||||
{
|
||||
// if table does not exist, return false
|
||||
if (!Schema::hasTable('piggy_bank_events')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get();
|
||||
/** @var PiggyBankEvent $event */
|
||||
foreach ($set as $event) {
|
||||
|
||||
if (is_null($event->transaction_journal_id)) {
|
||||
continue;
|
||||
}
|
||||
/** @var TransactionJournal $journal */
|
||||
$journal = $event->transactionJournal()->first();
|
||||
if (is_null($journal)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $journal->transactionType->type;
|
||||
if ($type !== TransactionType::TRANSFER) {
|
||||
$event->transaction_journal_id = null;
|
||||
$event->save();
|
||||
$this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is strangely complex, because the HAVING modifier is a no-no. And subqueries in Laravel are weird.
|
||||
*/
|
||||
private function setTransactionIdentifier()
|
||||
public function setTransactionIdentifier(): void
|
||||
{
|
||||
// if table does not exist, return false
|
||||
if (!Schema::hasTable('transaction_journals')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$subQuery = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->whereNull('transactions.deleted_at')
|
||||
->groupBy(['transaction_journals.id'])
|
||||
->select(['transaction_journals.id', DB::raw('COUNT(transactions.id) AS t_count')]);
|
||||
|
||||
$subQuery = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->whereNull('transactions.deleted_at')
|
||||
->groupBy(['transaction_journals.id'])
|
||||
->select(['transaction_journals.id', DB::raw('COUNT(transactions.id) AS t_count')]);
|
||||
$result = DB::table(DB::raw('(' . $subQuery->toSql() . ') AS derived'))
|
||||
->mergeBindings($subQuery->getQuery())
|
||||
->where('t_count', '>', 2)
|
||||
@@ -210,55 +132,180 @@ class UpgradeDatabase extends Command
|
||||
$journalIds = array_unique($result->pluck('id')->toArray());
|
||||
|
||||
foreach ($journalIds as $journalId) {
|
||||
$this->updateJournal(intval($journalId));
|
||||
$this->updateJournalidentifiers(intval($journalId));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure all accounts have proper currency info.
|
||||
* Each (asset) account must have a reference to a preferred currency. If the account does not have one, it's forced upon the account.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's seven but it can't really be helped.
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*/
|
||||
private function updateAccountCurrencies()
|
||||
public function updateAccountCurrencies(): void
|
||||
{
|
||||
$accounts = Account::leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||
->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])->get(['accounts.*']);
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
// get users preference, fall back to system pref.
|
||||
$defaultCurrencyCode = Preferences::getForUser($account->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data;
|
||||
$defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first();
|
||||
$accountCurrency = intval($account->getMeta('currency_id'));
|
||||
$openingBalance = $account->getOpeningBalance();
|
||||
$obCurrency = intval($openingBalance->transaction_currency_id);
|
||||
$accounts->each(
|
||||
function (Account $account) {
|
||||
// get users preference, fall back to system pref.
|
||||
$defaultCurrencyCode = Preferences::getForUser($account->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data;
|
||||
$defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first();
|
||||
$accountCurrency = intval($account->getMeta('currency_id'));
|
||||
$openingBalance = $account->getOpeningBalance();
|
||||
$obCurrency = intval($openingBalance->transaction_currency_id);
|
||||
|
||||
// both 0? set to default currency:
|
||||
if ($accountCurrency === 0 && $obCurrency === 0) {
|
||||
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $defaultCurrency->id]);
|
||||
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
|
||||
continue;
|
||||
// both 0? set to default currency:
|
||||
if ($accountCurrency === 0 && $obCurrency === 0) {
|
||||
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $defaultCurrency->id]);
|
||||
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// account is set to 0, opening balance is not?
|
||||
if ($accountCurrency === 0 && $obCurrency > 0) {
|
||||
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $obCurrency]);
|
||||
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// do not match and opening balance id is not null.
|
||||
if ($accountCurrency !== $obCurrency && $openingBalance->id > 0) {
|
||||
// update opening balance:
|
||||
$openingBalance->transaction_currency_id = $accountCurrency;
|
||||
$openingBalance->save();
|
||||
$this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
// account is set to 0, opening balance is not?
|
||||
if ($accountCurrency === 0 && $obCurrency > 0) {
|
||||
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $obCurrency]);
|
||||
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* This routine verifies that withdrawals, deposits and opening balances have the correct currency settings for
|
||||
* the accounts they are linked to.
|
||||
*
|
||||
* Both source and destination must match the respective currency preference of the related asset account.
|
||||
* So FF3 must verify all transactions.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*/
|
||||
public function updateOtherCurrencies(): void
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$set = TransactionJournal
|
||||
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
->whereIn('transaction_types.type', [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE])
|
||||
->get(['transaction_journals.*']);
|
||||
|
||||
$set->each(
|
||||
function (TransactionJournal $journal) use ($repository) {
|
||||
// get the transaction with the asset account in it:
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = $journal->transactions()
|
||||
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
|
||||
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||
->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])->first(['transactions.*']);
|
||||
if (is_null($transaction)) {
|
||||
return;
|
||||
}
|
||||
/** @var Account $account */
|
||||
$account = $transaction->account;
|
||||
$currency = $repository->find(intval($account->getMeta('currency_id')));
|
||||
$transactions = $journal->transactions()->get();
|
||||
$transactions->each(
|
||||
function (Transaction $transaction) use ($currency) {
|
||||
if (is_null($transaction->transaction_currency_id)) {
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
$transaction->save();
|
||||
}
|
||||
|
||||
// when mismatch in transaction:
|
||||
if ($transaction->transaction_currency_id !== $currency->id) {
|
||||
$transaction->foreign_currency_id = $transaction->transaction_currency_id;
|
||||
$transaction->foreign_amount = $transaction->amount;
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
$transaction->save();
|
||||
}
|
||||
}
|
||||
);
|
||||
// also update the journal, of course:
|
||||
$journal->transaction_currency_id = $currency->id;
|
||||
$journal->save();
|
||||
}
|
||||
);
|
||||
|
||||
// do not match:
|
||||
if ($accountCurrency !== $obCurrency) {
|
||||
// update opening balance:
|
||||
$openingBalance->transaction_currency_id = $accountCurrency;
|
||||
$openingBalance->save();
|
||||
$this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name));
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* This routine verifies that transfers have the correct currency settings for the accounts they are linked to.
|
||||
* For transfers, this is can be a destructive routine since we FORCE them into a currency setting whether they
|
||||
* like it or not. Previous routines MUST have set the currency setting for both accounts for this to work.
|
||||
*
|
||||
* A transfer always has the
|
||||
*
|
||||
* Both source and destination must match the respective currency preference. So FF3 must verify ALL
|
||||
* transactions.
|
||||
*/
|
||||
public function updateTransferCurrencies()
|
||||
{
|
||||
$set = TransactionJournal
|
||||
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
->where('transaction_types.type', TransactionType::TRANSFER)
|
||||
->get(['transaction_journals.*']);
|
||||
|
||||
$set->each(
|
||||
function (TransactionJournal $transfer) {
|
||||
// select all "source" transactions:
|
||||
/** @var Collection $transactions */
|
||||
$transactions = $transfer->transactions()->where('amount', '<', 0)->get();
|
||||
$transactions->each(
|
||||
function (Transaction $transaction) {
|
||||
$this->updateTransactionCurrency($transaction);
|
||||
$this->updateJournalCurrency($transaction);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// opening balance 0, account not zero? just continue:
|
||||
// both are equal, just continue:
|
||||
|
||||
/**
|
||||
* This method makes sure that the transaction journal uses the currency given in the transaction.
|
||||
*
|
||||
* @param Transaction $transaction
|
||||
*/
|
||||
private function updateJournalCurrency(Transaction $transaction): void
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$currency = $repository->find(intval($transaction->account->getMeta('currency_id')));
|
||||
$journal = $transaction->transactionJournal;
|
||||
|
||||
if (!(intval($currency->id) === intval($journal->transaction_currency_id))) {
|
||||
$this->line(
|
||||
sprintf(
|
||||
'Transfer #%d ("%s") has been updated to use %s instead of %s.', $journal->id, $journal->description, $currency->code,
|
||||
$journal->transactionCurrency->code
|
||||
)
|
||||
);
|
||||
$journal->transaction_currency_id = $currency->id;
|
||||
$journal->save();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,7 +314,7 @@ class UpgradeDatabase extends Command
|
||||
*
|
||||
* @param int $journalId
|
||||
*/
|
||||
private function updateJournal(int $journalId)
|
||||
private function updateJournalidentifiers(int $journalId): void
|
||||
{
|
||||
$identifier = 0;
|
||||
$processed = [];
|
||||
@@ -295,121 +342,134 @@ class UpgradeDatabase extends Command
|
||||
if (!is_null($opposing)) {
|
||||
// give both a new identifier:
|
||||
$transaction->identifier = $identifier;
|
||||
$opposing->identifier = $identifier;
|
||||
$transaction->save();
|
||||
$opposing->identifier = $identifier;
|
||||
$opposing->save();
|
||||
$processed[] = $transaction->id;
|
||||
$processed[] = $opposing->id;
|
||||
}
|
||||
$identifier++;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that withdrawals, deposits and transfers have
|
||||
* a currency setting matching their respective accounts
|
||||
*/
|
||||
private function updateJournalCurrencies()
|
||||
{
|
||||
$types = [
|
||||
TransactionType::WITHDRAWAL => '<',
|
||||
TransactionType::DEPOSIT => '>',
|
||||
];
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$notification = '%s #%d uses %s but should use %s. It has been updated. Please verify this in Firefly III.';
|
||||
$transfer = 'Transfer #%d has been updated to use the correct currencies. Please verify this in Firefly III.';
|
||||
$driver = DB::connection()->getDriverName();
|
||||
$pgsql = ['pgsql', 'postgresql'];
|
||||
|
||||
foreach ($types as $type => $operator) {
|
||||
$query = TransactionJournal
|
||||
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')->leftJoin(
|
||||
'transactions', function (JoinClause $join) use ($operator) {
|
||||
$join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', $operator, '0');
|
||||
}
|
||||
)
|
||||
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
|
||||
->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
|
||||
->where('transaction_types.type', $type)
|
||||
->where('account_meta.name', 'currency_id');
|
||||
if (in_array($driver, $pgsql)) {
|
||||
$query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('cast(account_meta.data as int)'));
|
||||
}
|
||||
if (!in_array($driver, $pgsql)) {
|
||||
$query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('account_meta.data'));
|
||||
}
|
||||
|
||||
$set = $query->get(['transaction_journals.*', 'account_meta.data as expected_currency_id', 'transactions.amount as transaction_amount']);
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($set as $journal) {
|
||||
$expectedCurrency = $repository->find(intval($journal->expected_currency_id));
|
||||
$line = sprintf($notification, $type, $journal->id, $journal->transactionCurrency->code, $expectedCurrency->code);
|
||||
|
||||
$journal->setMeta('foreign_amount', $journal->transaction_amount);
|
||||
$journal->setMeta('foreign_currency_id', $journal->transaction_currency_id);
|
||||
$journal->transaction_currency_id = $expectedCurrency->id;
|
||||
$journal->save();
|
||||
$this->line($line);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* For transfers it's slightly different. Both source and destination
|
||||
* must match the respective currency preference. So we must verify ALL
|
||||
* transactions.
|
||||
*/
|
||||
$set = TransactionJournal
|
||||
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
->where('transaction_types.type', TransactionType::TRANSFER)
|
||||
->get(['transaction_journals.*']);
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($set as $journal) {
|
||||
$updated = false;
|
||||
/** @var Transaction $sourceTransaction */
|
||||
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
$sourceCurrency = $repository->find(intval($sourceTransaction->account->getMeta('currency_id')));
|
||||
|
||||
if ($sourceCurrency->id !== $journal->transaction_currency_id) {
|
||||
$updated = true;
|
||||
$journal->transaction_currency_id = $sourceCurrency->id;
|
||||
$journal->save();
|
||||
}
|
||||
|
||||
// destination
|
||||
$destinationTransaction = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
$destinationCurrency = $repository->find(intval($destinationTransaction->account->getMeta('currency_id')));
|
||||
|
||||
if ($destinationCurrency->id !== $journal->transaction_currency_id) {
|
||||
$updated = true;
|
||||
$journal->deleteMeta('foreign_amount');
|
||||
$journal->deleteMeta('foreign_currency_id');
|
||||
$journal->setMeta('foreign_amount', $destinationTransaction->amount);
|
||||
$journal->setMeta('foreign_currency_id', $destinationCurrency->id);
|
||||
}
|
||||
if ($updated) {
|
||||
$line = sprintf($transfer, $journal->id);
|
||||
$this->line($line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method makes sure that the tranaction uses the same currency as the source account does.
|
||||
* If not, the currency is updated to include a reference to its original currency as the "foreign" currency.
|
||||
*
|
||||
* The transaction that is sent to this function MUST be the source transaction (amount negative).
|
||||
*
|
||||
* Method is long and complex bit I'm taking it for granted.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
* @SuppressWarnings(PHPMD.NPathComplexity)
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*
|
||||
* @param Transaction $transaction
|
||||
*/
|
||||
private function verifyCurrencyInfo()
|
||||
private function updateTransactionCurrency(Transaction $transaction): void
|
||||
{
|
||||
$count = 0;
|
||||
$transactions = Transaction::get();
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$currencyId = intval($transaction->transaction_currency_id);
|
||||
$foreignId = intval($transaction->foreign_currency_id);
|
||||
if ($currencyId === $foreignId) {
|
||||
$transaction->foreign_currency_id = null;
|
||||
$transaction->foreign_amount = null;
|
||||
$transaction->save();
|
||||
$count++;
|
||||
}
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$currency = $repository->find(intval($transaction->account->getMeta('currency_id')));
|
||||
|
||||
// has no currency ID? Must have, so fill in using account preference:
|
||||
if (is_null($transaction->transaction_currency_id)) {
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
Log::debug(sprintf('Transaction #%d has no currency setting, now set to %s', $transaction->id, $currency->code));
|
||||
$transaction->save();
|
||||
}
|
||||
$this->line(sprintf('Updated currency information for %d transactions', $count));
|
||||
|
||||
// does not match the source account (see above)? Can be fixed
|
||||
// when mismatch in transaction and NO foreign amount is set:
|
||||
if ($transaction->transaction_currency_id !== $currency->id && is_null($transaction->foreign_amount)) {
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Transaction #%d has a currency setting (#%d) that should be #%d. Amount remains %s, currency is changed.', $transaction->id,
|
||||
$transaction->transaction_currency_id, $currency->id, $transaction->amount
|
||||
)
|
||||
);
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
$transaction->save();
|
||||
}
|
||||
|
||||
// grab opposing transaction:
|
||||
/** @var TransactionJournal $journal */
|
||||
$journal = $transaction->transactionJournal;
|
||||
/** @var Transaction $opposing */
|
||||
$opposing = $journal->transactions()->where('amount', '>', 0)->where('identifier', $transaction->identifier)->first();
|
||||
$opposingCurrency = $repository->find(intval($opposing->account->getMeta('currency_id')));
|
||||
|
||||
if (is_null($opposingCurrency->id)) {
|
||||
Log::error(sprintf('Account #%d ("%s") must have currency preference but has none.', $opposing->account->id, $opposing->account->name));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// if the destination account currency is the same, both foreign_amount and foreign_currency_id must be NULL for both transactions:
|
||||
if ($opposingCurrency->id === $currency->id) {
|
||||
// update both transactions to match:
|
||||
$transaction->foreign_amount = null;
|
||||
$transaction->foreign_currency_id = null;
|
||||
$opposing->foreign_amount = null;
|
||||
$opposing->foreign_currency_id = null;
|
||||
$opposing->transaction_currency_id = $currency->id;
|
||||
$transaction->save();
|
||||
$opposing->save();
|
||||
Log::debug(sprintf('Cleaned up transaction #%d and #%d', $transaction->id, $opposing->id));
|
||||
|
||||
return;
|
||||
}
|
||||
// if destination account currency is different, both transactions must have this currency as foreign currency id.
|
||||
if ($opposingCurrency->id !== $currency->id) {
|
||||
$transaction->foreign_currency_id = $opposingCurrency->id;
|
||||
$opposing->foreign_currency_id = $opposingCurrency->id;
|
||||
$transaction->save();
|
||||
$opposing->save();
|
||||
Log::debug(sprintf('Verified foreign currency ID of transaction #%d and #%d', $transaction->id, $opposing->id));
|
||||
}
|
||||
|
||||
// if foreign amount of one is null and the other is not, use this to restore:
|
||||
if (is_null($transaction->foreign_amount) && !is_null($opposing->foreign_amount)) {
|
||||
$transaction->foreign_amount = bcmul(strval($opposing->foreign_amount), '-1');
|
||||
$transaction->save();
|
||||
Log::debug(sprintf('Restored foreign amount of transaction (1) #%d to %s', $transaction->id, $transaction->foreign_amount));
|
||||
}
|
||||
|
||||
// if foreign amount of one is null and the other is not, use this to restore (other way around)
|
||||
if (is_null($opposing->foreign_amount) && !is_null($transaction->foreign_amount)) {
|
||||
$opposing->foreign_amount = bcmul(strval($transaction->foreign_amount), '-1');
|
||||
$opposing->save();
|
||||
Log::debug(sprintf('Restored foreign amount of transaction (2) #%d to %s', $opposing->id, $opposing->foreign_amount));
|
||||
}
|
||||
|
||||
// when both are zero, try to grab it from journal:
|
||||
if (is_null($opposing->foreign_amount) && is_null($transaction->foreign_amount)) {
|
||||
|
||||
$foreignAmount = $journal->getMeta('foreign_amount');
|
||||
if (is_null($foreignAmount)) {
|
||||
Log::debug(sprintf('Journal #%d has missing foreign currency data, forced to do 1:1 conversion :(.', $transaction->transaction_journal_id));
|
||||
$transaction->foreign_amount = bcmul(strval($transaction->amount), '-1');
|
||||
$opposing->foreign_amount = bcmul(strval($opposing->amount), '-1');
|
||||
$transaction->save();
|
||||
$opposing->save();
|
||||
|
||||
return;
|
||||
}
|
||||
$foreignPositive = app('steam')->positive(strval($foreignAmount));
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Journal #%d has missing foreign currency info, try to restore from meta-data ("%s").', $transaction->transaction_journal_id, $foreignAmount
|
||||
)
|
||||
);
|
||||
$transaction->foreign_amount = bcmul($foreignPositive, '-1');
|
||||
$opposing->foreign_amount = $foreignPositive;
|
||||
$transaction->save();
|
||||
$opposing->save();
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@@ -84,6 +84,9 @@ class UpgradeFireflyInstructions extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render instructions.
|
||||
*/
|
||||
private function installInstructions()
|
||||
{
|
||||
/** @var string $version */
|
||||
@@ -102,7 +105,7 @@ class UpgradeFireflyInstructions extends Command
|
||||
$this->boxed('');
|
||||
if (is_null($text)) {
|
||||
|
||||
$this->boxed(sprintf('Thank you for installin Firefly III, v%s!', $version));
|
||||
$this->boxed(sprintf('Thank you for installing Firefly III, v%s!', $version));
|
||||
$this->boxedInfo('There are no extra installation instructions.');
|
||||
$this->boxed('Firefly III should be ready for use.');
|
||||
$this->boxed('');
|
||||
@@ -131,6 +134,9 @@ class UpgradeFireflyInstructions extends Command
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Render upgrade instructions.
|
||||
*/
|
||||
private function updateInstructions()
|
||||
{
|
||||
/** @var string $version */
|
||||
|
@@ -1,10 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* UseEncryption.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* UseEncryption.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class UseEncryption
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class UseEncryption extends Command
|
||||
{
|
||||
/**
|
||||
@@ -34,7 +58,6 @@ class UseEncryption extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
//
|
||||
$this->handleObjects('Account', 'name', 'encrypted');
|
||||
$this->handleObjects('Bill', 'name', 'name_encrypted');
|
||||
$this->handleObjects('Bill', 'match', 'match_encrypted');
|
||||
@@ -45,6 +68,8 @@ class UseEncryption extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Run each object and encrypt them (or not).
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $field
|
||||
* @param string $indicator
|
||||
|
69
app/Console/Commands/VerifiesAccessToken.php
Normal file
69
app/Console/Commands/VerifiesAccessToken.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* VerifiesAccessToken.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use Log;
|
||||
use Preferences;
|
||||
|
||||
/**
|
||||
* Trait VerifiesAccessToken
|
||||
*
|
||||
* Verifies user access token for sensitive commands.
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
trait VerifiesAccessToken
|
||||
{
|
||||
/**
|
||||
* Abstract method to make sure trait knows about method "option".
|
||||
* @param null $key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function option($key = null);
|
||||
|
||||
/**
|
||||
* Returns false when given token does not match given user token.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function verifyAccessToken(): bool
|
||||
{
|
||||
$userId = intval($this->option('user'));
|
||||
$token = strval($this->option('token'));
|
||||
/** @var UserRepositoryInterface $repository */
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
$user = $repository->find($userId);
|
||||
|
||||
if (is_null($user->id)) {
|
||||
Log::error(sprintf('verifyAccessToken(): no such user for input "%d"', $userId));
|
||||
|
||||
return false;
|
||||
}
|
||||
$accessToken = Preferences::getForUser($user, 'access_token', null);
|
||||
if (is_null($accessToken)) {
|
||||
Log::error(sprintf('User #%d has no access token, so cannot access command line options.', $userId));
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!($accessToken->data === $token)) {
|
||||
Log::error(sprintf('Invalid access token for user #%d.', $userId));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@@ -17,6 +17,8 @@ use Crypt;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\LinkType;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
@@ -25,12 +27,15 @@ use FireflyIII\User;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Preferences;
|
||||
use Schema;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Class VerifyDatabase
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class VerifyDatabase extends Command
|
||||
@@ -69,33 +74,93 @@ class VerifyDatabase extends Command
|
||||
$this->reportObject('budget');
|
||||
$this->reportObject('category');
|
||||
$this->reportObject('tag');
|
||||
|
||||
// accounts with no transactions.
|
||||
$this->reportAccounts();
|
||||
// budgets with no limits
|
||||
$this->reportBudgetLimits();
|
||||
// budgets with no transactions
|
||||
|
||||
// sum of transactions is not zero.
|
||||
$this->reportSum();
|
||||
// any deleted transaction journals that have transactions that are NOT deleted:
|
||||
$this->reportJournals();
|
||||
// deleted transactions that are connected to a not deleted journal.
|
||||
$this->reportTransactions();
|
||||
// deleted accounts that still have not deleted transactions or journals attached to them.
|
||||
$this->reportDeletedAccounts();
|
||||
|
||||
// report on journals with no transactions at all.
|
||||
$this->reportNoTransactions();
|
||||
|
||||
// transfers with budgets.
|
||||
$this->reportTransfersBudgets();
|
||||
|
||||
// report on journals with the wrong types of accounts.
|
||||
$this->reportIncorrectJournals();
|
||||
$this->repairPiggyBanks();
|
||||
$this->createLinkTypes();
|
||||
$this->createAccessTokens();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create user access tokens, if not present already.
|
||||
*/
|
||||
private function createAccessTokens()
|
||||
{
|
||||
$users = User::get();
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$pref = Preferences::getForUser($user, 'access_token', null);
|
||||
if (is_null($pref)) {
|
||||
$token = $user->generateAccessToken();
|
||||
Preferences::setForUser($user, 'access_token', $token);
|
||||
$this->line(sprintf('Generated access token for user %s', $user->email));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default link types if necessary.
|
||||
*/
|
||||
private function createLinkTypes()
|
||||
{
|
||||
$set = [
|
||||
'Related' => ['relates to', 'relates to'],
|
||||
'Refund' => ['(partially) refunds', 'is (partially) refunded by'],
|
||||
'Paid' => ['(partially) pays for', 'is (partially) paid for by'],
|
||||
'Reimbursement' => ['(partially) reimburses', 'is (partially) reimbursed by'],
|
||||
];
|
||||
foreach ($set as $name => $values) {
|
||||
$link = LinkType::where('name', $name)->where('outward', $values[0])->where('inward', $values[1])->first();
|
||||
if (is_null($link)) {
|
||||
$link = new LinkType;
|
||||
$link->name = $name;
|
||||
$link->outward = $values[0];
|
||||
$link->inward = $values[1];
|
||||
}
|
||||
$link->editable = false;
|
||||
$link->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Eeport (and fix) piggy banks. Make sure there are only transfers linked to piggy bank events.
|
||||
*/
|
||||
private function repairPiggyBanks(): void
|
||||
{
|
||||
$set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get();
|
||||
$set->each(
|
||||
function (PiggyBankEvent $event) {
|
||||
if (is_null($event->transaction_journal_id)) {
|
||||
return true;
|
||||
}
|
||||
/** @var TransactionJournal $journal */
|
||||
$journal = $event->transactionJournal()->first();
|
||||
if (is_null($journal)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$type = $journal->transactionType->type;
|
||||
if ($type !== TransactionType::TRANSFER) {
|
||||
$event->transaction_journal_id = null;
|
||||
$event->save();
|
||||
$this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports on accounts with no transactions.
|
||||
*/
|
||||
@@ -169,6 +234,9 @@ class VerifyDatabase extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report on journals with bad account types linked to them.
|
||||
*/
|
||||
private function reportIncorrectJournals()
|
||||
{
|
||||
$configuration = [
|
||||
@@ -235,7 +303,7 @@ class VerifyDatabase extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Report on journals without transactions.
|
||||
*/
|
||||
private function reportNoTransactions()
|
||||
{
|
||||
@@ -253,6 +321,8 @@ class VerifyDatabase extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Report on things with no linked journals.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
private function reportObject(string $name)
|
||||
@@ -324,7 +394,7 @@ class VerifyDatabase extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Report on transfers that have budgets.
|
||||
*/
|
||||
private function reportTransfersBudgets()
|
||||
{
|
||||
|
@@ -1,53 +1,26 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* Kernel.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console;
|
||||
|
||||
use FireflyIII\Console\Commands\CreateImport;
|
||||
use FireflyIII\Console\Commands\EncryptFile;
|
||||
use FireflyIII\Console\Commands\Import;
|
||||
use FireflyIII\Console\Commands\ScanAttachments;
|
||||
use FireflyIII\Console\Commands\UpgradeDatabase;
|
||||
use FireflyIII\Console\Commands\UpgradeFireflyInstructions;
|
||||
use FireflyIII\Console\Commands\UseEncryption;
|
||||
use FireflyIII\Console\Commands\VerifyDatabase;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
/**
|
||||
* Class Kernel
|
||||
*
|
||||
* @package FireflyIII\Console
|
||||
* File to make sure commnds work.
|
||||
*/
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* The bootstrap classes for the application.
|
||||
*
|
||||
* Next upgrade verify these are the same.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bootstrappers
|
||||
= [
|
||||
'Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables',
|
||||
'Illuminate\Foundation\Bootstrap\LoadConfiguration',
|
||||
'Illuminate\Foundation\Bootstrap\HandleExceptions',
|
||||
'Illuminate\Foundation\Bootstrap\RegisterFacades',
|
||||
'Illuminate\Foundation\Bootstrap\SetRequestForConsole',
|
||||
'Illuminate\Foundation\Bootstrap\RegisterProviders',
|
||||
'Illuminate\Foundation\Bootstrap\BootProviders',
|
||||
];
|
||||
|
||||
/**
|
||||
* The Artisan commands provided by your application.
|
||||
*
|
||||
@@ -55,23 +28,29 @@ class Kernel extends ConsoleKernel
|
||||
*/
|
||||
protected $commands
|
||||
= [
|
||||
UpgradeFireflyInstructions::class,
|
||||
VerifyDatabase::class,
|
||||
Import::class,
|
||||
CreateImport::class,
|
||||
EncryptFile::class,
|
||||
ScanAttachments::class,
|
||||
UpgradeDatabase::class,
|
||||
UseEncryption::class,
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* Register the Closure based commands for the application.
|
||||
* Register the commands for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function commands()
|
||||
{
|
||||
$this->load(__DIR__ . '/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
*
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
* @return void
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
44
app/Events/AdminRequestedTestMessage.php
Normal file
44
app/Events/AdminRequestedTestMessage.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* AdminRequestedTestMessage.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
|
||||
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class AdminRequestedTestMessage
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
class AdminRequestedTestMessage extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public $ipAddress;
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param User $user
|
||||
* @param string $ipAddress
|
||||
*/
|
||||
public function __construct(User $user, string $ipAddress)
|
||||
{
|
||||
Log::debug(sprintf('Triggered AdminRequestedTestMessage for user #%d (%s) and IP %s!', $user->id, $user->email, $ipAddress));
|
||||
$this->user = $user;
|
||||
$this->ipAddress = $ipAddress;
|
||||
}
|
||||
}
|
51
app/Events/UserChangedEmail.php
Normal file
51
app/Events/UserChangedEmail.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* UserChangedEmail.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class UserChangedEmail
|
||||
*
|
||||
* @package FireflyIII\Events
|
||||
*/
|
||||
class UserChangedEmail extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/** @var string */
|
||||
public $ipAddress;
|
||||
/** @var string */
|
||||
public $newEmail;
|
||||
/** @var string */
|
||||
public $oldEmail;
|
||||
/** @var User */
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* UserChangedEmail constructor.
|
||||
*
|
||||
* @param User $user
|
||||
* @param string $newEmail
|
||||
* @param string $oldEmail
|
||||
* @param string $ipAddress
|
||||
*/
|
||||
public function __construct(User $user, string $newEmail, string $oldEmail, string $ipAddress)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->ipAddress = $ipAddress;
|
||||
$this->oldEmail = $oldEmail;
|
||||
$this->newEmail = $newEmail;
|
||||
}
|
||||
}
|
@@ -1,50 +1,45 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* Handler.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Exceptions;
|
||||
|
||||
use ErrorException;
|
||||
use Exception;
|
||||
use FireflyIII\Jobs\MailError;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Session\TokenMismatchException;
|
||||
use Illuminate\Validation\ValidationException as ValException;
|
||||
use Request;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
/**
|
||||
* Class Handler
|
||||
*
|
||||
* @package FireflyIII\Exceptions
|
||||
*/
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* A list of the exception types that should not be reported.
|
||||
* A list of the inputs that are never flashed for validation exceptions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dontFlash
|
||||
= [
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
/**
|
||||
* A list of the exception types that are not reported.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dontReport
|
||||
= [
|
||||
AuthenticationException::class,
|
||||
AuthorizationException::class,
|
||||
HttpException::class,
|
||||
ModelNotFoundException::class,
|
||||
TokenMismatchException::class,
|
||||
ValException::class,
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -67,14 +62,13 @@ class Handler extends ExceptionHandler
|
||||
return parent::render($request, $exception);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Report or log an exception.
|
||||
*
|
||||
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
|
||||
*
|
||||
* @param Exception $exception
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five its fine.
|
||||
* @param \Exception $exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -107,22 +101,7 @@ class Handler extends ExceptionHandler
|
||||
dispatch($job);
|
||||
}
|
||||
|
||||
|
||||
parent::report($exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an authentication exception into an unauthenticated response.
|
||||
*
|
||||
* @param $request
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
protected function unauthenticated($request)
|
||||
{
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['error' => 'Unauthenticated.'], 401);
|
||||
}
|
||||
|
||||
return redirect()->guest('login');
|
||||
}
|
||||
}
|
||||
|
@@ -122,6 +122,7 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
|
||||
*/
|
||||
private function getAttachments(): Collection
|
||||
{
|
||||
$this->repository->setUser($this->user);
|
||||
$attachments = $this->repository->getBetween($this->start, $this->end);
|
||||
|
||||
return $attachments;
|
||||
|
@@ -15,6 +15,7 @@ namespace FireflyIII\Export\Collector;
|
||||
|
||||
|
||||
use FireflyIII\Models\ExportJob;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
@@ -26,6 +27,8 @@ class BasicCollector
|
||||
{
|
||||
/** @var ExportJob */
|
||||
protected $job;
|
||||
/** @var User */
|
||||
protected $user;
|
||||
/** @var Collection */
|
||||
private $entries;
|
||||
|
||||
@@ -58,7 +61,16 @@ class BasicCollector
|
||||
*/
|
||||
public function setJob(ExportJob $job)
|
||||
{
|
||||
$this->job = $job;
|
||||
$this->job = $job;
|
||||
$this->user = $job->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
public function setUser(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1,347 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* JournalExportCollector.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Export\Collector;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Collection;
|
||||
use Steam;
|
||||
|
||||
/**
|
||||
* Class JournalExportCollector
|
||||
*
|
||||
* @package FireflyIII\Export\Collector
|
||||
*/
|
||||
class JournalExportCollector extends BasicCollector implements CollectorInterface
|
||||
{
|
||||
/** @var Collection */
|
||||
private $accounts;
|
||||
/** @var Carbon */
|
||||
private $end;
|
||||
/** @var Carbon */
|
||||
private $start;
|
||||
|
||||
/** @var Collection */
|
||||
private $workSet;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function run(): bool
|
||||
{
|
||||
/*
|
||||
* Instead of collecting journals we collect transactions for the given accounts.
|
||||
* We left join the OPPOSING transaction AND some journal data.
|
||||
* After that we complement this info with budgets, categories, etc.
|
||||
*
|
||||
* This is way more efficient and will also work on split journals.
|
||||
*/
|
||||
$this->getWorkSet();
|
||||
|
||||
/*
|
||||
* Extract:
|
||||
* possible budget ids for journals
|
||||
* possible category ids journals
|
||||
* possible budget ids for transactions
|
||||
* possible category ids for transactions
|
||||
*
|
||||
* possible IBAN and account numbers?
|
||||
*
|
||||
*/
|
||||
$journals = $this->extractJournalIds();
|
||||
$transactions = $this->extractTransactionIds();
|
||||
|
||||
|
||||
// extend work set with category data from journals:
|
||||
$this->categoryDataForJournals($journals);
|
||||
|
||||
// extend work set with category cate from transactions (overrules journals):
|
||||
$this->categoryDataForTransactions($transactions);
|
||||
|
||||
// same for budgets:
|
||||
$this->budgetDataForJournals($journals);
|
||||
$this->budgetDataForTransactions($transactions);
|
||||
|
||||
$this->setEntries($this->workSet);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
*/
|
||||
public function setAccounts(Collection $accounts)
|
||||
{
|
||||
$this->accounts = $accounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*/
|
||||
public function setDates(Carbon $start, Carbon $end)
|
||||
{
|
||||
$this->start = $start;
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $journals
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function budgetDataForJournals(array $journals): bool
|
||||
{
|
||||
$set = DB::table('budget_transaction_journal')
|
||||
->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction_journal.budget_id')
|
||||
->whereIn('budget_transaction_journal.transaction_journal_id', $journals)
|
||||
->get(
|
||||
[
|
||||
'budget_transaction_journal.budget_id',
|
||||
'budget_transaction_journal.transaction_journal_id',
|
||||
'budgets.name',
|
||||
'budgets.encrypted',
|
||||
]
|
||||
);
|
||||
$set->each(
|
||||
function ($obj) {
|
||||
$obj->name = Steam::decrypt(intval($obj->encrypted), $obj->name);
|
||||
}
|
||||
);
|
||||
$array = [];
|
||||
foreach ($set as $obj) {
|
||||
$array[$obj->transaction_journal_id] = ['id' => $obj->budget_id, 'name' => $obj->name];
|
||||
}
|
||||
|
||||
$this->workSet->each(
|
||||
function ($obj) use ($array) {
|
||||
if (isset($array[$obj->transaction_journal_id])) {
|
||||
$obj->budget_id = $array[$obj->transaction_journal_id]['id'];
|
||||
$obj->budget_name = $array[$obj->transaction_journal_id]['name'];
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $transactions
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function budgetDataForTransactions(array $transactions): bool
|
||||
{
|
||||
$set = DB::table('budget_transaction')
|
||||
->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction.budget_id')
|
||||
->whereIn('budget_transaction.transaction_id', $transactions)
|
||||
->get(
|
||||
[
|
||||
'budget_transaction.budget_id',
|
||||
'budget_transaction.transaction_id',
|
||||
'budgets.name',
|
||||
'budgets.encrypted',
|
||||
]
|
||||
);
|
||||
$set->each(
|
||||
function ($obj) {
|
||||
$obj->name = Steam::decrypt(intval($obj->encrypted), $obj->name);
|
||||
}
|
||||
);
|
||||
$array = [];
|
||||
foreach ($set as $obj) {
|
||||
$array[$obj->transaction_id] = ['id' => $obj->budget_id, 'name' => $obj->name];
|
||||
}
|
||||
|
||||
$this->workSet->each(
|
||||
function ($obj) use ($array) {
|
||||
|
||||
// first transaction
|
||||
if (isset($array[$obj->id])) {
|
||||
$obj->budget_id = $array[$obj->id]['id'];
|
||||
$obj->budget_name = $array[$obj->id]['name'];
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $journals
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function categoryDataForJournals(array $journals): bool
|
||||
{
|
||||
$set = DB::table('category_transaction_journal')
|
||||
->leftJoin('categories', 'categories.id', '=', 'category_transaction_journal.category_id')
|
||||
->whereIn('category_transaction_journal.transaction_journal_id', $journals)
|
||||
->get(
|
||||
[
|
||||
'category_transaction_journal.category_id',
|
||||
'category_transaction_journal.transaction_journal_id',
|
||||
'categories.name',
|
||||
'categories.encrypted',
|
||||
]
|
||||
);
|
||||
$set->each(
|
||||
function ($obj) {
|
||||
$obj->name = Steam::decrypt(intval($obj->encrypted), $obj->name);
|
||||
}
|
||||
);
|
||||
$array = [];
|
||||
foreach ($set as $obj) {
|
||||
$array[$obj->transaction_journal_id] = ['id' => $obj->category_id, 'name' => $obj->name];
|
||||
}
|
||||
|
||||
$this->workSet->each(
|
||||
function ($obj) use ($array) {
|
||||
if (isset($array[$obj->transaction_journal_id])) {
|
||||
$obj->category_id = $array[$obj->transaction_journal_id]['id'];
|
||||
$obj->category_name = $array[$obj->transaction_journal_id]['name'];
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $transactions
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function categoryDataForTransactions(array $transactions): bool
|
||||
{
|
||||
$set = DB::table('category_transaction')
|
||||
->leftJoin('categories', 'categories.id', '=', 'category_transaction.category_id')
|
||||
->whereIn('category_transaction.transaction_id', $transactions)
|
||||
->get(
|
||||
[
|
||||
'category_transaction.category_id',
|
||||
'category_transaction.transaction_id',
|
||||
'categories.name',
|
||||
'categories.encrypted',
|
||||
]
|
||||
);
|
||||
$set->each(
|
||||
function ($obj) {
|
||||
$obj->name = Steam::decrypt(intval($obj->encrypted), $obj->name);
|
||||
}
|
||||
);
|
||||
$array = [];
|
||||
foreach ($set as $obj) {
|
||||
$array[$obj->transaction_id] = ['id' => $obj->category_id, 'name' => $obj->name];
|
||||
}
|
||||
|
||||
$this->workSet->each(
|
||||
function ($obj) use ($array) {
|
||||
|
||||
// first transaction
|
||||
if (isset($array[$obj->id])) {
|
||||
$obj->category_id = $array[$obj->id]['id'];
|
||||
$obj->category_name = $array[$obj->id]['name'];
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function extractJournalIds(): array
|
||||
{
|
||||
return $this->workSet->pluck('transaction_journal_id')->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function extractTransactionIds()
|
||||
{
|
||||
$set = $this->workSet->pluck('id')->toArray();
|
||||
$opposing = $this->workSet->pluck('opposing_id')->toArray();
|
||||
$complete = $set + $opposing;
|
||||
|
||||
return array_unique($complete);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*/
|
||||
private function getWorkSet()
|
||||
{
|
||||
$accountIds = $this->accounts->pluck('id')->toArray();
|
||||
$this->workSet = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->leftJoin(
|
||||
'transactions AS opposing', function (JoinClause $join) {
|
||||
$join->on('opposing.transaction_journal_id', '=', 'transactions.transaction_journal_id')
|
||||
->where('opposing.amount', '=', DB::raw('transactions.amount * -1'))
|
||||
->where('transactions.identifier', '=', DB::raw('opposing.identifier'));
|
||||
}
|
||||
)
|
||||
->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
|
||||
->leftJoin('accounts AS opposing_accounts', 'opposing.account_id', '=', 'opposing_accounts.id')
|
||||
->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', 'transaction_types.id')
|
||||
->leftJoin('transaction_currencies', 'transactions.transaction_currency_id', '=', 'transaction_currencies.id')
|
||||
->whereIn('transactions.account_id', $accountIds)
|
||||
->where('transaction_journals.user_id', $this->job->user_id)
|
||||
->where('transaction_journals.date', '>=', $this->start->format('Y-m-d'))
|
||||
->where('transaction_journals.date', '<=', $this->end->format('Y-m-d'))
|
||||
->where('transaction_journals.completed', 1)
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->whereNull('transactions.deleted_at')
|
||||
->whereNull('opposing.deleted_at')
|
||||
->orderBy('transaction_journals.date', 'DESC')
|
||||
->orderBy('transactions.identifier', 'ASC')
|
||||
->get(
|
||||
[
|
||||
'transactions.id',
|
||||
'transactions.amount',
|
||||
'transactions.description',
|
||||
'transactions.account_id',
|
||||
'accounts.name as account_name',
|
||||
'accounts.encrypted as account_name_encrypted',
|
||||
'transactions.identifier',
|
||||
|
||||
'opposing.id as opposing_id',
|
||||
'opposing.amount AS opposing_amount',
|
||||
'opposing.description as opposing_description',
|
||||
'opposing.account_id as opposing_account_id',
|
||||
'opposing_accounts.name as opposing_account_name',
|
||||
'opposing_accounts.encrypted as opposing_account_encrypted',
|
||||
'opposing.identifier as opposing_identifier',
|
||||
|
||||
'transaction_journals.id as transaction_journal_id',
|
||||
'transaction_journals.date',
|
||||
'transaction_journals.description as journal_description',
|
||||
'transaction_journals.encrypted as journal_encrypted',
|
||||
'transaction_journals.transaction_type_id',
|
||||
'transaction_types.type as transaction_type',
|
||||
'transactions.transaction_currency_id',
|
||||
'transaction_currencies.code AS transaction_currency_code',
|
||||
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
@@ -15,6 +15,7 @@ namespace FireflyIII\Export\Collector;
|
||||
|
||||
use Crypt;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Log;
|
||||
use Storage;
|
||||
|
||||
@@ -29,8 +30,6 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
||||
private $exportDisk;
|
||||
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
|
||||
private $uploadDisk;
|
||||
/** @var string */
|
||||
private $vintageFormat;
|
||||
|
||||
/**
|
||||
* AttachmentCollector constructor.
|
||||
@@ -50,14 +49,6 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
||||
public function run(): bool
|
||||
{
|
||||
Log::debug('Going to collect attachments', ['key' => $this->job->key]);
|
||||
|
||||
// file names associated with the old import routine.
|
||||
$this->vintageFormat = sprintf('csv-upload-%d-', $this->job->user->id);
|
||||
|
||||
// collect old upload files (names beginning with "csv-upload".
|
||||
$this->collectVintageUploads();
|
||||
|
||||
// then collect current upload files:
|
||||
$this->collectModernUploads();
|
||||
|
||||
return true;
|
||||
@@ -70,7 +61,8 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
||||
*/
|
||||
private function collectModernUploads(): bool
|
||||
{
|
||||
$set = $this->job->user->importJobs()->where('status', 'import_complete')->get(['import_jobs.*']);
|
||||
$set = $this->job->user->importJobs()->whereIn('status', ['import_complete', 'finished'])->get(['import_jobs.*']);
|
||||
Log::debug(sprintf('Found %d import jobs', $set->count()));
|
||||
$keys = [];
|
||||
if ($set->count() > 0) {
|
||||
$keys = $set->pluck('key')->toArray();
|
||||
@@ -83,59 +75,6 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method collects all the uploads that are uploaded using the "old" importer. So from before the summer of 2016.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function collectVintageUploads(): bool
|
||||
{
|
||||
// grab upload directory.
|
||||
$files = $this->uploadDisk->files();
|
||||
|
||||
foreach ($files as $entry) {
|
||||
$this->processVintageUpload($entry);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method tells you when the vintage upload file was actually uploaded.
|
||||
*
|
||||
* @param string $entry
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getVintageUploadDate(string $entry): string
|
||||
{
|
||||
// this is an original upload.
|
||||
$parts = explode('-', str_replace(['.csv.encrypted', $this->vintageFormat], '', $entry));
|
||||
$originalUpload = intval($parts[1]);
|
||||
$date = date('Y-m-d \a\t H-i-s', $originalUpload);
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells you if a file name is a vintage upload.
|
||||
*
|
||||
* @param string $entry
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isVintageImport(string $entry): bool
|
||||
{
|
||||
$len = strlen($this->vintageFormat);
|
||||
// file is part of the old import routine:
|
||||
if (substr($entry, 0, $len) === $this->vintageFormat) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*
|
||||
@@ -153,7 +92,7 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
||||
$content = '';
|
||||
try {
|
||||
$content = Crypt::decrypt($this->uploadDisk->get(sprintf('%s.upload', $key)));
|
||||
} catch (DecryptException $e) {
|
||||
} catch (FileNotFoundException | DecryptException $e) {
|
||||
Log::error(sprintf('Could not decrypt old import file "%s". Skipped because: %s', $key, $e->getMessage()));
|
||||
}
|
||||
|
||||
@@ -168,47 +107,4 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the file is a vintage upload, process it.
|
||||
*
|
||||
* @param string $entry
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function processVintageUpload(string $entry): bool
|
||||
{
|
||||
if ($this->isVintageImport($entry)) {
|
||||
$this->saveVintageImportFile($entry);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This will store the content of the old vintage upload somewhere.
|
||||
*
|
||||
* @param string $entry
|
||||
*/
|
||||
private function saveVintageImportFile(string $entry)
|
||||
{
|
||||
$content = '';
|
||||
try {
|
||||
$content = Crypt::decrypt($this->uploadDisk->get($entry));
|
||||
} catch (DecryptException $e) {
|
||||
Log::error('Could not decrypt old CSV import file ' . $entry . '. Skipped because ' . $e->getMessage());
|
||||
}
|
||||
|
||||
if (strlen($content) > 0) {
|
||||
// add to export disk.
|
||||
$date = $this->getVintageUploadDate($entry);
|
||||
$file = $this->job->key . '-Old import dated ' . $date . '.csv';
|
||||
$this->exportDisk->put($file, $content);
|
||||
$this->getEntries()->push($file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Export\Entry;
|
||||
|
||||
use FireflyIII\Models\Transaction;
|
||||
use Steam;
|
||||
|
||||
/**
|
||||
@@ -30,6 +31,7 @@ use Steam;
|
||||
*
|
||||
* Class Entry
|
||||
* @SuppressWarnings(PHPMD.LongVariable)
|
||||
* @SuppressWarnings(PHPMD.TooManyFields)
|
||||
*
|
||||
* @package FireflyIII\Export\Entry
|
||||
*/
|
||||
@@ -37,24 +39,43 @@ final class Entry
|
||||
{
|
||||
// @formatter:off
|
||||
public $journal_id;
|
||||
public $transaction_id = 0;
|
||||
|
||||
public $date;
|
||||
public $description;
|
||||
|
||||
public $currency_code;
|
||||
public $amount;
|
||||
public $foreign_currency_code = '';
|
||||
public $foreign_amount = '0';
|
||||
|
||||
public $transaction_type;
|
||||
|
||||
public $source_account_id;
|
||||
public $source_account_name;
|
||||
public $asset_account_id;
|
||||
public $asset_account_name;
|
||||
public $asset_account_iban;
|
||||
public $asset_account_bic;
|
||||
public $asset_account_number;
|
||||
public $asset_currency_code;
|
||||
|
||||
public $destination_account_id;
|
||||
public $destination_account_name;
|
||||
public $opposing_account_id;
|
||||
public $opposing_account_name;
|
||||
public $opposing_account_iban;
|
||||
public $opposing_account_bic;
|
||||
public $opposing_account_number;
|
||||
public $opposing_currency_code;
|
||||
|
||||
public $budget_id;
|
||||
public $budget_name;
|
||||
|
||||
public $category_id;
|
||||
public $category_name;
|
||||
|
||||
public $bill_id;
|
||||
public $bill_name;
|
||||
|
||||
public $notes;
|
||||
public $tags;
|
||||
// @formatter:on
|
||||
|
||||
/**
|
||||
@@ -65,32 +86,74 @@ final class Entry
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $object
|
||||
* Converts a given transaction (as collected by the collector) into an export entry.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // complex but little choice.
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) // cannot be helped
|
||||
*
|
||||
* @param Transaction $transaction
|
||||
*
|
||||
* @return Entry
|
||||
*/
|
||||
public static function fromObject($object): Entry
|
||||
public static function fromTransaction(Transaction $transaction): Entry
|
||||
{
|
||||
$entry = new self;
|
||||
$entry->journal_id = $object->transaction_journal_id;
|
||||
$entry->description = Steam::decrypt(intval($object->journal_encrypted), $object->journal_description);
|
||||
$entry->amount = $object->amount;
|
||||
$entry->date = $object->date;
|
||||
$entry->transaction_type = $object->transaction_type;
|
||||
$entry->currency_code = $object->transaction_currency_code;
|
||||
$entry->source_account_id = $object->account_id;
|
||||
$entry->source_account_name = Steam::decrypt(intval($object->account_name_encrypted), $object->account_name);
|
||||
$entry->destination_account_id = $object->opposing_account_id;
|
||||
$entry->destination_account_name = Steam::decrypt(intval($object->opposing_account_encrypted), $object->opposing_account_name);
|
||||
$entry->category_id = $object->category_id ?? '';
|
||||
$entry->category_name = $object->category_name ?? '';
|
||||
$entry->budget_id = $object->budget_id ?? '';
|
||||
$entry->budget_name = $object->budget_name ?? '';
|
||||
|
||||
// update description when transaction description is different:
|
||||
if (!is_null($object->description) && $object->description !== $entry->description) {
|
||||
$entry->description = $entry->description . ' (' . $object->description . ')';
|
||||
$entry = new self;
|
||||
$entry->journal_id = $transaction->journal_id;
|
||||
$entry->transaction_id = $transaction->id;
|
||||
$entry->date = $transaction->date->format('Ymd');
|
||||
$entry->description = $transaction->description;
|
||||
if (strlen(strval($transaction->transaction_description)) > 0) {
|
||||
$entry->description = $transaction->transaction_description . '(' . $transaction->description . ')';
|
||||
}
|
||||
$entry->currency_code = $transaction->transactionCurrency->code;
|
||||
$entry->amount = round($transaction->transaction_amount, $transaction->transactionCurrency->decimal_places);
|
||||
|
||||
$entry->foreign_currency_code = is_null($transaction->foreign_currency_id) ? null : $transaction->foreignCurrency->code;
|
||||
$entry->foreign_amount = is_null($transaction->foreign_currency_id)
|
||||
? null
|
||||
: strval(
|
||||
round(
|
||||
$transaction->transaction_foreign_amount, $transaction->foreignCurrency->decimal_places
|
||||
)
|
||||
);
|
||||
|
||||
$entry->transaction_type = $transaction->transaction_type_type;
|
||||
$entry->asset_account_id = $transaction->account_id;
|
||||
$entry->asset_account_name = app('steam')->tryDecrypt($transaction->account_name);
|
||||
$entry->asset_account_iban = $transaction->account_iban;
|
||||
$entry->asset_account_number = $transaction->account_number;
|
||||
$entry->asset_account_bic = $transaction->account_bic;
|
||||
$entry->asset_currency_code = $transaction->account_currency_code;
|
||||
|
||||
$entry->opposing_account_id = $transaction->opposing_account_id;
|
||||
$entry->opposing_account_name = app('steam')->tryDecrypt($transaction->opposing_account_name);
|
||||
$entry->opposing_account_iban = $transaction->opposing_account_iban;
|
||||
$entry->opposing_account_number = $transaction->opposing_account_number;
|
||||
$entry->opposing_account_bic = $transaction->opposing_account_bic;
|
||||
$entry->opposing_currency_code = $transaction->opposing_currency_code;
|
||||
|
||||
/** budget */
|
||||
$entry->budget_id = $transaction->transaction_budget_id;
|
||||
$entry->budget_name = app('steam')->tryDecrypt($transaction->transaction_budget_name);
|
||||
if (is_null($transaction->transaction_budget_id)) {
|
||||
$entry->budget_id = $transaction->transaction_journal_budget_id;
|
||||
$entry->budget_name = app('steam')->tryDecrypt($transaction->transaction_journal_budget_name);
|
||||
}
|
||||
|
||||
/** category */
|
||||
$entry->category_id = $transaction->transaction_category_id;
|
||||
$entry->category_name = app('steam')->tryDecrypt($transaction->transaction_category_name);
|
||||
if (is_null($transaction->transaction_category_id)) {
|
||||
$entry->category_id = $transaction->transaction_journal_category_id;
|
||||
$entry->category_name = app('steam')->tryDecrypt($transaction->transaction_journal_category_name);
|
||||
}
|
||||
|
||||
/** budget */
|
||||
$entry->bill_id = $transaction->bill_id;
|
||||
$entry->bill_name = app('steam')->tryDecrypt($transaction->bill_name);
|
||||
|
||||
$entry->tags = $transaction->tags;
|
||||
$entry->notes = $transaction->notes;
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
346
app/Export/ExpandedProcessor.php
Normal file
346
app/Export/ExpandedProcessor.php
Normal file
@@ -0,0 +1,346 @@
|
||||
<?php
|
||||
/**
|
||||
* ExpandedProcessor.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Export;
|
||||
|
||||
|
||||
use Crypt;
|
||||
use DB;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Export\Collector\AttachmentCollector;
|
||||
use FireflyIII\Export\Collector\UploadCollector;
|
||||
use FireflyIII\Export\Entry\Entry;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Helpers\Filter\InternalTransferFilter;
|
||||
use FireflyIII\Models\AccountMeta;
|
||||
use FireflyIII\Models\ExportJob;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournalMeta;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Storage;
|
||||
use ZipArchive;
|
||||
|
||||
/**
|
||||
* Class ExpandedProcessor
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) // its doing a lot.
|
||||
*
|
||||
* @package FireflyIII\Export
|
||||
*/
|
||||
class ExpandedProcessor implements ProcessorInterface
|
||||
{
|
||||
|
||||
/** @var Collection */
|
||||
public $accounts;
|
||||
/** @var string */
|
||||
public $exportFormat;
|
||||
/** @var bool */
|
||||
public $includeAttachments;
|
||||
/** @var bool */
|
||||
public $includeOldUploads;
|
||||
/** @var ExportJob */
|
||||
public $job;
|
||||
/** @var array */
|
||||
public $settings;
|
||||
/** @var Collection */
|
||||
private $exportEntries;
|
||||
/** @var Collection */
|
||||
private $files;
|
||||
/** @var Collection */
|
||||
private $journals;
|
||||
|
||||
/**
|
||||
* Processor constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->journals = new Collection;
|
||||
$this->exportEntries = new Collection;
|
||||
$this->files = new Collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function collectAttachments(): bool
|
||||
{
|
||||
/** @var AttachmentCollector $attachmentCollector */
|
||||
$attachmentCollector = app(AttachmentCollector::class);
|
||||
$attachmentCollector->setJob($this->job);
|
||||
$attachmentCollector->setDates($this->settings['startDate'], $this->settings['endDate']);
|
||||
$attachmentCollector->run();
|
||||
$this->files = $this->files->merge($attachmentCollector->getEntries());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all transaction journals.
|
||||
*
|
||||
* @return bool
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*/
|
||||
public function collectJournals(): bool
|
||||
{
|
||||
// use journal collector thing.
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setUser($this->job->user);
|
||||
$collector->setAccounts($this->accounts)->setRange($this->settings['startDate'], $this->settings['endDate'])
|
||||
->withOpposingAccount()->withBudgetInformation()->withCategoryInformation()
|
||||
->removeFilter(InternalTransferFilter::class);
|
||||
$transactions = $collector->getJournals();
|
||||
// get some more meta data for each entry:
|
||||
$ids = $transactions->pluck('journal_id')->toArray();
|
||||
$assetIds = $transactions->pluck('account_id')->toArray();
|
||||
$opposingIds = $transactions->pluck('opposing_account_id')->toArray();
|
||||
$notes = $this->getNotes($ids);
|
||||
$tags = $this->getTags($ids);
|
||||
/** @var array $ibans */
|
||||
$ibans = $this->getIbans($assetIds) + $this->getIbans($opposingIds);
|
||||
$currencies = $this->getAccountCurrencies($ibans);
|
||||
$transactions->each(
|
||||
function (Transaction $transaction) use ($notes, $tags, $ibans, $currencies) {
|
||||
$journalId = intval($transaction->journal_id);
|
||||
$accountId = intval($transaction->account_id);
|
||||
$opposingId = intval($transaction->opposing_account_id);
|
||||
$currencyId = $ibans[$accountId]['currency_id'] ?? 0;
|
||||
$opposingCurrencyId = $ibans[$opposingId]['currency_id'] ?? 0;
|
||||
$transaction->notes = $notes[$journalId] ?? '';
|
||||
$transaction->tags = join(',', $tags[$journalId] ?? []);
|
||||
$transaction->account_number = $ibans[$accountId]['accountNumber'] ?? '';
|
||||
$transaction->account_bic = $ibans[$accountId]['BIC'] ?? '';
|
||||
$transaction->account_currency_code = $currencies[$currencyId] ?? '';
|
||||
$transaction->opposing_account_number = $ibans[$opposingId]['accountNumber'] ?? '';
|
||||
$transaction->opposing_account_bic = $ibans[$opposingId]['BIC'] ?? '';
|
||||
$transaction->opposing_currency_code = $currencies[$opposingCurrencyId] ?? '';
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
$this->journals = $transactions;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function collectOldUploads(): bool
|
||||
{
|
||||
/** @var UploadCollector $uploadCollector */
|
||||
$uploadCollector = app(UploadCollector::class);
|
||||
$uploadCollector->setJob($this->job);
|
||||
$uploadCollector->run();
|
||||
|
||||
$this->files = $this->files->merge($uploadCollector->getEntries());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function convertJournals(): bool
|
||||
{
|
||||
$this->journals->each(
|
||||
function (Transaction $transaction) {
|
||||
$this->exportEntries->push(Entry::fromTransaction($transaction));
|
||||
}
|
||||
);
|
||||
Log::debug(sprintf('Count %d entries in exportEntries (convertJournals)', $this->exportEntries->count()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function createZipFile(): bool
|
||||
{
|
||||
$zip = new ZipArchive;
|
||||
$file = $this->job->key . '.zip';
|
||||
$fullPath = storage_path('export') . '/' . $file;
|
||||
|
||||
if ($zip->open($fullPath, ZipArchive::CREATE) !== true) {
|
||||
throw new FireflyException('Cannot store zip file.');
|
||||
}
|
||||
// for each file in the collection, add it to the zip file.
|
||||
$disk = Storage::disk('export');
|
||||
foreach ($this->getFiles() as $entry) {
|
||||
// is part of this job?
|
||||
$zipFileName = str_replace($this->job->key . '-', '', $entry);
|
||||
$zip->addFromString($zipFileName, $disk->get($entry));
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
|
||||
// delete the files:
|
||||
$this->deleteFiles();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function exportJournals(): bool
|
||||
{
|
||||
$exporterClass = config('firefly.export_formats.' . $this->exportFormat);
|
||||
$exporter = app($exporterClass);
|
||||
$exporter->setJob($this->job);
|
||||
$exporter->setEntries($this->exportEntries);
|
||||
$exporter->run();
|
||||
$this->files->push($exporter->getFileName());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getFiles(): Collection
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save export job settings to class.
|
||||
*
|
||||
* @param array $settings
|
||||
*/
|
||||
public function setSettings(array $settings)
|
||||
{
|
||||
// save settings
|
||||
$this->settings = $settings;
|
||||
$this->accounts = $settings['accounts'];
|
||||
$this->exportFormat = $settings['exportFormat'];
|
||||
$this->includeAttachments = $settings['includeAttachments'];
|
||||
$this->includeOldUploads = $settings['includeOldUploads'];
|
||||
$this->job = $settings['job'];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function deleteFiles()
|
||||
{
|
||||
$disk = Storage::disk('export');
|
||||
foreach ($this->getFiles() as $file) {
|
||||
$disk->delete($file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getAccountCurrencies(array $array): array
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$return = [];
|
||||
$ids = [];
|
||||
$repository->setUser($this->job->user);
|
||||
foreach ($array as $value) {
|
||||
$ids[] = $value['currency_id'] ?? 0;
|
||||
}
|
||||
$ids = array_unique($ids);
|
||||
$result = $repository->getByIds($ids);
|
||||
|
||||
foreach ($result as $currency) {
|
||||
$return[$currency->id] = $currency->code;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all IBAN / SWIFT / account numbers
|
||||
*
|
||||
* @param array $array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getIbans(array $array): array
|
||||
{
|
||||
$array = array_unique($array);
|
||||
$return = [];
|
||||
$set = AccountMeta::whereIn('account_id', $array)
|
||||
->leftJoin('accounts', 'accounts.id', 'account_meta.account_id')
|
||||
->where('accounts.user_id', $this->job->user_id)
|
||||
->whereIn('account_meta.name', ['accountNumber', 'BIC', 'currency_id'])
|
||||
->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data']);
|
||||
/** @var AccountMeta $meta */
|
||||
foreach ($set as $meta) {
|
||||
$id = intval($meta->account_id);
|
||||
$return[$id][$meta->name] = $meta->data;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns, if present, for the given journal ID's the notes.
|
||||
*
|
||||
* @param array $array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getNotes(array $array): array
|
||||
{
|
||||
$array = array_unique($array);
|
||||
$set = TransactionJournalMeta::whereIn('journal_meta.transaction_journal_id', $array)
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
|
||||
->where('transaction_journals.user_id', $this->job->user_id)
|
||||
->where('journal_meta.name', 'notes')->get(
|
||||
['journal_meta.transaction_journal_id', 'journal_meta.data', 'journal_meta.id']
|
||||
);
|
||||
$return = [];
|
||||
/** @var TransactionJournalMeta $meta */
|
||||
foreach ($set as $meta) {
|
||||
$id = intval($meta->transaction_journal_id);
|
||||
$return[$id] = $meta->data;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comma joined list of all the users tags linked to these journals.
|
||||
*
|
||||
* @param array $array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getTags(array $array): array
|
||||
{
|
||||
$set = DB::table('tag_transaction_journal')
|
||||
->whereIn('tag_transaction_journal.transaction_journal_id', $array)
|
||||
->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id')
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'tag_transaction_journal.transaction_journal_id')
|
||||
->where('transaction_journals.user_id', $this->job->user_id)
|
||||
->get(['tag_transaction_journal.transaction_journal_id', 'tags.tag']);
|
||||
$result = [];
|
||||
foreach ($set as $entry) {
|
||||
$id = intval($entry->transaction_journal_id);
|
||||
$result[$id] = isset($result[$id]) ? $result[$id] : [];
|
||||
$result[$id][] = Crypt::decrypt($entry->tag);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@@ -58,8 +58,12 @@ class CsvExporter extends BasicExporter implements ExporterInterface
|
||||
|
||||
// get field names for header row:
|
||||
$first = $this->getEntries()->first();
|
||||
$headers = array_keys(get_object_vars($first));
|
||||
$rows[] = $headers;
|
||||
$headers = [];
|
||||
if (!is_null($first)) {
|
||||
$headers = array_keys(get_object_vars($first));
|
||||
}
|
||||
|
||||
$rows[] = $headers;
|
||||
|
||||
/** @var Entry $entry */
|
||||
foreach ($this->getEntries() as $entry) {
|
||||
|
@@ -1,203 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Processor.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Export;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Export\Collector\AttachmentCollector;
|
||||
use FireflyIII\Export\Collector\JournalExportCollector;
|
||||
use FireflyIII\Export\Collector\UploadCollector;
|
||||
use FireflyIII\Export\Entry\Entry;
|
||||
use FireflyIII\Models\ExportJob;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Storage;
|
||||
use ZipArchive;
|
||||
|
||||
/**
|
||||
* Class Processor
|
||||
*
|
||||
* @package FireflyIII\Export
|
||||
*/
|
||||
class Processor implements ProcessorInterface
|
||||
{
|
||||
|
||||
/** @var Collection */
|
||||
public $accounts;
|
||||
/** @var string */
|
||||
public $exportFormat;
|
||||
/** @var bool */
|
||||
public $includeAttachments;
|
||||
/** @var bool */
|
||||
public $includeOldUploads;
|
||||
/** @var ExportJob */
|
||||
public $job;
|
||||
/** @var array */
|
||||
public $settings;
|
||||
/** @var Collection */
|
||||
private $exportEntries;
|
||||
/** @var Collection */
|
||||
private $files;
|
||||
/** @var Collection */
|
||||
private $journals;
|
||||
|
||||
/**
|
||||
* Processor constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->journals = new Collection;
|
||||
$this->exportEntries = new Collection;
|
||||
$this->files = new Collection;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function collectAttachments(): bool
|
||||
{
|
||||
/** @var AttachmentCollector $attachmentCollector */
|
||||
$attachmentCollector = app(AttachmentCollector::class);
|
||||
$attachmentCollector->setJob($this->job);
|
||||
$attachmentCollector->setDates($this->settings['startDate'], $this->settings['endDate']);
|
||||
$attachmentCollector->run();
|
||||
$this->files = $this->files->merge($attachmentCollector->getEntries());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function collectJournals(): bool
|
||||
{
|
||||
/** @var JournalExportCollector $collector */
|
||||
$collector = app(JournalExportCollector::class);
|
||||
$collector->setJob($this->job);
|
||||
$collector->setDates($this->settings['startDate'], $this->settings['endDate']);
|
||||
$collector->setAccounts($this->settings['accounts']);
|
||||
$collector->run();
|
||||
$this->journals = $collector->getEntries();
|
||||
Log::debug(sprintf('Count %d journals in collectJournals() ', $this->journals->count()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function collectOldUploads(): bool
|
||||
{
|
||||
/** @var UploadCollector $uploadCollector */
|
||||
$uploadCollector = app(UploadCollector::class);
|
||||
$uploadCollector->setJob($this->job);
|
||||
$uploadCollector->run();
|
||||
|
||||
$this->files = $this->files->merge($uploadCollector->getEntries());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function convertJournals(): bool
|
||||
{
|
||||
$count = 0;
|
||||
foreach ($this->journals as $object) {
|
||||
$this->exportEntries->push(Entry::fromObject($object));
|
||||
$count++;
|
||||
}
|
||||
Log::debug(sprintf('Count %d entries in exportEntries (convertJournals)', $this->exportEntries->count()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function createZipFile(): bool
|
||||
{
|
||||
$zip = new ZipArchive;
|
||||
$file = $this->job->key . '.zip';
|
||||
$fullPath = storage_path('export') . '/' . $file;
|
||||
|
||||
if ($zip->open($fullPath, ZipArchive::CREATE) !== true) {
|
||||
throw new FireflyException('Cannot store zip file.');
|
||||
}
|
||||
// for each file in the collection, add it to the zip file.
|
||||
$disk = Storage::disk('export');
|
||||
foreach ($this->getFiles() as $entry) {
|
||||
// is part of this job?
|
||||
$zipFileName = str_replace($this->job->key . '-', '', $entry);
|
||||
$zip->addFromString($zipFileName, $disk->get($entry));
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
|
||||
// delete the files:
|
||||
$this->deleteFiles();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function exportJournals(): bool
|
||||
{
|
||||
$exporterClass = config('firefly.export_formats.' . $this->exportFormat);
|
||||
$exporter = app($exporterClass);
|
||||
$exporter->setJob($this->job);
|
||||
$exporter->setEntries($this->exportEntries);
|
||||
$exporter->run();
|
||||
$this->files->push($exporter->getFileName());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getFiles(): Collection
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $settings
|
||||
*/
|
||||
public function setSettings(array $settings)
|
||||
{
|
||||
// save settings
|
||||
$this->settings = $settings;
|
||||
$this->accounts = $settings['accounts'];
|
||||
$this->exportFormat = $settings['exportFormat'];
|
||||
$this->includeAttachments = $settings['includeAttachments'];
|
||||
$this->includeOldUploads = $settings['includeOldUploads'];
|
||||
$this->job = $settings['job'];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function deleteFiles()
|
||||
{
|
||||
$disk = Storage::disk('export');
|
||||
foreach ($this->getFiles() as $file) {
|
||||
$disk->delete($file);
|
||||
}
|
||||
}
|
||||
}
|
@@ -145,6 +145,9 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) // not that long
|
||||
*
|
||||
*/
|
||||
private function getAuditReport(Account $account, Carbon $date): array
|
||||
{
|
||||
@@ -175,9 +178,6 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
$transaction->currency = $currency;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reverse set again.
|
||||
*/
|
||||
$return = [
|
||||
'journals' => $journals->reverse(),
|
||||
'exists' => $journals->count() > 0,
|
||||
|
55
app/Handlers/Events/AdminEventHandler.php
Normal file
55
app/Handlers/Events/AdminEventHandler.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* AdminEventHandler.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Events\AdminRequestedTestMessage;
|
||||
use FireflyIII\Mail\AdminTestMail;
|
||||
use Log;
|
||||
use Mail;
|
||||
use Session;
|
||||
use Swift_TransportException;
|
||||
|
||||
/**
|
||||
* Class AdminEventHandler
|
||||
*
|
||||
* @package FireflyIII\Handlers\Events
|
||||
*/
|
||||
class AdminEventHandler
|
||||
{
|
||||
/**
|
||||
* @param AdminRequestedTestMessage $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sendTestMessage(AdminRequestedTestMessage $event): bool
|
||||
{
|
||||
|
||||
$email = $event->user->email;
|
||||
$ipAddress = $event->ipAddress;
|
||||
|
||||
Log::debug(sprintf('Now in sendTestMessage event handler. Email is %s, IP is %s', $email, $ipAddress));
|
||||
try {
|
||||
Log::debug('Trying to send message...');
|
||||
Mail::to($email)->send(new AdminTestMail($email, $ipAddress));
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (Swift_TransportException $e) {
|
||||
Log::debug('Send message failed! :(');
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
Session::flash('error', 'Possible email error: ' . $e->getMessage());
|
||||
}
|
||||
Log::debug('If no error above this line, message was sent.');
|
||||
// @codeCoverageIgnoreEnd
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -19,8 +19,8 @@ use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface as JRI;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface as PRI;
|
||||
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface as RGRI;
|
||||
use FireflyIII\Rules\Processor;
|
||||
use FireflyIII\Support\Events\BillScanner;
|
||||
use FireflyIII\TransactionRules\Processor;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
@@ -57,11 +57,12 @@ class StoredJournalEventHandler
|
||||
/**
|
||||
* This method connects a new transfer to a piggy bank.
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param StoredTransactionJournal $event
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*/
|
||||
public function connectToPiggyBank(StoredTransactionJournal $event): bool
|
||||
{
|
||||
|
@@ -18,8 +18,8 @@ use FireflyIII\Events\UpdatedTransactionJournal;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
|
||||
use FireflyIII\Rules\Processor;
|
||||
use FireflyIII\Support\Events\BillScanner;
|
||||
use FireflyIII\TransactionRules\Processor;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
|
@@ -15,11 +15,15 @@ namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Events\RegisteredUser;
|
||||
use FireflyIII\Events\RequestedNewPassword;
|
||||
use FireflyIII\Events\UserChangedEmail;
|
||||
use FireflyIII\Mail\ConfirmEmailChangeMail;
|
||||
use FireflyIII\Mail\RegisteredUser as RegisteredUserMail;
|
||||
use FireflyIII\Mail\RequestedNewPassword as RequestedNewPasswordMail;
|
||||
use FireflyIII\Mail\UndoEmailChangeMail;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use Log;
|
||||
use Mail;
|
||||
use Preferences;
|
||||
use Swift_TransportException;
|
||||
|
||||
/**
|
||||
@@ -54,6 +58,54 @@ class UserEventHandler
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserChangedEmail $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sendEmailChangeConfirmMail(UserChangedEmail $event): bool
|
||||
{
|
||||
$newEmail = $event->newEmail;
|
||||
$oldEmail = $event->oldEmail;
|
||||
$user = $event->user;
|
||||
$ipAddress = $event->ipAddress;
|
||||
$token = Preferences::getForUser($user, 'email_change_confirm_token', 'invalid');
|
||||
$uri = route('profile.confirm-email-change', [$token->data]);
|
||||
try {
|
||||
Mail::to($newEmail)->send(new ConfirmEmailChangeMail($newEmail, $oldEmail, $uri, $ipAddress));
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (Swift_TransportException $e) {
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreEnd
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserChangedEmail $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sendEmailChangeUndoMail(UserChangedEmail $event): bool
|
||||
{
|
||||
$newEmail = $event->newEmail;
|
||||
$oldEmail = $event->oldEmail;
|
||||
$user = $event->user;
|
||||
$ipAddress = $event->ipAddress;
|
||||
$token = Preferences::getForUser($user, 'email_change_undo_token', 'invalid');
|
||||
$uri = route('profile.undo-email-change', [$token->data, hash('sha256', $oldEmail)]);
|
||||
try {
|
||||
Mail::to($oldEmail)->send(new UndoEmailChangeMail($newEmail, $oldEmail, $uri, $ipAddress));
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (Swift_TransportException $e) {
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreEnd
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestedNewPassword $event
|
||||
*
|
||||
|
@@ -99,7 +99,7 @@ class AttachmentHelper implements AttachmentHelperInterface
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function saveAttachmentsForModel(Model $model, array $files = null): bool
|
||||
public function saveAttachmentsForModel(Model $model, ?array $files): bool
|
||||
{
|
||||
if (is_array($files)) {
|
||||
foreach ($files as $entry) {
|
||||
@@ -169,7 +169,6 @@ class AttachmentHelper implements AttachmentHelperInterface
|
||||
|
||||
// store it:
|
||||
$this->uploadDisk->put($attachment->fileName(), $encrypted);
|
||||
|
||||
$attachment->uploaded = 1; // update attachment
|
||||
$attachment->save();
|
||||
$this->attachments->push($attachment);
|
||||
@@ -180,8 +179,6 @@ class AttachmentHelper implements AttachmentHelperInterface
|
||||
|
||||
// return it.
|
||||
return $attachment;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -55,6 +55,6 @@ interface AttachmentHelperInterface
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function saveAttachmentsForModel(Model $model, array $files = null): bool;
|
||||
public function saveAttachmentsForModel(Model $model, ?array $files): bool;
|
||||
|
||||
}
|
||||
|
@@ -32,6 +32,8 @@ use Steam;
|
||||
* Class MetaPieChart
|
||||
*
|
||||
* @package FireflyIII\Helpers\Chart
|
||||
*
|
||||
*
|
||||
*/
|
||||
class MetaPieChart implements MetaPieChartInterface
|
||||
{
|
||||
@@ -83,12 +85,15 @@ class MetaPieChart implements MetaPieChartInterface
|
||||
* @param string $group
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*/
|
||||
public function generate(string $direction, string $group): array
|
||||
{
|
||||
$transactions = $this->getTransactions($direction);
|
||||
$grouped = $this->groupByFields($transactions, $this->grouping[$group]);
|
||||
$chartData = $this->organizeByType($group, $grouped);
|
||||
$key = strval(trans('firefly.everything_else'));
|
||||
|
||||
// also collect all other transactions
|
||||
if ($this->collectOtherObjects && $direction === 'expense') {
|
||||
@@ -96,11 +101,12 @@ class MetaPieChart implements MetaPieChartInterface
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setUser($this->user);
|
||||
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::WITHDRAWAL]);
|
||||
$journals = $collector->getJournals();
|
||||
$sum = strval($journals->sum('transaction_amount'));
|
||||
$sum = bcmul($sum, '-1');
|
||||
$sum = bcsub($sum, $this->total);
|
||||
$chartData[strval(trans('firefly.everything_else'))] = $sum;
|
||||
|
||||
$journals = $collector->getJournals();
|
||||
$sum = strval($journals->sum('transaction_amount'));
|
||||
$sum = bcmul($sum, '-1');
|
||||
$sum = bcsub($sum, $this->total);
|
||||
$chartData[$key] = $sum;
|
||||
}
|
||||
|
||||
if ($this->collectOtherObjects && $direction === 'income') {
|
||||
@@ -108,10 +114,10 @@ class MetaPieChart implements MetaPieChartInterface
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setUser($this->user);
|
||||
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::DEPOSIT]);
|
||||
$journals = $collector->getJournals();
|
||||
$sum = strval($journals->sum('transaction_amount'));
|
||||
$sum = bcsub($sum, $this->total);
|
||||
$chartData[strval(trans('firefly.everything_else'))] = $sum;
|
||||
$journals = $collector->getJournals();
|
||||
$sum = strval($journals->sum('transaction_amount'));
|
||||
$sum = bcsub($sum, $this->total);
|
||||
$chartData[$key] = $sum;
|
||||
}
|
||||
|
||||
return $chartData;
|
||||
@@ -258,12 +264,9 @@ class MetaPieChart implements MetaPieChartInterface
|
||||
$collector->removeFilter(TransferFilter::class);
|
||||
}
|
||||
|
||||
if ($this->budgets->count() > 0) {
|
||||
$collector->setBudgets($this->budgets);
|
||||
}
|
||||
if ($this->categories->count() > 0) {
|
||||
$collector->setCategories($this->categories);
|
||||
}
|
||||
$collector->setBudgets($this->budgets);
|
||||
$collector->setCategories($this->categories);
|
||||
|
||||
if ($this->tags->count() > 0) {
|
||||
$collector->setTags($this->tags);
|
||||
$collector->withCategoryInformation();
|
||||
@@ -278,6 +281,9 @@ class MetaPieChart implements MetaPieChartInterface
|
||||
* @param array $fields
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*
|
||||
*/
|
||||
protected function groupByFields(Collection $set, array $fields): array
|
||||
{
|
||||
|
@@ -15,7 +15,6 @@ namespace FireflyIII\Helpers\Collector;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Crypt;
|
||||
use DB;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Filter\FilterInterface;
|
||||
@@ -31,7 +30,6 @@ use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
@@ -45,6 +43,9 @@ use Steam;
|
||||
* Class JournalCollector
|
||||
*
|
||||
* @package FireflyIII\Helpers\Collector
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*/
|
||||
class JournalCollector implements JournalCollectorInterface
|
||||
{
|
||||
@@ -64,29 +65,31 @@ class JournalCollector implements JournalCollectorInterface
|
||||
'transaction_journals.bill_id',
|
||||
'bills.name as bill_name',
|
||||
'bills.name_encrypted as bill_name_encrypted',
|
||||
'transactions.id as id',
|
||||
|
||||
'transactions.id as id',
|
||||
'transactions.description as transaction_description',
|
||||
'transactions.account_id',
|
||||
'transactions.identifier',
|
||||
'transactions.transaction_journal_id',
|
||||
|
||||
'transactions.amount as transaction_amount',
|
||||
|
||||
'transactions.transaction_currency_id as transaction_currency_id',
|
||||
|
||||
'transaction_currencies.code as transaction_currency_code',
|
||||
'transaction_currencies.symbol as transaction_currency_symbol',
|
||||
'transaction_currencies.decimal_places as transaction_currency_dp',
|
||||
|
||||
'transactions.foreign_amount as transaction_foreign_amount',
|
||||
'transactions.foreign_currency_id as foreign_currency_id',
|
||||
|
||||
'foreign_currencies.code as foreign_currency_code',
|
||||
'foreign_currencies.symbol as foreign_currency_symbol',
|
||||
'foreign_currencies.decimal_places as foreign_currency_dp',
|
||||
|
||||
'accounts.name as account_name',
|
||||
'accounts.encrypted as account_encrypted',
|
||||
'accounts.iban as account_iban',
|
||||
'account_types.type as account_type',
|
||||
|
||||
];
|
||||
/** @var bool */
|
||||
private $filterTransfers = false;
|
||||
@@ -175,12 +178,10 @@ class JournalCollector implements JournalCollectorInterface
|
||||
if (!is_null($transaction->bill_name)) {
|
||||
$transaction->bill_name = Steam::decrypt(intval($transaction->bill_name_encrypted), $transaction->bill_name);
|
||||
}
|
||||
$transaction->opposing_account_name = app('steam')->tryDecrypt($transaction->opposing_account_name);
|
||||
$transaction->account_iban = app('steam')->tryDecrypt($transaction->account_iban);
|
||||
$transaction->opposing_account_iban = app('steam')->tryDecrypt($transaction->opposing_account_iban);
|
||||
|
||||
try {
|
||||
$transaction->opposing_account_name = Crypt::decrypt($transaction->opposing_account_name);
|
||||
} catch (DecryptException $e) {
|
||||
// if this fails its already decrypted.
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
@@ -399,6 +400,10 @@ class JournalCollector implements JournalCollectorInterface
|
||||
*/
|
||||
public function setPage(int $page): JournalCollectorInterface
|
||||
{
|
||||
if ($page < 1) {
|
||||
$page = 1;
|
||||
}
|
||||
|
||||
$this->page = $page;
|
||||
|
||||
if ($page > 0) {
|
||||
@@ -411,10 +416,10 @@ class JournalCollector implements JournalCollectorInterface
|
||||
$this->offset = $offset;
|
||||
$this->query->skip($offset);
|
||||
Log::debug(sprintf('Changed offset to %d', $offset));
|
||||
|
||||
return $this;
|
||||
}
|
||||
if (is_null($this->limit)) {
|
||||
Log::debug('The limit is zero, cannot set the page.');
|
||||
}
|
||||
Log::debug('The limit is zero, cannot set the page.');
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -617,6 +622,8 @@ class JournalCollector implements JournalCollectorInterface
|
||||
$this->query->leftJoin('budgets as transaction_journal_budgets', 'transaction_journal_budgets.id', '=', 'budget_transaction_journal.budget_id');
|
||||
$this->query->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'transactions.id');
|
||||
$this->query->leftJoin('budgets as transaction_budgets', 'transaction_budgets.id', '=', 'budget_transaction.budget_id');
|
||||
$this->query->whereNull('transaction_journal_budgets.deleted_at');
|
||||
$this->query->whereNull('transaction_budgets.deleted_at');
|
||||
|
||||
$this->fields[] = 'budget_transaction_journal.budget_id as transaction_journal_budget_id';
|
||||
$this->fields[] = 'transaction_journal_budgets.encrypted as transaction_journal_budget_encrypted';
|
||||
@@ -673,9 +680,12 @@ class JournalCollector implements JournalCollectorInterface
|
||||
$this->query->leftJoin('account_types as opposing_account_types', 'opposing_accounts.account_type_id', '=', 'opposing_account_types.id');
|
||||
$this->query->whereNull('opposing.deleted_at');
|
||||
|
||||
$this->fields[] = 'opposing.account_id as opposing_account_id';
|
||||
$this->fields[] = 'opposing_accounts.name as opposing_account_name';
|
||||
$this->fields[] = 'opposing_accounts.encrypted as opposing_account_encrypted';
|
||||
$this->fields[] = 'opposing.id as opposing_id';
|
||||
$this->fields[] = 'opposing.account_id as opposing_account_id';
|
||||
$this->fields[] = 'opposing_accounts.name as opposing_account_name';
|
||||
$this->fields[] = 'opposing_accounts.encrypted as opposing_account_encrypted';
|
||||
$this->fields[] = 'opposing_accounts.iban as opposing_account_iban';
|
||||
|
||||
$this->fields[] = 'opposing_account_types.type as opposing_account_type';
|
||||
$this->joinedOpposing = true;
|
||||
Log::debug('joinedOpposing is now true!');
|
||||
|
@@ -41,11 +41,13 @@ class TransferFilter implements FilterInterface
|
||||
continue;
|
||||
}
|
||||
// make property string:
|
||||
$journalId = $transaction->transaction_journal_id;
|
||||
$amount = Steam::positive($transaction->transaction_amount);
|
||||
$accountIds = [intval($transaction->account_id), intval($transaction->opposing_account_id)];
|
||||
$journalId = $transaction->transaction_journal_id;
|
||||
$amount = Steam::positive($transaction->transaction_amount);
|
||||
$accountIds = [intval($transaction->account_id), intval($transaction->opposing_account_id)];
|
||||
$transactionIds = [$transaction->id, intval($transaction->opposing_id)];
|
||||
sort($accountIds);
|
||||
$key = $journalId . '-' . join(',', $accountIds) . '-' . $amount;
|
||||
sort($transactionIds);
|
||||
$key = $journalId . '-' . join(',', $transactionIds) . '-' . join(',', $accountIds) . '-' . $amount;
|
||||
if (!isset($count[$key])) {
|
||||
// not yet counted? add to new set and count it:
|
||||
$new->push($transaction);
|
||||
|
@@ -14,16 +14,12 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Helpers\Report;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use FireflyIII\Helpers\Collection\Balance;
|
||||
use FireflyIII\Helpers\Collection\BalanceEntry;
|
||||
use FireflyIII\Helpers\Collection\BalanceHeader;
|
||||
use FireflyIII\Helpers\Collection\BalanceLine;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
|
||||
@@ -71,17 +67,14 @@ class BalanceReportHelper implements BalanceReportHelperInterface
|
||||
|
||||
/** @var BudgetLimit $budgetLimit */
|
||||
foreach ($budgetLimits as $budgetLimit) {
|
||||
$line = $this->createBalanceLine($budgetLimit, $accounts);
|
||||
$balance->addBalanceLine($line);
|
||||
if (!is_null($budgetLimit->budget)) {
|
||||
$line = $this->createBalanceLine($budgetLimit, $accounts);
|
||||
$balance->addBalanceLine($line);
|
||||
}
|
||||
}
|
||||
Log::debug('Create rest of the things.');
|
||||
$noBudgetLine = $this->createNoBudgetLine($accounts, $start, $end);
|
||||
$coveredByTagLine = $this->createTagsBalanceLine($accounts, $start, $end);
|
||||
$leftUnbalancedLine = $this->createLeftUnbalancedLine($noBudgetLine, $coveredByTagLine);
|
||||
$noBudgetLine = $this->createNoBudgetLine($accounts, $start, $end);
|
||||
|
||||
$balance->addBalanceLine($noBudgetLine);
|
||||
$balance->addBalanceLine($coveredByTagLine);
|
||||
$balance->addBalanceLine($leftUnbalancedLine);
|
||||
$balance->setBalanceHeader($header);
|
||||
|
||||
Log::debug('Clear unused budgets.');
|
||||
@@ -93,54 +86,6 @@ class BalanceReportHelper implements BalanceReportHelperInterface
|
||||
return $balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method collects all transfers that are part of a "balancing act" tag
|
||||
* and groups the amounts of those transfers by their destination account.
|
||||
*
|
||||
* This is used to indicate which expenses, usually outside of budgets, have been
|
||||
* corrected by transfers from a savings account.
|
||||
*
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
private function allCoveredByBalancingActs(Collection $accounts, Carbon $start, Carbon $end): Collection
|
||||
{
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
$set = auth()->user()->tags()
|
||||
->leftJoin('tag_transaction_journal', 'tag_transaction_journal.tag_id', '=', 'tags.id')
|
||||
->leftJoin('transaction_journals', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id')
|
||||
->leftJoin(
|
||||
'transactions AS t_source', function (JoinClause $join) {
|
||||
$join->on('transaction_journals.id', '=', 't_source.transaction_journal_id')->where('t_source.amount', '<', 0);
|
||||
}
|
||||
)
|
||||
->leftJoin(
|
||||
'transactions AS t_destination', function (JoinClause $join) {
|
||||
$join->on('transaction_journals.id', '=', 't_destination.transaction_journal_id')->where('t_destination.amount', '>', 0);
|
||||
}
|
||||
)
|
||||
->where('tags.tagMode', 'balancingAct')
|
||||
->where('transaction_types.type', TransactionType::TRANSFER)
|
||||
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
|
||||
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->whereIn('t_source.account_id', $ids)
|
||||
->whereIn('t_destination.account_id', $ids)
|
||||
->groupBy('t_destination.account_id')
|
||||
->get(
|
||||
[
|
||||
't_destination.account_id',
|
||||
DB::raw('SUM(t_destination.amount) AS sum'),
|
||||
]
|
||||
);
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param BudgetLimit $budgetLimit
|
||||
@@ -168,40 +113,6 @@ class BalanceReportHelper implements BalanceReportHelperInterface
|
||||
return $line;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BalanceLine $noBudgetLine
|
||||
* @param BalanceLine $coveredByTagLine
|
||||
*
|
||||
* @return BalanceLine
|
||||
*/
|
||||
private function createLeftUnbalancedLine(BalanceLine $noBudgetLine, BalanceLine $coveredByTagLine): BalanceLine
|
||||
{
|
||||
$line = new BalanceLine;
|
||||
$line->setRole(BalanceLine::ROLE_DIFFROLE);
|
||||
$noBudgetEntries = $noBudgetLine->getBalanceEntries();
|
||||
$tagEntries = $coveredByTagLine->getBalanceEntries();
|
||||
|
||||
foreach ($noBudgetEntries as $entry) {
|
||||
$account = $entry->getAccount();
|
||||
$tagEntry = $tagEntries->filter(
|
||||
function (BalanceEntry $current) use ($account) {
|
||||
return $current->getAccount()->id === $account->id;
|
||||
}
|
||||
);
|
||||
if ($tagEntry->first()) {
|
||||
// found corresponding entry. As we should:
|
||||
$newEntry = new BalanceEntry;
|
||||
$newEntry->setAccount($account);
|
||||
$spent = bcadd($tagEntry->first()->getLeft(), $entry->getSpent());
|
||||
$newEntry->setSpent($spent);
|
||||
$line->addBalanceEntry($newEntry);
|
||||
}
|
||||
}
|
||||
|
||||
return $line;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
@@ -227,41 +138,6 @@ class BalanceReportHelper implements BalanceReportHelperInterface
|
||||
return $empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return BalanceLine
|
||||
*/
|
||||
private function createTagsBalanceLine(Collection $accounts, Carbon $start, Carbon $end): BalanceLine
|
||||
{
|
||||
$tags = new BalanceLine;
|
||||
$tagsLeft = $this->allCoveredByBalancingActs($accounts, $start, $end);
|
||||
|
||||
$tags->setRole(BalanceLine::ROLE_TAGROLE);
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
$leftEntry = $tagsLeft->filter(
|
||||
function (Tag $tag) use ($account) {
|
||||
return $tag->account_id === $account->id;
|
||||
}
|
||||
);
|
||||
$left = '0';
|
||||
if (!is_null($leftEntry->first())) {
|
||||
$left = $leftEntry->first()->sum;
|
||||
}
|
||||
|
||||
// balanced by tags
|
||||
$tagEntry = new BalanceEntry;
|
||||
$tagEntry->setAccount($account);
|
||||
$tagEntry->setLeft($left);
|
||||
$tags->addBalanceEntry($tagEntry);
|
||||
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Balance $balance
|
||||
|
@@ -42,6 +42,7 @@ class BudgetReportHelper implements BudgetReportHelperInterface
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly 5.
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) // all the arrays make it long.
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
|
@@ -39,6 +39,7 @@ use View;
|
||||
* Class AccountController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*/
|
||||
class AccountController extends Controller
|
||||
{
|
||||
@@ -141,9 +142,14 @@ class AccountController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit an account.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Account $account
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // long and complex but not that excessively so.
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function edit(Request $request, Account $account)
|
||||
@@ -194,7 +200,7 @@ class AccountController extends Controller
|
||||
|
||||
return view(
|
||||
'accounts.edit', compact(
|
||||
'allCurrencies', 'currencySelectList', 'account', 'currency', 'subTitle', 'subTitleIcon', 'what', 'roles'
|
||||
'allCurrencies', 'currencySelectList', 'account', 'currency', 'subTitle', 'subTitleIcon', 'what', 'roles', 'preFilled'
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -237,12 +243,16 @@ class AccountController extends Controller
|
||||
|
||||
|
||||
/**
|
||||
* Show an account.
|
||||
* @param Request $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param Account $account
|
||||
* @param string $moment
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // long and complex but not that excessively so.
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*/
|
||||
public function show(Request $request, JournalRepositoryInterface $repository, Account $account, string $moment = '')
|
||||
{
|
||||
@@ -253,7 +263,7 @@ class AccountController extends Controller
|
||||
$currencyRepos = app(CurrencyRepositoryInterface::class);
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$chartUri = route('chart.account.single', [$account->id]);
|
||||
$start = null;
|
||||
@@ -274,55 +284,31 @@ class AccountController extends Controller
|
||||
if (strlen($moment) > 0 && $moment !== 'all') {
|
||||
$start = new Carbon($moment);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
$subTitle = trans(
|
||||
'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||
'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
$fStart = $start->formatLocalized($this->monthAndDayFormat);
|
||||
$fEnd = $end->formatLocalized($this->monthAndDayFormat);
|
||||
$subTitle = trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
|
||||
$chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d')]);
|
||||
$periods = $this->getPeriodOverview($account);
|
||||
}
|
||||
|
||||
// prep for current period
|
||||
// prep for current period view
|
||||
if (strlen($moment) === 0) {
|
||||
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
|
||||
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
|
||||
$subTitle = trans(
|
||||
'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||
'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
$fStart = $start->formatLocalized($this->monthAndDayFormat);
|
||||
$fEnd = $end->formatLocalized($this->monthAndDayFormat);
|
||||
$subTitle = trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
|
||||
$periods = $this->getPeriodOverview($account);
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$loop = 0;
|
||||
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||
Log::info('Now at loop start.');
|
||||
while ($count === 0 && $loop < 3) {
|
||||
$loop++;
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
Log::info('Count is zero, search for journals.');
|
||||
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
|
||||
if (!is_null($start)) {
|
||||
$collector->setRange($start, $end);
|
||||
}
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('accounts/show/' . $account->id . '/' . $moment);
|
||||
$count = $journals->getCollection()->count();
|
||||
if ($count === 0 && $loop < 3) {
|
||||
$start->subDay();
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
}
|
||||
// grab journals:
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
|
||||
if (!is_null($start)) {
|
||||
$collector->setRange($start, $end);
|
||||
}
|
||||
|
||||
if ($moment !== 'all' && $loop > 1) {
|
||||
$subTitle = trans(
|
||||
'firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||
'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath(route('accounts.show', [$account->id, $moment]));
|
||||
|
||||
return view(
|
||||
'accounts.show',
|
||||
@@ -413,6 +399,8 @@ class AccountController extends Controller
|
||||
* @param Account $account The account involved.
|
||||
*
|
||||
* @return Collection
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*/
|
||||
private function getPeriodOverview(Account $account): Collection
|
||||
{
|
||||
@@ -421,9 +409,9 @@ class AccountController extends Controller
|
||||
$start = $repository->oldestJournalDate($account);
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range, null);
|
||||
$entries = new Collection;
|
||||
|
||||
$count = 0;
|
||||
// properties for cache
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($start);
|
||||
@@ -436,24 +424,20 @@ class AccountController extends Controller
|
||||
}
|
||||
|
||||
Log::debug('Going to get period expenses and incomes.');
|
||||
while ($end >= $start) {
|
||||
while ($end >= $start && $count < 90) {
|
||||
$end = Navigation::startOfPeriod($end, $range);
|
||||
$currentEnd = Navigation::endOfPeriod($end, $range);
|
||||
|
||||
// try a collector for income:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)
|
||||
->setTypes([TransactionType::DEPOSIT])
|
||||
->withOpposingAccount();
|
||||
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)->setTypes([TransactionType::DEPOSIT])->withOpposingAccount();
|
||||
$earned = strval($collector->getJournals()->sum('transaction_amount'));
|
||||
|
||||
// try a collector for expenses:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)
|
||||
->setTypes([TransactionType::WITHDRAWAL])
|
||||
->withOpposingAccount();
|
||||
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)->setTypes([TransactionType::WITHDRAWAL])->withOpposingAccount();
|
||||
$spent = strval($collector->getJournals()->sum('transaction_amount'));
|
||||
$dateStr = $end->format('Y-m-d');
|
||||
$dateName = Navigation::periodShow($end, $range);
|
||||
@@ -466,7 +450,7 @@ class AccountController extends Controller
|
||||
'date' => clone $end]
|
||||
);
|
||||
$end = Navigation::subtractPeriod($end, $range, 1);
|
||||
|
||||
$count++;
|
||||
}
|
||||
$cache->store($entries);
|
||||
|
||||
|
@@ -14,7 +14,11 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Controllers\Admin;
|
||||
|
||||
|
||||
use FireflyIII\Events\AdminRequestedTestMessage;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Session;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class HomeController
|
||||
@@ -34,4 +38,18 @@ class HomeController extends Controller
|
||||
return view('admin.index', compact('title', 'mainTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function testMessage(Request $request)
|
||||
{
|
||||
$ipAddress = $request->ip();
|
||||
Log::debug(sprintf('Now in testMessage() controller. IP is %s', $ipAddress));
|
||||
event(new AdminRequestedTestMessage(auth()->user(), $ipAddress));
|
||||
Session::flash('info', strval(trans('firefly.send_test_triggered')));
|
||||
return redirect(route('admin.index'));
|
||||
}
|
||||
|
||||
}
|
||||
|
229
app/Http/Controllers/Admin/LinkController.php
Normal file
229
app/Http/Controllers/Admin/LinkController.php
Normal file
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/**
|
||||
* LinkController.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Admin;
|
||||
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\LinkTypeFormRequest;
|
||||
use FireflyIII\Models\LinkType;
|
||||
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Preferences;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* Class LinkController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Admin
|
||||
*/
|
||||
class LinkController extends Controller
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
View::share('title', strval(trans('firefly.administration')));
|
||||
View::share('mainTitleIcon', 'fa-hand-spock-o');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$subTitle = trans('firefly.create_new_link_type');
|
||||
$subTitleIcon = 'fa-link';
|
||||
|
||||
// put previous url in session if not redirect from store (not "create another").
|
||||
if (session('link_types.create.fromStore') !== true) {
|
||||
$this->rememberPreviousUri('link_types.create.uri');
|
||||
}
|
||||
|
||||
return view('admin.link.create', compact('subTitle', 'subTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
* @param LinkType $linkType
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||
*/
|
||||
public function delete(Request $request, LinkTypeRepositoryInterface $repository, LinkType $linkType)
|
||||
{
|
||||
if (!$linkType->editable) {
|
||||
$request->session()->flash('error', strval(trans('firefly.cannot_edit_link_type', ['name' => $linkType->name])));
|
||||
|
||||
return redirect(route('admin.links.index'));
|
||||
}
|
||||
|
||||
$subTitle = trans('firefly.delete_link_type', ['name' => $linkType->name]);
|
||||
$otherTypes = $repository->get();
|
||||
$count = $repository->countJournals($linkType);
|
||||
$moveTo = [];
|
||||
$moveTo[0] = trans('firefly.do_not_save_connection');
|
||||
/** @var LinkType $otherType */
|
||||
foreach ($otherTypes as $otherType) {
|
||||
if ($otherType->id !== $linkType->id) {
|
||||
$moveTo[$otherType->id] = sprintf('%s (%s / %s)', $otherType->name, $otherType->inward, $otherType->outward);
|
||||
}
|
||||
}
|
||||
// put previous url in session
|
||||
$this->rememberPreviousUri('link_types.delete.uri');
|
||||
|
||||
return view('admin.link.delete', compact('linkType', 'subTitle', 'moveTo', 'count'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
* @param LinkType $linkType
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function destroy(Request $request, LinkTypeRepositoryInterface $repository, LinkType $linkType)
|
||||
{
|
||||
$name = $linkType->name;
|
||||
$moveTo = $repository->find(intval($request->get('move_link_type_before_delete')));
|
||||
$repository->destroy($linkType, $moveTo);
|
||||
|
||||
$request->session()->flash('success', strval(trans('firefly.deleted_link_type', ['name' => $name])));
|
||||
Preferences::mark();
|
||||
|
||||
return redirect($this->getPreviousUri('link_types.delete.uri'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param LinkType $linkType
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
|
||||
*/
|
||||
public function edit(Request $request, LinkType $linkType)
|
||||
{
|
||||
if (!$linkType->editable) {
|
||||
$request->session()->flash('error', strval(trans('firefly.cannot_edit_link_type', ['name' => $linkType->name])));
|
||||
|
||||
return redirect(route('admin.links.index'));
|
||||
}
|
||||
$subTitle = trans('firefly.edit_link_type', ['name' => $linkType->name]);
|
||||
$subTitleIcon = 'fa-link';
|
||||
|
||||
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||
if (session('link_types.edit.fromUpdate') !== true) {
|
||||
$this->rememberPreviousUri('link_types.edit.uri');
|
||||
}
|
||||
$request->session()->forget('link_types.edit.fromUpdate');
|
||||
|
||||
return view('admin.link.edit', compact('subTitle', 'subTitleIcon', 'linkType'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function index(LinkTypeRepositoryInterface $repository)
|
||||
{
|
||||
$subTitle = trans('firefly.journal_link_configuration');
|
||||
$subTitleIcon = 'fa-link';
|
||||
$linkTypes = $repository->get();
|
||||
$linkTypes->each(
|
||||
function (LinkType $linkType) use ($repository) {
|
||||
$linkType->journalCount = $repository->countJournals($linkType);
|
||||
}
|
||||
);
|
||||
|
||||
return view('admin.link.index', compact('subTitle', 'subTitleIcon', 'linkTypes'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkType $linkType
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function show(LinkType $linkType)
|
||||
{
|
||||
$subTitle = trans('firefly.overview_for_link', ['name' => $linkType->name]);
|
||||
$subTitleIcon = 'fa-link';
|
||||
$links = $linkType->transactionJournalLinks()->get();
|
||||
|
||||
return view('admin.link.show', compact('subTitle', 'subTitleIcon', 'linkType', 'links'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTypeFormRequest $request
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
*
|
||||
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function store(LinkTypeFormRequest $request, LinkTypeRepositoryInterface $repository)
|
||||
{
|
||||
$data = [
|
||||
'name' => $request->string('name'),
|
||||
'inward' => $request->string('inward'),
|
||||
'outward' => $request->string('outward'),
|
||||
];
|
||||
$linkType = $repository->store($data);
|
||||
$request->session()->flash('success', strval(trans('firefly.stored_new_link_type', ['name' => $linkType->name])));
|
||||
|
||||
if (intval($request->get('create_another')) === 1) {
|
||||
// set value so create routine will not overwrite URL:
|
||||
$request->session()->put('link_types.create.fromStore', true);
|
||||
|
||||
return redirect(route('link_types.create', [$request->input('what')]))->withInput();
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect($this->getPreviousUri('link_types.create.uri'));
|
||||
}
|
||||
|
||||
public function update(LinkTypeFormRequest $request, LinkTypeRepositoryInterface $repository, LinkType $linkType)
|
||||
{
|
||||
if (!$linkType->editable) {
|
||||
$request->session()->flash('error', strval(trans('firefly.cannot_edit_link_type', ['name' => $linkType->name])));
|
||||
|
||||
return redirect(route('admin.links.index'));
|
||||
}
|
||||
|
||||
$data = [
|
||||
'name' => $request->string('name'),
|
||||
'inward' => $request->string('inward'),
|
||||
'outward' => $request->string('outward'),
|
||||
];
|
||||
$repository->update($linkType, $data);
|
||||
|
||||
$request->session()->flash('success', strval(trans('firefly.updated_link_type', ['name' => $linkType->name])));
|
||||
Preferences::mark();
|
||||
|
||||
if (intval($request->get('return_to_edit')) === 1) {
|
||||
// set value so edit routine will not overwrite URL:
|
||||
$request->session()->put('link_types.edit.fromUpdate', true);
|
||||
|
||||
return redirect(route('admin.links.edit', [$linkType->id]))->withInput(['return_to_edit' => 1]);
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect($this->getPreviousUri('link_types.edit.uri'));
|
||||
}
|
||||
|
||||
}
|
@@ -48,6 +48,32 @@ class UserController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function delete(User $user)
|
||||
{
|
||||
$subTitle = trans('firefly.delete_user', ['email' => $user->email]);
|
||||
|
||||
return view('admin.users.delete', compact('user', 'subTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @param UserRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function destroy(User $user, UserRepositoryInterface $repository)
|
||||
{
|
||||
$repository->destroy($user);
|
||||
Session::flash('success', strval(trans('firefly.user_deleted')));
|
||||
|
||||
return redirect(route('admin.users'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*
|
||||
@@ -64,9 +90,10 @@ class UserController extends Controller
|
||||
$subTitle = strval(trans('firefly.edit_user', ['email' => $user->email]));
|
||||
$subTitleIcon = 'fa-user-o';
|
||||
$codes = [
|
||||
'' => strval(trans('firefly.no_block_code')),
|
||||
'bounced' => strval(trans('firefly.block_code_bounced')),
|
||||
'expired' => strval(trans('firefly.block_code_expired')),
|
||||
'' => strval(trans('firefly.no_block_code')),
|
||||
'bounced' => strval(trans('firefly.block_code_bounced')),
|
||||
'expired' => strval(trans('firefly.block_code_expired')),
|
||||
'email_changed' => strval(trans('firefly.block_code_email_changed')),
|
||||
];
|
||||
|
||||
return view('admin.users.edit', compact('user', 'subTitle', 'subTitleIcon', 'codes'));
|
||||
@@ -143,6 +170,7 @@ class UserController extends Controller
|
||||
}
|
||||
|
||||
$repository->changeStatus($user, $data['blocked'], $data['blocked_code']);
|
||||
$repository->updateEmail($user, $data['email']);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.updated_user', ['email' => $user->email])));
|
||||
Preferences::mark();
|
||||
|
@@ -99,7 +99,6 @@ class AttachmentController extends Controller
|
||||
public function download(AttachmentRepositoryInterface $repository, Attachment $attachment)
|
||||
{
|
||||
|
||||
|
||||
if ($repository->exists($attachment)) {
|
||||
$content = $repository->getContent($attachment);
|
||||
$quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\'));
|
||||
|
@@ -1,31 +1,34 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* ForgotPasswordController.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Auth;
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||
use Illuminate\Http\Request;
|
||||
use Password;
|
||||
|
||||
/**
|
||||
* Class ForgotPasswordController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Auth
|
||||
*/
|
||||
class ForgotPasswordController extends Controller
|
||||
{
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Reset Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller is responsible for handling password reset emails and
|
||||
| includes a trait which assists in sending these notifications from
|
||||
| your application to your users. Feel free to explore this trait.
|
||||
|
|
||||
*/
|
||||
|
||||
use SendsPasswordResetEmails;
|
||||
|
||||
/**
|
||||
@@ -36,37 +39,6 @@ class ForgotPasswordController extends Controller
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a reset link to the given user.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @param UserRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function sendResetLinkEmail(Request $request, UserRepositoryInterface $repository)
|
||||
{
|
||||
$this->validate($request, ['email' => 'required|email']);
|
||||
|
||||
// verify if the user is not a demo user. If so, we give him back an error.
|
||||
$user = User::where('email', $request->get('email'))->first();
|
||||
|
||||
if (!is_null($user) && $repository->hasRole($user, 'demo')) {
|
||||
return back()->withErrors(['email' => trans('firefly.cannot_reset_demo_user')]);
|
||||
}
|
||||
|
||||
$response = $this->broker()->sendResetLink($request->only('email'));
|
||||
|
||||
if ($response === Password::RESET_LINK_SENT) {
|
||||
return back()->with('status', trans($response));
|
||||
}
|
||||
|
||||
// If an error was returned by the password broker, we will get this message
|
||||
// translated so we can notify a user of the problem. We'll redirect back
|
||||
// to where the users came from so they can attempt this process again.
|
||||
return back()->withErrors(['email' => trans($response)]); // @codeCoverageIgnore
|
||||
}
|
||||
}
|
||||
|
@@ -1,38 +1,49 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* LoginController.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Auth;
|
||||
|
||||
use Config;
|
||||
use FireflyConfig;
|
||||
use FireflyIII\Events\UserChangedEmail;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Cookie\CookieJar;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Http\Request;
|
||||
use Lang;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* Class LoginController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Auth
|
||||
*/
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Login Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller handles authenticating users for the application and
|
||||
| redirecting them to your home screen. The controller uses a trait
|
||||
| to conveniently provide its functionality to your applications.
|
||||
|
|
||||
*/
|
||||
|
||||
use AuthenticatesUsers;
|
||||
|
||||
/**
|
||||
* Where to redirect users after login.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectTo = '/home';
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
@@ -40,79 +51,11 @@ class LoginController extends Controller
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware('guest', ['except' => 'logout']);
|
||||
$this->middleware('guest')->except('logout');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a login request to the application.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
|
||||
*/
|
||||
public function login(Request $request)
|
||||
{
|
||||
$this->validateLogin($request);
|
||||
$lockedOut = $this->hasTooManyLoginAttempts($request);
|
||||
if ($lockedOut) {
|
||||
$this->fireLockoutEvent($request);
|
||||
|
||||
return $this->sendLockoutResponse($request);
|
||||
}
|
||||
|
||||
$credentials = $this->credentials($request);
|
||||
$credentials['blocked'] = 0; // must not be blocked.
|
||||
|
||||
if ($this->guard()->attempt($credentials, $request->has('remember'))) {
|
||||
return $this->sendLoginResponse($request);
|
||||
}
|
||||
|
||||
$errorMessage = $this->getBlockedError($credentials['email']);
|
||||
|
||||
if (!$lockedOut) {
|
||||
$this->incrementLoginAttempts($request);
|
||||
}
|
||||
|
||||
return $this->sendFailedLoginResponse($request, $errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param CookieJar $cookieJar
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function logout(Request $request, CookieJar $cookieJar)
|
||||
{
|
||||
if (intval(getenv('SANDSTORM')) === 1) {
|
||||
return view('error')->with('message', strval(trans('firefly.sandstorm_not_available')));
|
||||
}
|
||||
|
||||
$cookie = $cookieJar->forever('twoFactorAuthenticated', 'false');
|
||||
|
||||
$this->guard()->logout();
|
||||
|
||||
$request->session()->flush();
|
||||
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect('/')->withCookie($cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function redirectTo(): string
|
||||
{
|
||||
return route('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the application login form.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @param CookieJar $cookieJar
|
||||
* Show the application's login form.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
@@ -120,8 +63,9 @@ class LoginController extends Controller
|
||||
{
|
||||
// forget 2fa cookie:
|
||||
$cookie = $cookieJar->forever('twoFactorAuthenticated', 'false');
|
||||
|
||||
// is allowed to?
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data;
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
$allowRegistration = true;
|
||||
if ($singleUserMode === true && $userCount > 0) {
|
||||
@@ -133,59 +77,4 @@ class LoginController extends Controller
|
||||
|
||||
return view('auth.login', compact('allowRegistration', 'email', 'remember'))->withCookie($cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the failed login message.
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getFailedLoginMessage(string $message)
|
||||
{
|
||||
if (strlen($message) > 0) {
|
||||
return $message;
|
||||
}
|
||||
|
||||
return Lang::has('auth.failed') ? Lang::get('auth.failed') : 'These credentials do not match our records.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the failed login response instance.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $message
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
protected function sendFailedLoginResponse(Request $request, string $message)
|
||||
{
|
||||
return redirect()->back()
|
||||
->withInput($request->only($this->username(), 'remember'))
|
||||
->withErrors(
|
||||
[
|
||||
$this->username() => $this->getFailedLoginMessage($message),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getBlockedError(string $email): string
|
||||
{
|
||||
// check if user is blocked:
|
||||
$errorMessage = '';
|
||||
/** @var User $foundUser */
|
||||
$foundUser = User::where('email', $email)->where('blocked', 1)->first();
|
||||
if (!is_null($foundUser)) {
|
||||
// user exists, but is blocked:
|
||||
$code = strlen(strval($foundUser->blocked_code)) > 0 ? $foundUser->blocked_code : 'general_blocked';
|
||||
$errorMessage = strval(trans('firefly.' . $code . '_error', ['email' => $email]));
|
||||
}
|
||||
|
||||
return $errorMessage;
|
||||
}
|
||||
}
|
||||
|
@@ -1,88 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* PasswordController.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Auth;
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* Class PasswordController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Auth
|
||||
* @method getEmailSubject()
|
||||
* @method getSendResetLinkEmailSuccessResponse(string $response)
|
||||
* @method getSendResetLinkEmailFailureResponse(string $response)
|
||||
*/
|
||||
class PasswordController extends Controller
|
||||
{
|
||||
|
||||
use ResetsPasswords;
|
||||
|
||||
/**
|
||||
* Create a new password controller instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a reset link to the given user.
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 7 but ok
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function sendResetLinkEmail(Request $request)
|
||||
{
|
||||
$this->validate($request, ['email' => 'required|email']);
|
||||
|
||||
$user = User::whereEmail($request->get('email'))->first();
|
||||
$response = 'passwords.blocked';
|
||||
|
||||
if (is_null($user)) {
|
||||
$response = Password::INVALID_USER;
|
||||
}
|
||||
|
||||
if (!is_null($user) && intval($user->blocked) === 0) {
|
||||
$response = Password::sendResetLink(
|
||||
$request->only('email'), function (Message $message) {
|
||||
$message->subject($this->getEmailSubject());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
switch ($response) {
|
||||
case Password::RESET_LINK_SENT:
|
||||
return $this->getSendResetLinkEmailSuccessResponse($response);
|
||||
|
||||
case Password::INVALID_USER:
|
||||
case 'passwords.blocked':
|
||||
default:
|
||||
return $this->getSendResetLinkEmailFailureResponse($response);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,42 +1,44 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* RegisterController.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Auth;
|
||||
|
||||
use Auth;
|
||||
use Config;
|
||||
use FireflyConfig;
|
||||
use FireflyIII\Events\RegisteredUser;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Foundation\Auth\RegistersUsers;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Session;
|
||||
use Validator;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* Class RegisterController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Auth
|
||||
*/
|
||||
class RegisterController extends Controller
|
||||
{
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Register Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller handles the registration of new users as well as their
|
||||
| validation and creation. By default this controller uses a trait to
|
||||
| provide this functionality without requiring any additional code.
|
||||
|
|
||||
*/
|
||||
|
||||
use RegistersUsers;
|
||||
|
||||
/**
|
||||
* Where to redirect users after login / registration.
|
||||
* Where to redirect users after registration.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
@@ -44,6 +46,7 @@ class RegisterController extends Controller
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -52,14 +55,16 @@ class RegisterController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* Handle a registration request for the application.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function register(Request $request)
|
||||
{
|
||||
// is allowed to?
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data;
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
if ($singleUserMode === true && $userCount > 0) {
|
||||
$message = 'Registration is currently not available.';
|
||||
@@ -67,29 +72,19 @@ class RegisterController extends Controller
|
||||
return view('error', compact('message'));
|
||||
}
|
||||
|
||||
$this->validator($request->all())->validate();
|
||||
|
||||
$validator = $this->validator($request->all());
|
||||
event(new Registered($user = $this->create($request->all())));
|
||||
|
||||
if ($validator->fails()) {
|
||||
$this->throwValidationException($request, $validator);
|
||||
}
|
||||
|
||||
$user = $this->create($request->all());
|
||||
|
||||
// trigger user registration event:
|
||||
event(new RegisteredUser($user, $request->ip()));
|
||||
|
||||
Auth::login($user);
|
||||
$this->guard()->login($user);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.registered')));
|
||||
Session::flash('gaEventCategory', 'user');
|
||||
Session::flash('gaEventAction', 'new-registration');
|
||||
|
||||
return redirect($this->redirectPath());
|
||||
return $this->registered($request, $user)
|
||||
?: redirect($this->redirectPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* OLD
|
||||
* Show the application registration form.
|
||||
*
|
||||
* @param Request $request
|
||||
@@ -99,10 +94,10 @@ class RegisterController extends Controller
|
||||
public function showRegistrationForm(Request $request)
|
||||
{
|
||||
// is demo site?
|
||||
$isDemoSite = FireflyConfig::get('is_demo_site', Config::get('firefly.configuration.is_demo_site'))->data;
|
||||
$isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data;
|
||||
|
||||
// is allowed to?
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', Config::get('firefly.configuration.single_user_mode'))->data;
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
if ($singleUserMode === true && $userCount > 0) {
|
||||
$message = 'Registration is currently not available.';
|
||||
@@ -112,6 +107,7 @@ class RegisterController extends Controller
|
||||
|
||||
$email = $request->old('email');
|
||||
|
||||
|
||||
return view('auth.register', compact('isDemoSite', 'email'));
|
||||
}
|
||||
|
||||
@@ -120,19 +116,16 @@ class RegisterController extends Controller
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return User
|
||||
* @return \FireflyIII\User
|
||||
*/
|
||||
protected function create(array $data)
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = User::create(
|
||||
return User::create(
|
||||
[
|
||||
'email' => $data['email'],
|
||||
'password' => bcrypt($data['password']),
|
||||
]
|
||||
);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,8 +139,8 @@ class RegisterController extends Controller
|
||||
{
|
||||
return Validator::make(
|
||||
$data, [
|
||||
'email' => 'required|email|max:255|unique:users',
|
||||
'password' => 'required|min:6|confirmed',
|
||||
'email' => 'required|string|email|max:255|unique:users',
|
||||
'password' => 'required|string|secure_password|confirmed',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@@ -1,32 +1,43 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* ResetPasswordController.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Auth;
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* Class ResetPasswordController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Auth
|
||||
*/
|
||||
class ResetPasswordController extends Controller
|
||||
{
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Reset Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller is responsible for handling password reset requests
|
||||
| and uses a simple trait to include this behavior. You're free to
|
||||
| explore this trait and override any methods you wish to tweak.
|
||||
|
|
||||
*/
|
||||
|
||||
use ResetsPasswords;
|
||||
|
||||
/**
|
||||
* Where to redirect users after resetting their password.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectTo = '/home';
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
@@ -34,7 +45,6 @@ class ResetPasswordController extends Controller
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->middleware('guest');
|
||||
}
|
||||
}
|
||||
|
@@ -34,6 +34,8 @@ class TwoFactorController extends Controller
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
|
||||
* @throws FireflyException
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
|
@@ -13,6 +13,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Amount;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Http\Requests\BillFormRequest;
|
||||
@@ -129,6 +130,11 @@ class BillController extends Controller
|
||||
if (session('bills.edit.fromUpdate') !== true) {
|
||||
$this->rememberPreviousUri('bills.edit.uri');
|
||||
}
|
||||
|
||||
$currency = Amount::getDefaultCurrency();
|
||||
$bill->amount_min = round($bill->amount_min, $currency->decimal_places);
|
||||
$bill->amount_max = round($bill->amount_max, $currency->decimal_places);
|
||||
|
||||
$request->session()->forget('bills.edit.fromUpdate');
|
||||
$request->session()->flash('gaEventCategory', 'bills');
|
||||
$request->session()->flash('gaEventAction', 'edit');
|
||||
@@ -206,7 +212,7 @@ class BillController extends Controller
|
||||
/** @var Carbon $date */
|
||||
$date = session('start');
|
||||
$year = $date->year;
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$yearAverage = $repository->getYearAverage($bill, $date);
|
||||
$overallAverage = $repository->getOverallAverage($bill);
|
||||
@@ -217,7 +223,7 @@ class BillController extends Controller
|
||||
$collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->setLimit($pageSize)->setPage($page)->withBudgetInformation()
|
||||
->withCategoryInformation();
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('/bills/show/' . $bill->id);
|
||||
$journals->setPath(route('bills.show', [$bill->id]));
|
||||
|
||||
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, new Carbon);
|
||||
$hideBill = true;
|
||||
|
@@ -40,6 +40,7 @@ use View;
|
||||
* Class BudgetController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*/
|
||||
class BudgetController extends Controller
|
||||
{
|
||||
@@ -75,11 +76,9 @@ class BudgetController extends Controller
|
||||
*/
|
||||
public function amount(Request $request, Budget $budget)
|
||||
{
|
||||
$amount = intval($request->get('amount'));
|
||||
/** @var Carbon $start */
|
||||
$start = session('start', Carbon::now()->startOfMonth());
|
||||
/** @var Carbon $end */
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
$amount = intval($request->get('amount'));
|
||||
$start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
|
||||
$end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
|
||||
$budgetLimit = $this->repository->updateLimitAmount($budget, $start, $end, $amount);
|
||||
if ($amount === 0) {
|
||||
$budgetLimit = null;
|
||||
@@ -170,6 +169,9 @@ class BudgetController extends Controller
|
||||
* @param string|null $moment
|
||||
*
|
||||
* @return View
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) complex because of while loop
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*/
|
||||
public function index(string $moment = null)
|
||||
{
|
||||
@@ -184,7 +186,6 @@ class BudgetController extends Controller
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
} catch (Exception $e) {
|
||||
// start and end are already defined.
|
||||
|
||||
}
|
||||
}
|
||||
$next = clone $end;
|
||||
@@ -192,16 +193,12 @@ class BudgetController extends Controller
|
||||
$prev = clone $start;
|
||||
$prev->subDay();
|
||||
$prev = Navigation::startOfPeriod($prev, $range);
|
||||
|
||||
|
||||
$this->repository->cleanupBudgets();
|
||||
|
||||
|
||||
$budgets = $this->repository->getActiveBudgets();
|
||||
$inactive = $this->repository->getInactiveBudgets();
|
||||
$periodStart = $start->formatLocalized($this->monthAndDayFormat);
|
||||
$periodEnd = $end->formatLocalized($this->monthAndDayFormat);
|
||||
$budgetInformation = $this->collectBudgetInformation($budgets, $start, $end);
|
||||
$budgetInformation = $this->repository->collectBudgetInformation($budgets, $start, $end);
|
||||
$defaultCurrency = Amount::getDefaultCurrency();
|
||||
$available = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
|
||||
$spent = array_sum(array_column($budgetInformation, 'spent'));
|
||||
@@ -243,17 +240,85 @@ class BudgetController extends Controller
|
||||
compact(
|
||||
'available', 'currentMonth', 'next', 'nextText', 'prev', 'prevText',
|
||||
'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets',
|
||||
'spent', 'budgeted', 'previousLoop', 'nextLoop', 'start'
|
||||
'spent', 'budgeted', 'previousLoop', 'nextLoop', 'start', 'end'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function infoIncome(Carbon $start, Carbon $end)
|
||||
{
|
||||
// properties for cache
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('info-income');
|
||||
|
||||
if ($cache->has()) {
|
||||
$result = $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
if (!$cache->has()) {
|
||||
$result = [
|
||||
'available' => '0',
|
||||
'earned' => '0',
|
||||
'suggested' => '0',
|
||||
];
|
||||
$currency = Amount::getDefaultCurrency();
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$begin = Navigation::subtractPeriod($start, $range, 3);
|
||||
|
||||
// get average amount available.
|
||||
$total = '0';
|
||||
$count = 0;
|
||||
$currentStart = clone $begin;
|
||||
while ($currentStart < $start) {
|
||||
$currentEnd = Navigation::endOfPeriod($currentStart, $range);
|
||||
$total = bcadd($total, $this->repository->getAvailableBudget($currency, $currentStart, $currentEnd));
|
||||
$currentStart = Navigation::addPeriod($currentStart, $range, 0);
|
||||
$count++;
|
||||
}
|
||||
$result['available'] = bcdiv($total, strval($count));
|
||||
|
||||
// amount earned in this period:
|
||||
$subDay = clone $end;
|
||||
$subDay->subDay();
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($begin, $subDay)->setTypes([TransactionType::DEPOSIT])->withOpposingAccount();
|
||||
$result['earned'] = bcdiv(strval($collector->getJournals()->sum('transaction_amount')), strval($count));
|
||||
|
||||
// amount spent in period
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($begin, $subDay)->setTypes([TransactionType::WITHDRAWAL])->withOpposingAccount();
|
||||
$result['spent'] = bcdiv(strval($collector->getJournals()->sum('transaction_amount')), strval($count));
|
||||
// suggestion starts with the amount spent
|
||||
$result['suggested'] = bcmul($result['spent'], '-1');
|
||||
$result['suggested'] = bccomp($result['suggested'], $result['earned']) === 1 ? $result['earned'] : $result['suggested'];
|
||||
// unless it's more than you earned. So min() of suggested/earned
|
||||
|
||||
|
||||
$cache->store($result);
|
||||
}
|
||||
|
||||
|
||||
return view('budgets.info', compact('result', 'begin', 'currentEnd'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param string $moment
|
||||
*
|
||||
* @return View
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*/
|
||||
public function noBudget(Request $request, JournalRepositoryInterface $repository, string $moment = '')
|
||||
{
|
||||
@@ -293,37 +358,15 @@ class BudgetController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
|
||||
$count = 0;
|
||||
$loop = 0;
|
||||
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||
Log::info('Now at no-budget loop start.');
|
||||
while ($count === 0 && $loop < 3) {
|
||||
$loop++;
|
||||
Log::info(sprintf('Count is zero, search for journals between %s and %s.', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page)
|
||||
->withoutBudget()->withOpposingAccount();
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('/budgets/list/no-budget');
|
||||
$count = $journals->getCollection()->count();
|
||||
if ($count === 0 && $loop < 3) {
|
||||
$start->subDay();
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
|
||||
if ($moment !== 'all' && $loop > 1) {
|
||||
$subTitle = trans(
|
||||
'firefly.without_budget_between',
|
||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page)
|
||||
->withoutBudget()->withOpposingAccount();
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath(route('budgets.no-budget'));
|
||||
|
||||
return view('budgets.no-budget', compact('journals', 'subTitle', 'moment', 'periods', 'start', 'end'));
|
||||
}
|
||||
@@ -335,15 +378,15 @@ class BudgetController extends Controller
|
||||
*/
|
||||
public function postUpdateIncome(BudgetIncomeRequest $request)
|
||||
{
|
||||
$start = session('start', new Carbon);
|
||||
$end = session('end', new Carbon);
|
||||
$start = Carbon::createFromFormat('Y-m-d', $request->string('start'));
|
||||
$end = Carbon::createFromFormat('Y-m-d', $request->string('end'));
|
||||
$defaultCurrency = Amount::getDefaultCurrency();
|
||||
$amount = $request->get('amount');
|
||||
|
||||
$this->repository->setAvailableBudget($defaultCurrency, $start, $end, $amount);
|
||||
Preferences::mark();
|
||||
|
||||
return redirect(route('budgets.index'));
|
||||
return redirect(route('budgets.index', [$start->format('Y-m-d')]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,7 +400,7 @@ class BudgetController extends Controller
|
||||
/** @var Carbon $start */
|
||||
$start = session('first', Carbon::create()->startOfYear());
|
||||
$end = new Carbon;
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$limits = $this->getLimits($budget, $start, $end);
|
||||
$repetition = null;
|
||||
@@ -366,7 +409,7 @@ class BudgetController extends Controller
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page)->withCategoryInformation();
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('/budgets/show/' . $budget->id);
|
||||
$journals->setPath(route('budgets.show', [$budget->id]));
|
||||
|
||||
|
||||
$subTitle = trans('firefly.all_journals_for_budget', ['name' => $budget->name]);
|
||||
@@ -388,7 +431,7 @@ class BudgetController extends Controller
|
||||
throw new FireflyException('This budget limit is not part of this budget.');
|
||||
}
|
||||
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$subTitle = trans(
|
||||
'firefly.budget_in_period', [
|
||||
@@ -404,7 +447,7 @@ class BudgetController extends Controller
|
||||
$collector->setAllAssetAccounts()->setRange($budgetLimit->start_date, $budgetLimit->end_date)
|
||||
->setBudget($budget)->setLimit($pageSize)->setPage($page)->withCategoryInformation();
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('/budgets/show/' . $budget->id . '/' . $budgetLimit->id);
|
||||
$journals->setPath(route('budgets.show', [$budget->id, $budgetLimit->id]));
|
||||
|
||||
|
||||
$start = session('first', Carbon::create()->startOfYear());
|
||||
@@ -465,62 +508,20 @@ class BudgetController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return View
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function updateIncome()
|
||||
public function updateIncome(Carbon $start, Carbon $end)
|
||||
{
|
||||
$start = session('start', new Carbon);
|
||||
$end = session('end', new Carbon);
|
||||
$defaultCurrency = Amount::getDefaultCurrency();
|
||||
$available = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
|
||||
|
||||
$available = round($available, $defaultCurrency->decimal_places);
|
||||
|
||||
return view('budgets.income', compact('available', 'start', 'end'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $budgets
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function collectBudgetInformation(Collection $budgets, Carbon $start, Carbon $end): array
|
||||
{
|
||||
// get account information
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
|
||||
$return = [];
|
||||
/** @var Budget $budget */
|
||||
foreach ($budgets as $budget) {
|
||||
$budgetId = $budget->id;
|
||||
$return[$budgetId] = [
|
||||
'spent' => $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end),
|
||||
'budgeted' => '0',
|
||||
'currentRep' => false,
|
||||
];
|
||||
$budgetLimits = $this->repository->getBudgetLimits($budget, $start, $end);
|
||||
$otherLimits = new Collection;
|
||||
|
||||
// get all the budget limits relevant between start and end and examine them:
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($budgetLimits as $limit) {
|
||||
if ($limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end)
|
||||
) {
|
||||
$return[$budgetId]['currentLimit'] = $limit;
|
||||
$return[$budgetId]['budgeted'] = $limit->amount;
|
||||
continue;
|
||||
}
|
||||
// otherwise it's just one of the many relevant repetitions:
|
||||
$otherLimits->push($limit);
|
||||
}
|
||||
$return[$budgetId]['otherLimits'] = $otherLimits;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
* @param Carbon $start
|
||||
@@ -567,7 +568,7 @@ class BudgetController extends Controller
|
||||
$start = $first->date ?? new Carbon;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range, null);
|
||||
$entries = new Collection;
|
||||
|
||||
// properties for cache
|
||||
|
@@ -164,10 +164,12 @@ class CategoryController extends Controller
|
||||
public function noCategory(Request $request, JournalRepositoryInterface $repository, string $moment = '')
|
||||
{
|
||||
// default values:
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = null;
|
||||
$end = null;
|
||||
$periods = new Collection;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = null;
|
||||
$end = null;
|
||||
$periods = new Collection;
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
|
||||
// prep for "all" view.
|
||||
if ($moment === 'all') {
|
||||
@@ -199,37 +201,14 @@ class CategoryController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
|
||||
$count = 0;
|
||||
$loop = 0;
|
||||
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||
Log::info('Now at no-cat loop start.');
|
||||
while ($count === 0 && $loop < 3) {
|
||||
$loop++;
|
||||
Log::info('Count is zero, search for journals.');
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount();
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('/categories/list/no-category');
|
||||
$count = $journals->getCollection()->count();
|
||||
if ($count === 0 && $loop < 3) {
|
||||
$start->subDay();
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
|
||||
if ($moment !== 'all' && $loop > 1) {
|
||||
$subTitle = trans(
|
||||
'firefly.without_category_between',
|
||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount()
|
||||
->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]);
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath(route('categories.no-category'));
|
||||
|
||||
return view('categories.no-category', compact('journals', 'subTitle', 'moment', 'periods', 'start', 'end'));
|
||||
}
|
||||
@@ -247,20 +226,19 @@ class CategoryController extends Controller
|
||||
// default values:
|
||||
$subTitle = $category->name;
|
||||
$subTitleIcon = 'fa-bar-chart';
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$count = 0;
|
||||
$loop = 0;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = null;
|
||||
$end = null;
|
||||
$periods = new Collection;
|
||||
|
||||
|
||||
// prep for "all" view.
|
||||
if ($moment === 'all') {
|
||||
$subTitle = trans('firefly.all_journals_for_category', ['name' => $category->name]);
|
||||
$start = $repository->firstUseDate($category);
|
||||
$first = $repository->firstUseDate($category);
|
||||
/** @var Carbon $start */
|
||||
$start = is_null($first) ? new Carbon : $first;
|
||||
$end = new Carbon;
|
||||
}
|
||||
|
||||
@@ -278,7 +256,9 @@ class CategoryController extends Controller
|
||||
|
||||
// prep for current period
|
||||
if (strlen($moment) === 0) {
|
||||
/** @var Carbon $start */
|
||||
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
|
||||
/** @var Carbon $end */
|
||||
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
|
||||
$periods = $this->getPeriodOverview($category);
|
||||
$subTitle = trans(
|
||||
@@ -287,34 +267,15 @@ class CategoryController extends Controller
|
||||
'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||
Log::info('Now at category loop start.');
|
||||
while ($count === 0 && $loop < 3) {
|
||||
$loop++;
|
||||
Log::info('Count is zero, search for journals.');
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
|
||||
->setCategory($category)->withBudgetInformation()->withCategoryInformation();
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath('categories/show/' . $category->id);
|
||||
$count = $journals->getCollection()->count();
|
||||
if ($count === 0 && $loop < 3) {
|
||||
$start->subDay();
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
|
||||
if ($moment !== 'all' && $loop > 1) {
|
||||
$subTitle = trans(
|
||||
'firefly.journals_in_period_for_category',
|
||||
['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||
'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
|
||||
->setCategory($category)->withBudgetInformation()->withCategoryInformation();
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath(route('categories.show', [$category->id]));
|
||||
|
||||
|
||||
return view('categories.show', compact('category', 'moment', 'journals', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end'));
|
||||
}
|
||||
@@ -382,7 +343,7 @@ class CategoryController extends Controller
|
||||
$start = $first->date ?? new Carbon;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range, null);
|
||||
$entries = new Collection;
|
||||
|
||||
// properties for cache
|
||||
@@ -401,11 +362,11 @@ class CategoryController extends Controller
|
||||
$end = Navigation::startOfPeriod($end, $range);
|
||||
$currentEnd = Navigation::endOfPeriod($end, $range);
|
||||
|
||||
// count journals without budget in this period:
|
||||
// count journals without category in this period:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()
|
||||
->withOpposingAccount();
|
||||
->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]);
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$count = $collector->getJournals()->count();
|
||||
|
||||
@@ -463,13 +424,14 @@ class CategoryController extends Controller
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$first = $repository->firstUseDate($category);
|
||||
if ($first->year === 1900) {
|
||||
if (is_null($first)) {
|
||||
$first = new Carbon;
|
||||
}
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$first = Navigation::startOfPeriod($first, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range, null);
|
||||
$entries = new Collection;
|
||||
$count = 0;
|
||||
|
||||
// properties for entries with their amounts.
|
||||
$cache = new CacheProperties();
|
||||
@@ -481,7 +443,7 @@ class CategoryController extends Controller
|
||||
if ($cache->has()) {
|
||||
return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
while ($end >= $first) {
|
||||
while ($end >= $first && $count < 90) {
|
||||
$end = Navigation::startOfPeriod($end, $range);
|
||||
$currentEnd = Navigation::endOfPeriod($end, $range);
|
||||
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $end, $currentEnd);
|
||||
@@ -509,6 +471,7 @@ class CategoryController extends Controller
|
||||
]
|
||||
);
|
||||
$end = Navigation::subtractPeriod($end, $range, 1);
|
||||
$count++;
|
||||
}
|
||||
$cache->store($entries);
|
||||
|
||||
|
@@ -163,7 +163,7 @@ class BudgetController extends Controller
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function expenseAsset(Budget $budget, BudgetLimit $budgetLimit = null)
|
||||
public function expenseAsset(Budget $budget, ?BudgetLimit $budgetLimit)
|
||||
{
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($budget->id);
|
||||
@@ -208,7 +208,7 @@ class BudgetController extends Controller
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function expenseCategory(Budget $budget, BudgetLimit $budgetLimit = null)
|
||||
public function expenseCategory(Budget $budget, ?BudgetLimit $budgetLimit)
|
||||
{
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($budget->id);
|
||||
@@ -255,7 +255,7 @@ class BudgetController extends Controller
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function expenseExpense(Budget $budget, BudgetLimit $budgetLimit = null)
|
||||
public function expenseExpense(Budget $budget, ?BudgetLimit $budgetLimit)
|
||||
{
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($budget->id);
|
||||
|
@@ -67,7 +67,7 @@ class CategoryController extends Controller
|
||||
|
||||
$start = $repository->firstUseDate($category);
|
||||
|
||||
if ($start->year === 1900) {
|
||||
if (is_null($start)) {
|
||||
$start = new Carbon;
|
||||
}
|
||||
|
||||
|
@@ -44,6 +44,8 @@ class Controller extends BaseController
|
||||
protected $monthAndDayFormat;
|
||||
/** @var string */
|
||||
protected $monthFormat;
|
||||
/** @var string */
|
||||
protected $redirectUri = '/';
|
||||
|
||||
/**
|
||||
* Controller constructor.
|
||||
@@ -61,6 +63,7 @@ class Controller extends BaseController
|
||||
View::share('IS_DEMO_SITE', $isDemoSite);
|
||||
View::share('DEMO_USERNAME', env('DEMO_USERNAME', ''));
|
||||
View::share('DEMO_PASSWORD', env('DEMO_PASSWORD', ''));
|
||||
View::share('FF_VERSION', config('firefly.version'));
|
||||
|
||||
|
||||
$this->middleware(
|
||||
@@ -115,10 +118,10 @@ class Controller extends BaseController
|
||||
{
|
||||
$uri = strval(session($identifier));
|
||||
if (!(strpos($identifier, 'delete') === false) && !(strpos($uri, '/show/') === false)) {
|
||||
$uri = route('index');
|
||||
$uri = $this->redirectUri;
|
||||
}
|
||||
if (!(strpos($uri, 'javascript') === false)) {
|
||||
$uri = route('index');
|
||||
if (!(strpos($uri, 'jscript') === false)) {
|
||||
$uri = $this->redirectUri;
|
||||
}
|
||||
|
||||
return $uri;
|
||||
|
@@ -46,7 +46,7 @@ class ExportController extends Controller
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
View::share('mainTitleIcon', 'fa-file-archive-o');
|
||||
View::share('title', trans('firefly.export_data'));
|
||||
View::share('title', trans('firefly.export_and_backup_data'));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -137,13 +137,14 @@ class ExportController extends Controller
|
||||
public function postIndex(ExportFormRequest $request, AccountRepositoryInterface $repository, ExportJobRepositoryInterface $jobs)
|
||||
{
|
||||
$job = $jobs->findByKey($request->get('job'));
|
||||
$accounts = $request->get('accounts') ?? [];
|
||||
$settings = [
|
||||
'accounts' => $repository->getAccountsById($request->get('accounts')),
|
||||
'accounts' => $repository->getAccountsById($accounts),
|
||||
'startDate' => new Carbon($request->get('export_start_range')),
|
||||
'endDate' => new Carbon($request->get('export_end_range')),
|
||||
'exportFormat' => $request->get('exportFormat'),
|
||||
'includeAttachments' => intval($request->get('include_attachments')) === 1,
|
||||
'includeOldUploads' => intval($request->get('include_old_uploads')) === 1,
|
||||
'includeAttachments' => $request->boolean('include_attachments'),
|
||||
'includeOldUploads' => $request->boolean('include_old_uploads'),
|
||||
'job' => $job,
|
||||
];
|
||||
|
||||
@@ -159,12 +160,14 @@ class ExportController extends Controller
|
||||
$jobs->changeStatus($job, 'export_status_collecting_journals');
|
||||
$processor->collectJournals();
|
||||
$jobs->changeStatus($job, 'export_status_collected_journals');
|
||||
|
||||
/*
|
||||
* Transform to exportable entries:
|
||||
*/
|
||||
$jobs->changeStatus($job, 'export_status_converting_to_export_format');
|
||||
$processor->convertJournals();
|
||||
$jobs->changeStatus($job, 'export_status_converted_to_export_format');
|
||||
|
||||
/*
|
||||
* Transform to (temporary) file:
|
||||
*/
|
||||
@@ -180,6 +183,7 @@ class ExportController extends Controller
|
||||
$jobs->changeStatus($job, 'export_status_collected_attachments');
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Collect old uploads
|
||||
*/
|
||||
|
@@ -82,6 +82,14 @@ class HomeController extends Controller
|
||||
*/
|
||||
public function displayError()
|
||||
{
|
||||
Log::debug('This is a test message at the DEBUG level.');
|
||||
Log::info('This is a test message at the INFO level.');
|
||||
Log::notice('This is a test message at the NOTICE level.');
|
||||
Log::warning('This is a test message at the WARNING level.');
|
||||
Log::error('This is a test message at the ERROR level.');
|
||||
Log::critical('This is a test message at the CRITICAL level.');
|
||||
Log::alert('This is a test message at the ALERT level.');
|
||||
Log::emergency('This is a test message at the EMERGENCY level.');
|
||||
throw new FireflyException('A very simple test error.');
|
||||
}
|
||||
|
||||
@@ -121,9 +129,9 @@ class HomeController extends Controller
|
||||
/** @var Carbon $start */
|
||||
$start = session('start', Carbon::now()->startOfMonth());
|
||||
/** @var Carbon $end */
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
$accounts = $repository->getAccountsById($frontPage->data);
|
||||
$showDepositsFrontpage = Preferences::get('showDepositsFrontpage', false)->data;
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
$accounts = $repository->getAccountsById($frontPage->data);
|
||||
$showDeps = Preferences::get('showDepositsFrontpage', false)->data;
|
||||
|
||||
// zero bills? Hide some elements from view.
|
||||
/** @var BillRepositoryInterface $billRepository */
|
||||
@@ -138,7 +146,7 @@ class HomeController extends Controller
|
||||
}
|
||||
|
||||
return view(
|
||||
'index', compact('count', 'title', 'subTitle', 'mainTitleIcon', 'transactions', 'showDepositsFrontpage', 'billCount')
|
||||
'index', compact('count', 'subTitle', 'transactions', 'showDeps', 'billCount')
|
||||
);
|
||||
}
|
||||
|
||||
|
153
app/Http/Controllers/Import/BankController.php
Normal file
153
app/Http/Controllers/Import/BankController.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
/**
|
||||
* BankController.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Import;
|
||||
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Support\Import\Information\InformationInterface;
|
||||
use FireflyIII\Support\Import\Prerequisites\PrerequisitesInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Log;
|
||||
use Session;
|
||||
|
||||
class BankController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* This method must ask the user all parameters necessary to start importing data. This may not be enough
|
||||
* to finish the import itself (ie. mapping) but it should be enough to begin: accounts to import from,
|
||||
* accounts to import into, data ranges, etc.
|
||||
*
|
||||
* @param string $bank
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
|
||||
*/
|
||||
public function form(string $bank)
|
||||
{
|
||||
$class = config(sprintf('firefly.import_pre.%s', $bank));
|
||||
/** @var PrerequisitesInterface $object */
|
||||
$object = app($class);
|
||||
$object->setUser(auth()->user());
|
||||
|
||||
if ($object->hasPrerequisites()) {
|
||||
return redirect(route('import.bank.prerequisites', [$bank]));
|
||||
}
|
||||
$class = config(sprintf('firefly.import_info.%s', $bank));
|
||||
/** @var InformationInterface $object */
|
||||
$object = app($class);
|
||||
$object->setUser(auth()->user());
|
||||
$remoteAccounts = $object->getAccounts();
|
||||
|
||||
return view('import.bank.form', compact('remoteAccounts', 'bank'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* With the information given in the submitted form Firefly III will call upon the bank's classes to return transaction
|
||||
* information as requested. The user will be able to map unknown data and continue. Or maybe, it's put into some kind of
|
||||
* fake CSV file and forwarded to the import routine.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param string $bank
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|null
|
||||
*/
|
||||
public function postForm(Request $request, string $bank)
|
||||
{
|
||||
|
||||
$class = config(sprintf('firefly.import_pre.%s', $bank));
|
||||
/** @var PrerequisitesInterface $object */
|
||||
$object = app($class);
|
||||
$object->setUser(auth()->user());
|
||||
|
||||
if ($object->hasPrerequisites()) {
|
||||
return redirect(route('import.bank.prerequisites', [$bank]));
|
||||
}
|
||||
$remoteAccounts = $request->get('do_import');
|
||||
if (!is_array($remoteAccounts) || count($remoteAccounts) === 0) {
|
||||
Session::flash('error', 'Must select accounts');
|
||||
|
||||
return redirect(route('import.bank.form', [$bank]));
|
||||
}
|
||||
$remoteAccounts = array_keys($remoteAccounts);
|
||||
$class = config(sprintf('firefly.import_pre.%s', $bank));
|
||||
// get import file
|
||||
|
||||
// get import config
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method processes the prerequisites the user has entered in the previous step.
|
||||
*
|
||||
* Whatever storePrerequisites does, it should make sure that the system is ready to continue immediately. So
|
||||
* no extra calls or stuff, except maybe to open a session
|
||||
*
|
||||
* @see PrerequisitesInterface::storePrerequisites
|
||||
*
|
||||
* @param Request $request
|
||||
* @param string $bank
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function postPrerequisites(Request $request, string $bank)
|
||||
{
|
||||
Log::debug(sprintf('Now in postPrerequisites for %s', $bank));
|
||||
$class = config(sprintf('firefly.import_pre.%s', $bank));
|
||||
/** @var PrerequisitesInterface $object */
|
||||
$object = app($class);
|
||||
$object->setUser(auth()->user());
|
||||
if (!$object->hasPrerequisites()) {
|
||||
Log::debug(sprintf('No more prerequisites for %s, move to form.', $bank));
|
||||
|
||||
return redirect(route('import.bank.form', [$bank]));
|
||||
}
|
||||
Log::debug('Going to store entered preprerequisites.');
|
||||
// store post data
|
||||
$result = $object->storePrerequisites($request);
|
||||
|
||||
if ($result->count() > 0) {
|
||||
Session::flash('error', $result->first());
|
||||
|
||||
return redirect(route('import.bank.prerequisites', [$bank]));
|
||||
}
|
||||
|
||||
return redirect(route('import.bank.form', [$bank]));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method shows you, if necessary, a form that allows you to enter any required values, such as API keys,
|
||||
* login passwords or other values.
|
||||
*
|
||||
* @param string $bank
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
|
||||
*/
|
||||
public function prerequisites(string $bank)
|
||||
{
|
||||
$class = config(sprintf('firefly.import_pre.%s', $bank));
|
||||
/** @var PrerequisitesInterface $object */
|
||||
$object = app($class);
|
||||
$object->setUser(auth()->user());
|
||||
|
||||
if ($object->hasPrerequisites()) {
|
||||
$view = $object->getView();
|
||||
$parameters = $object->getViewParameters();
|
||||
|
||||
return view($view, $parameters);
|
||||
}
|
||||
|
||||
return redirect(route('import.bank.form', [$bank]));
|
||||
}
|
||||
|
||||
}
|
306
app/Http/Controllers/Import/FileController.php
Normal file
306
app/Http/Controllers/Import/FileController.php
Normal file
@@ -0,0 +1,306 @@
|
||||
<?php
|
||||
/**
|
||||
* FileController.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Import;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\ImportUploadRequest;
|
||||
use FireflyIII\Import\Configurator\ConfiguratorInterface;
|
||||
use FireflyIII\Import\Routine\ImportRoutine;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response as LaravelResponse;
|
||||
use Log;
|
||||
use Response;
|
||||
use Session;
|
||||
use View;
|
||||
|
||||
|
||||
/**
|
||||
* Class FileController.
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Import
|
||||
*/
|
||||
class FileController extends Controller
|
||||
{
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
public $repository;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
View::share('mainTitleIcon', 'fa-archive');
|
||||
View::share('title', trans('firefly.import_index_title'));
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is step 3. This repeats until the job is configured.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return View
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function configure(ImportJob $job)
|
||||
{
|
||||
// create configuration class:
|
||||
$configurator = $this->makeConfigurator($job);
|
||||
|
||||
// is the job already configured?
|
||||
if ($configurator->isJobConfigured()) {
|
||||
$this->repository->updateStatus($job, 'configured');
|
||||
|
||||
return redirect(route('import.file.status', [$job->key]));
|
||||
}
|
||||
$view = $configurator->getNextView();
|
||||
$data = $configurator->getNextData();
|
||||
$subTitle = trans('firefly.import_config_bread_crumb');
|
||||
$subTitleIcon = 'fa-wrench';
|
||||
|
||||
return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a JSON file of the job's configuration and send it to the user.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return LaravelResponse
|
||||
*/
|
||||
public function download(ImportJob $job)
|
||||
{
|
||||
Log::debug('Now in download()', ['job' => $job->key]);
|
||||
$config = $job->configuration;
|
||||
|
||||
// This is CSV import specific:
|
||||
$config['column-roles-complete'] = false;
|
||||
$config['column-mapping-complete'] = false;
|
||||
$config['initial-config-complete'] = false;
|
||||
$config['delimiter'] = $config['delimiter'] === "\t" ? 'tab' : $config['delimiter'];
|
||||
|
||||
$result = json_encode($config, JSON_PRETTY_PRINT);
|
||||
$name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\'));
|
||||
|
||||
/** @var LaravelResponse $response */
|
||||
$response = response($result, 200);
|
||||
$response->header('Content-disposition', 'attachment; filename=' . $name)
|
||||
->header('Content-Type', 'application/json')
|
||||
->header('Content-Description', 'File Transfer')
|
||||
->header('Connection', 'Keep-Alive')
|
||||
->header('Expires', '0')
|
||||
->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
|
||||
->header('Pragma', 'public')
|
||||
->header('Content-Length', strlen($result));
|
||||
|
||||
return $response;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This is step 1. Upload a file.
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$subTitle = trans('firefly.import_index_sub_title');
|
||||
$subTitleIcon = 'fa-home';
|
||||
$importFileTypes = [];
|
||||
$defaultImportType = config('firefly.default_import_format');
|
||||
|
||||
foreach (array_keys(config('firefly.import_formats')) as $type) {
|
||||
$importFileTypes[$type] = trans('firefly.import_file_type_' . $type);
|
||||
}
|
||||
|
||||
return view('import.file.index', compact('subTitle', 'subTitleIcon', 'importFileTypes', 'defaultImportType'));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is step 2. It creates an Import Job. Stores the import.
|
||||
*
|
||||
* @param ImportUploadRequest $request
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function initialize(ImportUploadRequest $request)
|
||||
{
|
||||
Log::debug('Now in initialize()');
|
||||
|
||||
// create import job:
|
||||
$type = $request->get('import_file_type');
|
||||
$job = $this->repository->create($type);
|
||||
Log::debug('Created new job', ['key' => $job->key, 'id' => $job->id]);
|
||||
|
||||
// process file:
|
||||
$this->repository->processFile($job, $request->files->get('import_file'));
|
||||
|
||||
// process config, if present:
|
||||
if ($request->files->has('configuration_file')) {
|
||||
$this->repository->processConfiguration($job, $request->files->get('configuration_file'));
|
||||
}
|
||||
|
||||
$this->repository->updateStatus($job, 'initialized');
|
||||
|
||||
return redirect(route('import.file.configure', [$job->key]));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Show status of import job in JSON.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function json(ImportJob $job)
|
||||
{
|
||||
$result = [
|
||||
'started' => false,
|
||||
'finished' => false,
|
||||
'running' => false,
|
||||
'errors' => array_values($job->extended_status['errors']),
|
||||
'percentage' => 0,
|
||||
'show_percentage' => false,
|
||||
'steps' => $job->extended_status['steps'],
|
||||
'done' => $job->extended_status['done'],
|
||||
'statusText' => trans('firefly.import_status_job_' . $job->status),
|
||||
'status' => $job->status,
|
||||
'finishedText' => '',
|
||||
];
|
||||
|
||||
if ($job->extended_status['steps'] !== 0) {
|
||||
$result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0);
|
||||
$result['show_percentage'] = true;
|
||||
}
|
||||
|
||||
if ($job->status === 'finished') {
|
||||
$tagId = $job->extended_status['tag'];
|
||||
/** @var TagRepositoryInterface $repository */
|
||||
$repository = app(TagRepositoryInterface::class);
|
||||
$tag = $repository->find($tagId);
|
||||
$result['finished'] = true;
|
||||
$result['finishedText'] = trans('firefly.import_status_finished_job', ['link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]);
|
||||
}
|
||||
|
||||
if ($job->status === 'running') {
|
||||
$result['started'] = true;
|
||||
$result['running'] = true;
|
||||
}
|
||||
|
||||
return Response::json($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 4. Save the configuration.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function postConfigure(Request $request, ImportJob $job)
|
||||
{
|
||||
Log::debug('Now in postConfigure()', ['job' => $job->key]);
|
||||
$configurator = $this->makeConfigurator($job);
|
||||
|
||||
// is the job already configured?
|
||||
if ($configurator->isJobConfigured()) {
|
||||
return redirect(route('import.file.status', [$job->key]));
|
||||
}
|
||||
$data = $request->all();
|
||||
$configurator->configureJob($data);
|
||||
|
||||
// get possible warning from configurator:
|
||||
$warning = $configurator->getWarningMessage();
|
||||
|
||||
if (strlen($warning) > 0) {
|
||||
Session::flash('warning', $warning);
|
||||
}
|
||||
|
||||
// return to configure
|
||||
return redirect(route('import.file.configure', [$job->key]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function start(ImportJob $job)
|
||||
{
|
||||
/** @var ImportRoutine $routine */
|
||||
$routine = app(ImportRoutine::class);
|
||||
$routine->setJob($job);
|
||||
$result = $routine->run();
|
||||
if ($result) {
|
||||
return Response::json(['run' => 'ok']);
|
||||
}
|
||||
|
||||
throw new FireflyException('Job did not complete succesfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||
*/
|
||||
public function status(ImportJob $job)
|
||||
{
|
||||
$statuses = ['configured', 'running', 'finished'];
|
||||
if (!in_array($job->status, $statuses)) {
|
||||
return redirect(route('import.file.configure', [$job->key]));
|
||||
}
|
||||
$subTitle = trans('firefly.import_status_sub_title');
|
||||
$subTitleIcon = 'fa-star';
|
||||
|
||||
return view('import.file.status', compact('job', 'subTitle', 'subTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return ConfiguratorInterface
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function makeConfigurator(ImportJob $job): ConfiguratorInterface
|
||||
{
|
||||
$type = $job->file_type;
|
||||
$key = sprintf('firefly.import_configurators.%s', $type);
|
||||
$className = config($key);
|
||||
if (is_null($className)) {
|
||||
throw new FireflyException('Cannot find configurator class for this job.'); // @codeCoverageIgnore
|
||||
}
|
||||
/** @var ConfiguratorInterface $configurator */
|
||||
$configurator = app($className);
|
||||
$configurator->setJob($job);
|
||||
|
||||
|
||||
return $configurator;
|
||||
}
|
||||
}
|
@@ -12,17 +12,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Requests\ImportUploadRequest;
|
||||
use FireflyIII\Import\Configurator\ConfiguratorInterface;
|
||||
use FireflyIII\Import\Routine\ImportRoutine;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response as LaravelResponse;
|
||||
use Log;
|
||||
use Response;
|
||||
use View;
|
||||
|
||||
/**
|
||||
@@ -53,72 +43,9 @@ class ImportController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is step 3. This repeats until the job is configured.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return View
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function configure(ImportJob $job)
|
||||
{
|
||||
// create configuration class:
|
||||
$configurator = $this->makeConfigurator($job);
|
||||
|
||||
// is the job already configured?
|
||||
if ($configurator->isJobConfigured()) {
|
||||
$this->repository->updateStatus($job, 'configured');
|
||||
|
||||
return redirect(route('import.status', [$job->key]));
|
||||
}
|
||||
$view = $configurator->getNextView();
|
||||
$data = $configurator->getNextData();
|
||||
$subTitle = trans('firefly.import_config_bread_crumb');
|
||||
$subTitleIcon = 'fa-wrench';
|
||||
|
||||
return view($view, compact('data', 'job', 'subTitle', 'subTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a JSON file of the job's configuration and send it to the user.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return LaravelResponse
|
||||
*/
|
||||
public function download(ImportJob $job)
|
||||
{
|
||||
Log::debug('Now in download()', ['job' => $job->key]);
|
||||
$config = $job->configuration;
|
||||
|
||||
// This is CSV import specific:
|
||||
$config['column-roles-complete'] = false;
|
||||
$config['column-mapping-complete'] = false;
|
||||
$config['initial-config-complete'] = false;
|
||||
$config['delimiter'] = $config['delimiter'] === "\t" ? 'tab' : $config['delimiter'];
|
||||
|
||||
$result = json_encode($config, JSON_PRETTY_PRINT);
|
||||
$name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\'));
|
||||
|
||||
/** @var LaravelResponse $response */
|
||||
$response = response($result, 200);
|
||||
$response->header('Content-disposition', 'attachment; filename=' . $name)
|
||||
->header('Content-Type', 'application/json')
|
||||
->header('Content-Description', 'File Transfer')
|
||||
->header('Connection', 'Keep-Alive')
|
||||
->header('Expires', '0')
|
||||
->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
|
||||
->header('Pragma', 'public')
|
||||
->header('Content-Length', strlen($result));
|
||||
|
||||
return $response;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This is step 1. Upload a file.
|
||||
* General import index
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
@@ -136,160 +63,4 @@ class ImportController extends Controller
|
||||
return view('import.index', compact('subTitle', 'subTitleIcon', 'importFileTypes', 'defaultImportType'));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is step 2. It creates an Import Job. Stores the import.
|
||||
*
|
||||
* @param ImportUploadRequest $request
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function initialize(ImportUploadRequest $request)
|
||||
{
|
||||
Log::debug('Now in initialize()');
|
||||
|
||||
// create import job:
|
||||
$type = $request->get('import_file_type');
|
||||
$job = $this->repository->create($type);
|
||||
Log::debug('Created new job', ['key' => $job->key, 'id' => $job->id]);
|
||||
|
||||
// process file:
|
||||
$this->repository->processFile($job, $request->files->get('import_file'));
|
||||
|
||||
// process config, if present:
|
||||
if ($request->files->has('configuration_file')) {
|
||||
$this->repository->processConfiguration($job, $request->files->get('configuration_file'));
|
||||
}
|
||||
|
||||
$this->repository->updateStatus($job, 'initialized');
|
||||
|
||||
return redirect(route('import.configure', [$job->key]));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Show status of import job in JSON.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function json(ImportJob $job)
|
||||
{
|
||||
$result = [
|
||||
'started' => false,
|
||||
'finished' => false,
|
||||
'running' => false,
|
||||
'errors' => array_values($job->extended_status['errors']),
|
||||
'percentage' => 0,
|
||||
'show_percentage' => false,
|
||||
'steps' => $job->extended_status['steps'],
|
||||
'done' => $job->extended_status['done'],
|
||||
'statusText' => trans('firefly.import_status_job_' . $job->status),
|
||||
'status' => $job->status,
|
||||
'finishedText' => '',
|
||||
];
|
||||
|
||||
if ($job->extended_status['steps'] !== 0) {
|
||||
$result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0);
|
||||
$result['show_percentage'] = true;
|
||||
}
|
||||
|
||||
if ($job->status === 'finished') {
|
||||
$tagId = $job->extended_status['tag'];
|
||||
/** @var TagRepositoryInterface $repository */
|
||||
$repository = app(TagRepositoryInterface::class);
|
||||
$tag = $repository->find($tagId);
|
||||
$result['finished'] = true;
|
||||
$result['finishedText'] = trans('firefly.import_status_finished_job', ['link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]);
|
||||
}
|
||||
|
||||
if ($job->status === 'running') {
|
||||
$result['started'] = true;
|
||||
$result['running'] = true;
|
||||
}
|
||||
|
||||
return Response::json($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 4. Save the configuration.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function postConfigure(Request $request, ImportJob $job)
|
||||
{
|
||||
Log::debug('Now in postConfigure()', ['job' => $job->key]);
|
||||
$configurator = $this->makeConfigurator($job);
|
||||
|
||||
// is the job already configured?
|
||||
if ($configurator->isJobConfigured()) {
|
||||
return redirect(route('import.status', [$job->key]));
|
||||
}
|
||||
$data = $request->all();
|
||||
$configurator->configureJob($data);
|
||||
|
||||
// return to configure
|
||||
return redirect(route('import.configure', [$job->key]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function start(ImportJob $job)
|
||||
{
|
||||
/** @var ImportRoutine $routine */
|
||||
$routine = app(ImportRoutine::class);
|
||||
$routine->setJob($job);
|
||||
$result = $routine->run();
|
||||
if ($result) {
|
||||
return Response::json(['run' => 'ok']);
|
||||
}
|
||||
|
||||
throw new FireflyException('Job did not complete succesfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||
*/
|
||||
public function status(ImportJob $job)
|
||||
{
|
||||
$statuses = ['configured', 'running', 'finished'];
|
||||
if (!in_array($job->status, $statuses)) {
|
||||
return redirect(route('import.configure', [$job->key]));
|
||||
}
|
||||
$subTitle = trans('firefly.import_status_sub_title');
|
||||
$subTitleIcon = 'fa-star';
|
||||
|
||||
return view('import.status', compact('job', 'subTitle', 'subTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return ConfiguratorInterface
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function makeConfigurator(ImportJob $job): ConfiguratorInterface
|
||||
{
|
||||
$type = $job->file_type;
|
||||
$key = sprintf('firefly.import_configurators.%s', $type);
|
||||
$className = config($key);
|
||||
if (is_null($className)) {
|
||||
throw new FireflyException('Cannot find configurator class for this job.'); // @codeCoverageIgnore
|
||||
}
|
||||
/** @var ConfiguratorInterface $configurator */
|
||||
$configurator = app($className);
|
||||
$configurator->setJob($job);
|
||||
|
||||
|
||||
return $configurator;
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Amount;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
@@ -19,9 +20,9 @@ use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Log;
|
||||
use Navigation;
|
||||
use Preferences;
|
||||
use Session;
|
||||
|
||||
/**
|
||||
* Class JavascriptController
|
||||
@@ -88,11 +89,6 @@ class JavascriptController extends Controller
|
||||
*/
|
||||
public function variables(Request $request)
|
||||
{
|
||||
$picker = $this->getDateRangePicker();
|
||||
$start = Session::get('start');
|
||||
$end = Session::get('end');
|
||||
$linkTitle = sprintf('%s - %s', $start->formatLocalized($this->monthAndDayFormat), $end->formatLocalized($this->monthAndDayFormat));
|
||||
$firstDate = session('first')->format('Y-m-d');
|
||||
$localeconv = localeconv();
|
||||
$accounting = Amount::getJsConfig($localeconv);
|
||||
$localeconv = localeconv();
|
||||
@@ -100,15 +96,16 @@ class JavascriptController extends Controller
|
||||
$localeconv['frac_digits'] = $defaultCurrency->decimal_places;
|
||||
$pref = Preferences::get('language', config('firefly.default_language', 'en_US'));
|
||||
$lang = $pref->data;
|
||||
$data = [
|
||||
'picker' => $picker,
|
||||
'linkTitle' => $linkTitle,
|
||||
'firstDate' => $firstDate,
|
||||
'currencyCode' => Amount::getCurrencyCode(),
|
||||
'currencySymbol' => Amount::getCurrencySymbol(),
|
||||
'accounting' => $accounting,
|
||||
'localeconv' => $localeconv,
|
||||
'language' => $lang,
|
||||
$dateRange = $this->getDateRangeConfig();
|
||||
|
||||
$data = [
|
||||
'currencyCode' => Amount::getCurrencyCode(),
|
||||
'currencySymbol' => Amount::getCurrencySymbol(),
|
||||
'accounting' => $accounting,
|
||||
'localeconv' => $localeconv,
|
||||
'language' => $lang,
|
||||
'dateRangeTitle' => $dateRange['title'],
|
||||
'dateRangeConfig' => $dateRange['configuration'],
|
||||
];
|
||||
$request->session()->keep(['two-factor-secret']);
|
||||
|
||||
@@ -119,40 +116,73 @@ class JavascriptController extends Controller
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getDateRangePicker(): array
|
||||
private function getDateRangeConfig(): array
|
||||
{
|
||||
$viewRange = Preferences::get('viewRange', '1M')->data;
|
||||
$start = Session::get('start');
|
||||
$end = Session::get('end');
|
||||
$start = session('start');
|
||||
$end = session('end');
|
||||
$first = session('first');
|
||||
$title = sprintf('%s - %s', $start->formatLocalized($this->monthAndDayFormat), $end->formatLocalized($this->monthAndDayFormat));
|
||||
$isCustom = session('is_custom_range');
|
||||
$ranges = [
|
||||
// first range is the current range:
|
||||
$title => [$start, $end],
|
||||
];
|
||||
Log::debug(sprintf('viewRange is %s', $viewRange));
|
||||
|
||||
$prevStart = clone $start;
|
||||
$prevEnd = clone $start;
|
||||
$nextStart = clone $end;
|
||||
$nextEnd = clone $end;
|
||||
if ($viewRange === 'custom') {
|
||||
$days = $start->diffInDays($end);
|
||||
$prevStart->subDays($days);
|
||||
$nextEnd->addDays($days);
|
||||
unset($days);
|
||||
// get the format for the ranges:
|
||||
$format = $this->getFormatByRange($viewRange);
|
||||
|
||||
// when current range is a custom range, add the current period as the next range.
|
||||
if ($isCustom) {
|
||||
Log::debug('Custom is true.');
|
||||
$index = $start->formatLocalized($format);
|
||||
$customPeriodStart = Navigation::startOfPeriod($start, $viewRange);
|
||||
$customPeriodEnd = Navigation::endOfPeriod($customPeriodStart, $viewRange);
|
||||
$ranges[$index] = [$customPeriodStart, $customPeriodEnd];
|
||||
}
|
||||
// then add previous range and next range
|
||||
$previousDate = Navigation::subtractPeriod($start, $viewRange);
|
||||
$index = $previousDate->formatLocalized($format);
|
||||
$previousStart = Navigation::startOfPeriod($previousDate, $viewRange);
|
||||
$previousEnd = Navigation::endOfPeriod($previousStart, $viewRange);
|
||||
$ranges[$index] = [$previousStart, $previousEnd];
|
||||
|
||||
if ($viewRange !== 'custom') {
|
||||
$prevStart = Navigation::subtractPeriod($start, $viewRange);// subtract for previous period
|
||||
$prevEnd = Navigation::endOfPeriod($prevStart, $viewRange);
|
||||
$nextStart = Navigation::addPeriod($start, $viewRange, 0); // add for previous period
|
||||
$nextEnd = Navigation::endOfPeriod($nextStart, $viewRange);
|
||||
}
|
||||
$nextDate = Navigation::addPeriod($start, $viewRange, 0);
|
||||
$index = $nextDate->formatLocalized($format);
|
||||
$nextStart = Navigation::startOfPeriod($nextDate, $viewRange);
|
||||
$nextEnd = Navigation::endOfPeriod($nextStart, $viewRange);
|
||||
$ranges[$index] = [$nextStart, $nextEnd];
|
||||
|
||||
$ranges = [];
|
||||
$ranges['current'] = [$start->format('Y-m-d'), $end->format('Y-m-d')];
|
||||
$ranges['previous'] = [$prevStart->format('Y-m-d'), $prevEnd->format('Y-m-d')];
|
||||
$ranges['next'] = [$nextStart->format('Y-m-d'), $nextEnd->format('Y-m-d')];
|
||||
// everything
|
||||
$index = strval(trans('firefly.everything'));
|
||||
$ranges[$index] = [$first, new Carbon];
|
||||
|
||||
$return = [
|
||||
'title' => $title,
|
||||
'configuration' => [
|
||||
'apply' => strval(trans('firefly.apply')),
|
||||
'cancel' => strval(trans('firefly.cancel')),
|
||||
'from' => strval(trans('firefly.from')),
|
||||
'to' => strval(trans('firefly.to')),
|
||||
'customRange' => strval(trans('firefly.customRange')),
|
||||
'start' => $start->format('Y-m-d'),
|
||||
'end' => $end->format('Y-m-d'),
|
||||
'ranges' => $ranges,
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
return $return;
|
||||
|
||||
}
|
||||
|
||||
private function getFormatByRange(string $viewRange): string
|
||||
{
|
||||
switch ($viewRange) {
|
||||
default:
|
||||
throw new FireflyException('The date picker does not yet support "' . $viewRange . '".'); // @codeCoverageIgnore
|
||||
throw new FireflyException(sprintf('The date picker does not yet support "%s".', $viewRange)); // @codeCoverageIgnore
|
||||
case '1D':
|
||||
case 'custom':
|
||||
$format = (string)trans('config.month_and_day');
|
||||
@@ -174,18 +204,6 @@ class JavascriptController extends Controller
|
||||
break;
|
||||
}
|
||||
|
||||
$current = $start->formatLocalized($format);
|
||||
$next = $nextStart->formatLocalized($format);
|
||||
$prev = $prevStart->formatLocalized($format);
|
||||
|
||||
return [
|
||||
'start' => $start->format('Y-m-d'),
|
||||
'end' => $end->format('Y-m-d'),
|
||||
'current' => $current,
|
||||
'previous' => $prev,
|
||||
'next' => $next,
|
||||
'ranges' => $ranges,
|
||||
];
|
||||
return $format;
|
||||
}
|
||||
|
||||
}
|
||||
|
171
app/Http/Controllers/Json/AutoCompleteController.php
Normal file
171
app/Http/Controllers/Json/AutoCompleteController.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* AutoCompleteController.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Json;
|
||||
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Response;
|
||||
|
||||
/**
|
||||
* Class AutoCompleteController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Json
|
||||
*/
|
||||
class AutoCompleteController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all accounts.
|
||||
*
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*/
|
||||
public function allAccounts(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$return = array_unique(
|
||||
$repository->getAccountsByType(
|
||||
[AccountType::REVENUE, AccountType::EXPENSE, AccountType::BENEFICIARY, AccountType::DEFAULT, AccountType::ASSET]
|
||||
)->pluck('name')->toArray()
|
||||
);
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalCollectorInterface $collector
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function allTransactionJournals(JournalCollectorInterface $collector)
|
||||
{
|
||||
$collector->setLimit(250)->setPage(1);
|
||||
$return = array_unique($collector->getJournals()->pluck('description')->toArray());
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all beneficiaries.
|
||||
*
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*/
|
||||
public function expenseAccounts(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$set = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]);
|
||||
$filtered = $set->filter(
|
||||
function (Account $account) {
|
||||
if ($account->active) {
|
||||
return $account;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
$return = array_unique($filtered->pluck('name')->toArray());
|
||||
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalCollectorInterface $collector
|
||||
*
|
||||
* @param TransactionJournal $except
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse|mixed
|
||||
*/
|
||||
public function journalsWithId(JournalCollectorInterface $collector, TransactionJournal $except)
|
||||
{
|
||||
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty('recent-journals-id');
|
||||
|
||||
if ($cache->has()) {
|
||||
return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$collector->setLimit(400)->setPage(1);
|
||||
$set = $collector->getJournals()->pluck('description', 'journal_id')->toArray();
|
||||
$return = [];
|
||||
foreach ($set as $id => $description) {
|
||||
$id = intval($id);
|
||||
if ($id !== $except->id) {
|
||||
$return[] = [
|
||||
'id' => $id,
|
||||
'name' => $id . ': ' . $description,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$cache->store($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*/
|
||||
public function revenueAccounts(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$set = $repository->getAccountsByType([AccountType::REVENUE]);
|
||||
$filtered = $set->filter(
|
||||
function (Account $account) {
|
||||
if ($account->active) {
|
||||
return $account;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
$return = array_unique($filtered->pluck('name')->toArray());
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalCollectorInterface $collector
|
||||
* @param string $what
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function transactionJournals(JournalCollectorInterface $collector, string $what)
|
||||
{
|
||||
$type = config('firefly.transactionTypesByWhat.' . $what);
|
||||
$types = [$type];
|
||||
|
||||
$collector->setTypes($types)->setLimit(250)->setPage(1);
|
||||
$return = array_unique($collector->getJournals()->pluck('description')->toArray());
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
}
|
193
app/Http/Controllers/Json/BoxController.php
Normal file
193
app/Http/Controllers/Json/BoxController.php
Normal file
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
/**
|
||||
* BoxController.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Json;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Response;
|
||||
|
||||
/**
|
||||
* Class BoxController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Json
|
||||
*/
|
||||
class BoxController extends Controller
|
||||
{
|
||||
/**
|
||||
* @param BudgetRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function available(BudgetRepositoryInterface $repository)
|
||||
{
|
||||
$start = session('start', Carbon::now()->startOfMonth());
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
$today = new Carbon;
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($today);
|
||||
$cache->addProperty('box-available');
|
||||
if ($cache->has()) {
|
||||
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
// get available amount
|
||||
$currency = app('amount')->getDefaultCurrency();
|
||||
$available = $repository->getAvailableBudget($currency, $start, $end);
|
||||
|
||||
|
||||
// get spent amount:
|
||||
$budgets = $repository->getActiveBudgets();
|
||||
$budgetInformation = $repository->collectBudgetInformation($budgets, $start, $end);
|
||||
$spent = strval(array_sum(array_column($budgetInformation, 'spent')));
|
||||
$left = bcadd($available, $spent);
|
||||
// left less than zero? then it's zero:
|
||||
if (bccomp($left, '0') === -1) {
|
||||
$left = '0';
|
||||
}
|
||||
$days = $today->diffInDays($end) + 1;
|
||||
$perDay = '0';
|
||||
if ($days !== 0) {
|
||||
$perDay = bcdiv($left, strval($days));
|
||||
}
|
||||
|
||||
$return = [
|
||||
'perDay' => app('amount')->formatAnything($currency, $perDay, false),
|
||||
'left' => app('amount')->formatAnything($currency, $left, false),
|
||||
];
|
||||
|
||||
$cache->store($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function balance()
|
||||
{
|
||||
$start = session('start', Carbon::now()->startOfMonth());
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('box-balance');
|
||||
if ($cache->has()) {
|
||||
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// try a collector for income:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)
|
||||
->setTypes([TransactionType::DEPOSIT])
|
||||
->withOpposingAccount();
|
||||
$income = strval($collector->getJournals()->sum('transaction_amount'));
|
||||
$currency = app('amount')->getDefaultCurrency();
|
||||
|
||||
// expense:
|
||||
// try a collector for expenses:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)
|
||||
->setTypes([TransactionType::WITHDRAWAL])
|
||||
->withOpposingAccount();
|
||||
$expense = strval($collector->getJournals()->sum('transaction_amount'));
|
||||
|
||||
$response = [
|
||||
'income' => app('amount')->formatAnything($currency, $income, false),
|
||||
'expense' => app('amount')->formatAnything($currency, $expense, false),
|
||||
'combined' => app('amount')->formatAnything($currency, bcadd($income, $expense), false),
|
||||
];
|
||||
|
||||
$cache->store($response);
|
||||
|
||||
return Response::json($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BillRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function bills(BillRepositoryInterface $repository)
|
||||
{
|
||||
$start = session('start', Carbon::now()->startOfMonth());
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('box-bills');
|
||||
if ($cache->has()) {
|
||||
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
/*
|
||||
* Since both this method and the chart use the exact same data, we can suffice
|
||||
* with calling the one method in the bill repository that will get this amount.
|
||||
*/
|
||||
$paidAmount = bcmul($repository->getBillsPaidInRange($start, $end), '-1');
|
||||
$unpaidAmount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
|
||||
$currency = app('amount')->getDefaultCurrency();
|
||||
|
||||
$return = [
|
||||
'paid' => app('amount')->formatAnything($currency, $paidAmount, false),
|
||||
'unpaid' => app('amount')->formatAnything($currency, $unpaidAmount, false),
|
||||
];
|
||||
$cache->store($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function netWorth(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$today = new Carbon(date('Y-m-d')); // needed so its per day.
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($today);
|
||||
$cache->addProperty('box-net-worth');
|
||||
if ($cache->has()) {
|
||||
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$currency = app('amount')->getDefaultCurrency();
|
||||
$balances = app('steam')->balancesByAccounts($accounts, $today);
|
||||
$sum = '0';
|
||||
foreach ($balances as $entry) {
|
||||
$sum = bcadd($sum, $entry);
|
||||
}
|
||||
|
||||
$return = [
|
||||
'net_worth' => app('amount')->formatAnything($currency, $sum, false),
|
||||
];
|
||||
|
||||
$cache->store($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
}
|
@@ -163,4 +163,4 @@ class IntroController
|
||||
return $steps;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -16,9 +16,7 @@ namespace FireflyIII\Http\Controllers;
|
||||
use Amount;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
@@ -62,151 +60,6 @@ class JsonController extends Controller
|
||||
return Response::json(['html' => $view]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all accounts.
|
||||
*
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*/
|
||||
public function allAccounts(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$return = array_unique(
|
||||
$repository->getAccountsByType(
|
||||
[AccountType::REVENUE, AccountType::EXPENSE, AccountType::BENEFICIARY, AccountType::DEFAULT, AccountType::ASSET]
|
||||
)->pluck('name')->toArray()
|
||||
);
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalCollectorInterface $collector
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function allTransactionJournals(JournalCollectorInterface $collector)
|
||||
{
|
||||
$collector->setLimit(100)->setPage(1);
|
||||
$return = array_unique($collector->getJournals()->pluck('description')->toArray());
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BillRepositoryInterface $repository
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function boxBillsPaid(BillRepositoryInterface $repository)
|
||||
{
|
||||
$start = session('start', Carbon::now()->startOfMonth());
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
|
||||
/*
|
||||
* Since both this method and the chart use the exact same data, we can suffice
|
||||
* with calling the one method in the bill repository that will get this amount.
|
||||
*/
|
||||
$amount = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
|
||||
$amount = bcmul($amount, '-1');
|
||||
$currency = Amount::getDefaultCurrency();
|
||||
|
||||
$data = ['box' => 'bills-paid', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
|
||||
|
||||
return Response::json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BillRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function boxBillsUnpaid(BillRepositoryInterface $repository)
|
||||
{
|
||||
$start = session('start', Carbon::now()->startOfMonth());
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
$amount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
|
||||
$currency = Amount::getDefaultCurrency();
|
||||
$data = ['box' => 'bills-unpaid', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
|
||||
|
||||
return Response::json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @internal param AccountTaskerInterface $accountTasker
|
||||
* @internal param AccountRepositoryInterface $repository
|
||||
*
|
||||
*/
|
||||
public function boxIn()
|
||||
{
|
||||
$start = session('start', Carbon::now()->startOfMonth());
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
|
||||
// works for json too!
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('box-in');
|
||||
if ($cache->has()) {
|
||||
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// try a collector for income:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)
|
||||
->setTypes([TransactionType::DEPOSIT])
|
||||
->withOpposingAccount();
|
||||
|
||||
$amount = strval($collector->getJournals()->sum('transaction_amount'));
|
||||
$currency = Amount::getDefaultCurrency();
|
||||
$data = ['box' => 'in', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
|
||||
$cache->store($data);
|
||||
|
||||
return Response::json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* @internal param AccountTaskerInterface $accountTasker
|
||||
* @internal param AccountRepositoryInterface $repository
|
||||
*
|
||||
*/
|
||||
public function boxOut()
|
||||
{
|
||||
$start = session('start', Carbon::now()->startOfMonth());
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
|
||||
// works for json too!
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('box-out');
|
||||
if ($cache->has()) {
|
||||
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// try a collector for expenses:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)
|
||||
->setTypes([TransactionType::WITHDRAWAL])
|
||||
->withOpposingAccount();
|
||||
$amount = strval($collector->getJournals()->sum('transaction_amount'));
|
||||
$currency = Amount::getDefaultCurrency();
|
||||
$data = ['box' => 'out', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
|
||||
$cache->store($data);
|
||||
|
||||
return Response::json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BudgetRepositoryInterface $repository
|
||||
*
|
||||
@@ -235,36 +88,6 @@ class JsonController extends Controller
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all beneficiaries.
|
||||
*
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*/
|
||||
public function expenseAccounts(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$return = array_unique($repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY])->pluck('name')->toArray());
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*/
|
||||
public function revenueAccounts(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$return = array_unique($repository->getAccountsByType([AccountType::REVENUE])->pluck('name')->toArray());
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all beneficiaries.
|
||||
*
|
||||
@@ -281,26 +104,6 @@ class JsonController extends Controller
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalCollectorInterface $collector
|
||||
* @param string $what
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function transactionJournals(JournalCollectorInterface $collector, string $what)
|
||||
{
|
||||
$type = config('firefly.transactionTypesByWhat.' . $what);
|
||||
$types = [$type];
|
||||
|
||||
$collector->setTypes($types)->setLimit(100)->setPage(1);
|
||||
$return = array_unique($collector->getJournals()->pluck('description')->toArray());
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalRepositoryInterface $repository
|
||||
*
|
||||
@@ -329,6 +132,7 @@ class JsonController extends Controller
|
||||
$triggers[$key] = trans('firefly.rule_trigger_' . $key . '_choice');
|
||||
}
|
||||
}
|
||||
asort($triggers);
|
||||
|
||||
$view = view('rules.partials.trigger', compact('triggers', 'count'))->render();
|
||||
|
||||
|
@@ -85,27 +85,27 @@ class PreferencesController extends Controller
|
||||
*/
|
||||
public function index(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$viewRangePref = Preferences::get('viewRange', '1M');
|
||||
$viewRange = $viewRangePref->data;
|
||||
$frontPageAccounts = Preferences::get('frontPageAccounts', []);
|
||||
$language = Preferences::get('language', config('firefly.default_language', 'en_US'))->data;
|
||||
$transactionPageSize = Preferences::get('transactionPageSize', 50)->data;
|
||||
$customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
|
||||
$showDepositsFrontpage = Preferences::get('showDepositsFrontpage', false)->data;
|
||||
$fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
|
||||
$fiscalYearStart = date('Y') . '-' . $fiscalYearStartStr;
|
||||
$tjOptionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
|
||||
$is2faEnabled = Preferences::get('twoFactorAuthEnabled', 0)->data; // twoFactorAuthEnabled
|
||||
$has2faSecret = !is_null(Preferences::get('twoFactorAuthSecret')); // hasTwoFactorAuthSecret
|
||||
$showIncomplete = env('SHOW_INCOMPLETE_TRANSLATIONS', false) === true;
|
||||
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$viewRangePref = Preferences::get('viewRange', '1M');
|
||||
$viewRange = $viewRangePref->data;
|
||||
$frontPageAccounts = Preferences::get('frontPageAccounts', []);
|
||||
$language = Preferences::get('language', config('firefly.default_language', 'en_US'))->data;
|
||||
$transactionPageSize = Preferences::get('transactionPageSize', 50)->data;
|
||||
$customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
|
||||
$showDeps = Preferences::get('showDepositsFrontpage', false)->data;
|
||||
$fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
|
||||
$fiscalYearStart = date('Y') . '-' . $fiscalYearStartStr;
|
||||
$tjOptionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
|
||||
$is2faEnabled = Preferences::get('twoFactorAuthEnabled', 0)->data; // twoFactorAuthEnabled
|
||||
$has2faSecret = !is_null(Preferences::get('twoFactorAuthSecret')); // hasTwoFactorAuthSecret
|
||||
$showIncomplete = env('SHOW_INCOMPLETE_TRANSLATIONS', false) === true;
|
||||
|
||||
return view(
|
||||
'preferences.index',
|
||||
compact(
|
||||
'language', 'accounts', 'frontPageAccounts', 'tjOptionalFields',
|
||||
'viewRange', 'customFiscalYear', 'transactionPageSize', 'fiscalYearStart', 'is2faEnabled',
|
||||
'has2faSecret', 'showIncomplete', 'showDepositsFrontpage'
|
||||
'has2faSecret', 'showIncomplete', 'showDeps'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@@ -13,14 +13,20 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use FireflyIII\Events\UserChangedEmail;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Exceptions\ValidationException;
|
||||
use FireflyIII\Http\Middleware\IsLimitedUser;
|
||||
use FireflyIII\Http\Requests\DeleteAccountFormRequest;
|
||||
use FireflyIII\Http\Requests\EmailFormRequest;
|
||||
use FireflyIII\Http\Requests\ProfileFormRequest;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Hash;
|
||||
use Log;
|
||||
use Preferences;
|
||||
use Session;
|
||||
use View;
|
||||
|
||||
@@ -47,10 +53,23 @@ class ProfileController extends Controller
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
$this->middleware(IsLimitedUser::class);
|
||||
$this->middleware(IsLimitedUser::class)->except(['confirmEmailChange', 'undoEmailChange']);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return View
|
||||
*/
|
||||
public function changeEmail()
|
||||
{
|
||||
$title = auth()->user()->email;
|
||||
$email = auth()->user()->email;
|
||||
$subTitle = strval(trans('firefly.change_your_email'));
|
||||
$subTitleIcon = 'fa-envelope';
|
||||
|
||||
return view('profile.change-email', compact('title', 'subTitle', 'subTitleIcon', 'email'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return View
|
||||
*/
|
||||
@@ -63,6 +82,37 @@ class ProfileController extends Controller
|
||||
return view('profile.change-password', compact('title', 'subTitle', 'subTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $token
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function confirmEmailChange(string $token)
|
||||
{
|
||||
// find preference with this token value.
|
||||
$set = Preferences::findByName('email_change_confirm_token');
|
||||
$user = null;
|
||||
/** @var Preference $preference */
|
||||
foreach ($set as $preference) {
|
||||
if ($preference->data === $token) {
|
||||
$user = $preference->user;
|
||||
}
|
||||
}
|
||||
// update user to clear blocked and blocked_code.
|
||||
if (is_null($user)) {
|
||||
throw new FireflyException('Invalid token.');
|
||||
}
|
||||
$user->blocked = 0;
|
||||
$user->blocked_code = '';
|
||||
$user->save();
|
||||
|
||||
// return to login.
|
||||
Session::flash('success', strval(trans('firefly.login_with_new_email')));
|
||||
|
||||
return redirect(route('login'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return View
|
||||
*/
|
||||
@@ -84,7 +134,57 @@ class ProfileController extends Controller
|
||||
$subTitle = auth()->user()->email;
|
||||
$userId = auth()->user()->id;
|
||||
|
||||
return view('profile.index', compact('subTitle', 'userId'));
|
||||
// get access token or create one.
|
||||
$accessToken = Preferences::get('access_token', null);
|
||||
if (is_null($accessToken)) {
|
||||
$token = auth()->user()->generateAccessToken();
|
||||
$accessToken = Preferences::set('access_token', $token);
|
||||
}
|
||||
|
||||
return view('profile.index', compact('subTitle', 'userId', 'accessToken'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EmailFormRequest $request
|
||||
* @param UserRepositoryInterface $repository
|
||||
*
|
||||
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function postChangeEmail(EmailFormRequest $request, UserRepositoryInterface $repository)
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$newEmail = $request->string('email');
|
||||
$oldEmail = $user->email;
|
||||
if ($newEmail === $user->email) {
|
||||
Session::flash('error', strval(trans('firefly.email_not_changed')));
|
||||
|
||||
return redirect(route('profile.change-email'))->withInput();
|
||||
}
|
||||
$existing = $repository->findByEmail($newEmail);
|
||||
if (!is_null($existing)) {
|
||||
// force user logout.
|
||||
$this->guard()->logout();
|
||||
$request->session()->invalidate();
|
||||
|
||||
Session::flash('success', strval(trans('firefly.email_changed')));
|
||||
|
||||
return redirect(route('index'));
|
||||
}
|
||||
|
||||
// now actually update user:
|
||||
$repository->changeEmail($user, $newEmail);
|
||||
|
||||
// call event.
|
||||
$ipAddress = $request->ip();
|
||||
event(new UserChangedEmail($user, $newEmail, $oldEmail, $ipAddress));
|
||||
|
||||
// force user logout.
|
||||
Auth::guard()->logout();
|
||||
$request->session()->invalidate();
|
||||
Session::flash('success', strval(trans('firefly.email_changed')));
|
||||
|
||||
return redirect(route('index'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,6 +240,64 @@ class ProfileController extends Controller
|
||||
return redirect(route('index'));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function regenerate()
|
||||
{
|
||||
$token = auth()->user()->generateAccessToken();
|
||||
Preferences::set('access_token', $token);
|
||||
Session::flash('success', strval(trans('firefly.token_regenerated')));
|
||||
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $token
|
||||
* @param string $hash
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function undoEmailChange(string $token, string $hash)
|
||||
{
|
||||
// find preference with this token value.
|
||||
$set = Preferences::findByName('email_change_undo_token');
|
||||
$user = null;
|
||||
/** @var Preference $preference */
|
||||
foreach ($set as $preference) {
|
||||
if ($preference->data === $token) {
|
||||
$user = $preference->user;
|
||||
}
|
||||
}
|
||||
if (is_null($user)) {
|
||||
throw new FireflyException('Invalid token.');
|
||||
}
|
||||
|
||||
// found user.
|
||||
// which email address to return to?
|
||||
$set = Preferences::beginsWith($user, 'previous_email_');
|
||||
$match = null;
|
||||
foreach ($set as $entry) {
|
||||
$hashed = hash('sha256', $entry->data);
|
||||
if ($hashed === $hash) {
|
||||
$match = $entry->data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_null($match)) {
|
||||
throw new FireflyException('Invalid token.');
|
||||
}
|
||||
// change user back
|
||||
$user->email = $match;
|
||||
$user->blocked = 0;
|
||||
$user->blocked_code = '';
|
||||
$user->save();
|
||||
|
||||
// return to login.
|
||||
Session::flash('success', strval(trans('firefly.login_with_old_email')));
|
||||
|
||||
return redirect(route('login'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
|
@@ -27,7 +27,7 @@ use FireflyIII\Models\RuleTrigger;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
|
||||
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
|
||||
use FireflyIII\Rules\TransactionMatcher;
|
||||
use FireflyIII\TransactionRules\TransactionMatcher;
|
||||
use Illuminate\Http\Request;
|
||||
use Preferences;
|
||||
use Response;
|
||||
@@ -166,10 +166,13 @@ class RuleController extends Controller
|
||||
*/
|
||||
public function edit(Request $request, RuleRepositoryInterface $repository, Rule $rule)
|
||||
{
|
||||
$oldTriggers = $this->getCurrentTriggers($rule);
|
||||
$triggerCount = count($oldTriggers);
|
||||
$oldActions = $this->getCurrentActions($rule);
|
||||
$actionCount = count($oldActions);
|
||||
/** @var RuleGroupRepositoryInterface $ruleGroupRepository */
|
||||
$ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
|
||||
$oldTriggers = $this->getCurrentTriggers($rule);
|
||||
$triggerCount = count($oldTriggers);
|
||||
$oldActions = $this->getCurrentActions($rule);
|
||||
$actionCount = count($oldActions);
|
||||
$ruleGroups = ExpandedForm::makeSelectList($ruleGroupRepository->get());
|
||||
|
||||
// has old input?
|
||||
if ($request->old()) {
|
||||
@@ -191,7 +194,12 @@ class RuleController extends Controller
|
||||
Session::flash('gaEventCategory', 'rules');
|
||||
Session::flash('gaEventAction', 'edit-rule');
|
||||
|
||||
return view('rules.rule.edit', compact('rule', 'subTitle', 'primaryTrigger', 'oldTriggers', 'oldActions', 'triggerCount', 'actionCount'));
|
||||
return view(
|
||||
'rules.rule.edit', compact(
|
||||
'rule', 'subTitle',
|
||||
'primaryTrigger', 'oldTriggers', 'oldActions', 'triggerCount', 'actionCount', 'ruleGroups'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -523,7 +531,7 @@ class RuleController extends Controller
|
||||
$actions[] = view(
|
||||
'rules.partials.action',
|
||||
[
|
||||
'oldTrigger' => $entry->action_type,
|
||||
'oldAction' => $entry->action_type,
|
||||
'oldValue' => $entry->action_value,
|
||||
'oldChecked' => $entry->stop_processing,
|
||||
'count' => $count,
|
||||
|
@@ -51,7 +51,7 @@ class SearchController extends Controller
|
||||
*/
|
||||
public function index(Request $request, SearchInterface $searcher)
|
||||
{
|
||||
$fullQuery = $request->get('q');
|
||||
$fullQuery = strval($request->get('q'));
|
||||
|
||||
// parse search terms:
|
||||
$searcher->parseQuery($fullQuery);
|
||||
|
@@ -22,7 +22,6 @@ use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Navigation;
|
||||
use Preferences;
|
||||
use Session;
|
||||
@@ -44,9 +43,6 @@ use View;
|
||||
class TagController extends Controller
|
||||
{
|
||||
|
||||
/** @var array */
|
||||
public $tagOptions = [];
|
||||
|
||||
/** @var TagRepositoryInterface */
|
||||
protected $repository;
|
||||
|
||||
@@ -57,21 +53,13 @@ class TagController extends Controller
|
||||
{
|
||||
parent::__construct();
|
||||
View::share('hideTags', true);
|
||||
$this->redirectUri = route('tags.index');
|
||||
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(TagRepositoryInterface::class);
|
||||
$this->tagOptions = [
|
||||
'nothing' => trans('firefly.regular_tag'),
|
||||
'balancingAct' => trans('firefly.balancing_act'),
|
||||
'advancePayment' => trans('firefly.advance_payment'),
|
||||
];
|
||||
|
||||
|
||||
View::share('title', strval(trans('firefly.tags')));
|
||||
View::share('mainTitleIcon', 'fa-tags');
|
||||
View::share('tagOptions', $this->tagOptions);
|
||||
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -79,22 +67,16 @@ class TagController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* Create a new tag.
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function create(Request $request)
|
||||
public function create()
|
||||
{
|
||||
$subTitle = trans('firefly.new_tag');
|
||||
$subTitleIcon = 'fa-tag';
|
||||
$apiKey = env('GOOGLE_MAPS_API_KEY', '');
|
||||
|
||||
$preFilled = [
|
||||
'tagMode' => 'nothing',
|
||||
];
|
||||
if (!$request->old('tagMode')) {
|
||||
Session::flash('preFilled', $preFilled);
|
||||
}
|
||||
// put previous url in session if not redirect from store (not "create another").
|
||||
if (session('tags.create.fromStore') !== true) {
|
||||
$this->rememberPreviousUri('tags.create.uri');
|
||||
@@ -107,6 +89,8 @@ class TagController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a tag
|
||||
*
|
||||
* @param Tag $tag
|
||||
*
|
||||
* @return View
|
||||
@@ -141,38 +125,18 @@ class TagController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
* Edit a tag
|
||||
*
|
||||
* @param TagRepositoryInterface $repository
|
||||
* @param Tag $tag
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function edit(Tag $tag, TagRepositoryInterface $repository)
|
||||
public function edit(Tag $tag)
|
||||
{
|
||||
$subTitle = trans('firefly.edit_tag', ['tag' => $tag->tag]);
|
||||
$subTitleIcon = 'fa-tag';
|
||||
$apiKey = env('GOOGLE_MAPS_API_KEY', '');
|
||||
|
||||
/*
|
||||
* Default tag options (again)
|
||||
*/
|
||||
$tagOptions = $this->tagOptions;
|
||||
|
||||
/*
|
||||
* Can this tag become another type?
|
||||
*/
|
||||
$allowAdvance = $repository->tagAllowAdvance($tag);
|
||||
$allowToBalancingAct = $repository->tagAllowBalancing($tag);
|
||||
|
||||
// edit tag options:
|
||||
if ($allowAdvance === false) {
|
||||
unset($tagOptions['advancePayment']);
|
||||
}
|
||||
if ($allowToBalancingAct === false) {
|
||||
unset($tagOptions['balancingAct']);
|
||||
}
|
||||
|
||||
|
||||
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||
if (session('tags.edit.fromUpdate') !== true) {
|
||||
$this->rememberPreviousUri('tags.edit.uri');
|
||||
@@ -181,46 +145,34 @@ class TagController extends Controller
|
||||
Session::flash('gaEventCategory', 'tags');
|
||||
Session::flash('gaEventAction', 'edit');
|
||||
|
||||
return view('tags.edit', compact('tag', 'subTitle', 'subTitleIcon', 'tagOptions', 'apiKey'));
|
||||
return view('tags.edit', compact('tag', 'subTitle', 'subTitleIcon', 'apiKey'));
|
||||
}
|
||||
|
||||
/**
|
||||
* View all tags
|
||||
*
|
||||
* @param TagRepositoryInterface $repository
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function index(TagRepositoryInterface $repository)
|
||||
{
|
||||
$title = 'Tags';
|
||||
$mainTitleIcon = 'fa-tags';
|
||||
$types = ['nothing', 'balancingAct', 'advancePayment'];
|
||||
$count = $repository->count();
|
||||
|
||||
// loop each types and get the tags, group them by year.
|
||||
$collection = [];
|
||||
foreach ($types as $type) {
|
||||
// collect tags by year:
|
||||
/** @var Carbon $start */
|
||||
$start = clone(session('first'));
|
||||
$now = new Carbon;
|
||||
$clouds = [];
|
||||
$clouds['no-date'] = $repository->tagCloud(null);
|
||||
while ($now > $start) {
|
||||
$year = $now->year;
|
||||
$clouds[$year] = $repository->tagCloud($year);
|
||||
|
||||
/** @var Collection $tags */
|
||||
$tags = $repository->getByType($type);
|
||||
$tags = $tags->sortBy(
|
||||
function (Tag $tag) {
|
||||
$date = !is_null($tag->date) ? $tag->date->format('Ymd') : '000000';
|
||||
|
||||
return strtolower($date . $tag->tag);
|
||||
}
|
||||
);
|
||||
|
||||
/** @var Tag $tag */
|
||||
foreach ($tags as $tag) {
|
||||
|
||||
$year = is_null($tag->date) ? trans('firefly.no_year') : $tag->date->year;
|
||||
$monthFormatted = is_null($tag->date) ? trans('firefly.no_month') : $tag->date->formatLocalized($this->monthFormat);
|
||||
|
||||
$collection[$type][$year][$monthFormatted][] = $tag;
|
||||
}
|
||||
$now->subYear();
|
||||
}
|
||||
$count = $repository->count();
|
||||
|
||||
return view('tags.index', compact('title', 'mainTitleIcon', 'types', 'collection', 'count'));
|
||||
return view('tags.index', compact('clouds', 'count'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,26 +188,21 @@ class TagController extends Controller
|
||||
// default values:
|
||||
$subTitle = $tag->tag;
|
||||
$subTitleIcon = 'fa-tag';
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$count = 0;
|
||||
$loop = 0;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = null;
|
||||
$end = null;
|
||||
$periods = new Collection;
|
||||
$apiKey = env('GOOGLE_MAPS_API_KEY', '');
|
||||
$sum = '0';
|
||||
$path = 'tags/show/' . $tag->id;
|
||||
|
||||
$path = route('tags.show', [$tag->id]);
|
||||
|
||||
// prep for "all" view.
|
||||
if ($moment === 'all') {
|
||||
$subTitle = trans('firefly.all_journals_for_tag', ['tag' => $tag->tag]);
|
||||
$start = $repository->firstUseDate($tag);
|
||||
$end = new Carbon;
|
||||
$sum = $repository->sumOfTag($tag);
|
||||
$path = 'tags/show/' . $tag->id . '/all';
|
||||
$path = route('tags.show', [$tag->id, 'all']);
|
||||
}
|
||||
|
||||
// prep for "specific date" view.
|
||||
@@ -268,12 +215,14 @@ class TagController extends Controller
|
||||
'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
$periods = $this->getPeriodOverview($tag);
|
||||
$sum = $repository->sumOfTag($tag, $start, $end);
|
||||
$path = route('tags.show', [$tag->id, $moment]);
|
||||
}
|
||||
|
||||
// prep for current period
|
||||
if (strlen($moment) === 0) {
|
||||
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
|
||||
/** @var Carbon $start */
|
||||
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
|
||||
/** @var Carbon $end */
|
||||
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
|
||||
$periods = $this->getPeriodOverview($tag);
|
||||
$subTitle = trans(
|
||||
@@ -281,37 +230,19 @@ class TagController extends Controller
|
||||
['tag' => $tag->tag, 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||
Log::info('Now at tag loop start.');
|
||||
while ($count === 0 && $loop < 3) {
|
||||
$loop++;
|
||||
Log::info(sprintf('Count is zero, search for journals between %s and %s (pagesize %d, page %d).', $start, $end, $pageSize, $page));
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
|
||||
->setTag($tag)->withBudgetInformation()->withCategoryInformation()->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath($path);
|
||||
$count = $journals->getCollection()->count();
|
||||
if ($count === 0 && $loop < 3) {
|
||||
$start->subDay();
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
|
||||
if ($moment !== 'all' && $loop > 1) {
|
||||
$subTitle = trans(
|
||||
'firefly.journals_in_period_for_tag',
|
||||
['tag' => $tag->tag, 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
|
||||
->setTag($tag)->withBudgetInformation()->withCategoryInformation()->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath($path);
|
||||
|
||||
return view('tags.show', compact('apiKey', 'tag', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end', 'moment'));
|
||||
$sums = $repository->sumsOfTag($tag, $start, $end);
|
||||
|
||||
return view('tags.show', compact('apiKey', 'tag', 'sums', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'start', 'end', 'moment'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param TagFormRequest $request
|
||||
*
|
||||
@@ -408,4 +339,6 @@ class TagController extends Controller
|
||||
return $collection;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -85,7 +85,7 @@ class ConvertController extends Controller
|
||||
|
||||
// cannot convert split.
|
||||
if ($journal->transactions()->count() > 2) {
|
||||
Session::flash('error', trans('firefly.cannot_convert_split_journl'));
|
||||
Session::flash('error', trans('firefly.cannot_convert_split_journal'));
|
||||
|
||||
return redirect(route('transactions.show', [$journal->id]));
|
||||
}
|
||||
@@ -134,7 +134,7 @@ class ConvertController extends Controller
|
||||
}
|
||||
|
||||
if ($journal->transactions()->count() > 2) {
|
||||
Session::flash('error', trans('firefly.cannot_convert_split_journl'));
|
||||
Session::flash('error', trans('firefly.cannot_convert_split_journal'));
|
||||
|
||||
return redirect(route('transactions.show', [$journal->id]));
|
||||
}
|
||||
@@ -185,7 +185,7 @@ class ConvertController extends Controller
|
||||
case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL:
|
||||
case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL:
|
||||
// three and five
|
||||
if ($data['destination_account_expense'] === '') {
|
||||
if ($data['destination_account_expense'] === '' || is_null($data['destination_account_expense'])) {
|
||||
// destination is a cash account.
|
||||
$destination = $accountRepository->getCashAccount();
|
||||
|
||||
@@ -232,7 +232,7 @@ class ConvertController extends Controller
|
||||
case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT:
|
||||
case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT:
|
||||
|
||||
if ($data['source_account_revenue'] === '') {
|
||||
if ($data['source_account_revenue'] === '' || is_null($data['source_account_revenue'])) {
|
||||
// destination is a cash account.
|
||||
$destination = $accountRepository->getCashAccount();
|
||||
|
||||
|
148
app/Http/Controllers/Transaction/LinkController.php
Normal file
148
app/Http/Controllers/Transaction/LinkController.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
/**
|
||||
* LinkController.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Transaction;
|
||||
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\JournalLinkRequest;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionJournalLink;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
|
||||
use Log;
|
||||
use Preferences;
|
||||
use Session;
|
||||
use URL;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* Class LinkController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Transaction
|
||||
*/
|
||||
class LinkController extends Controller
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
// some useful repositories:
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
View::share('title', trans('firefly.transactions'));
|
||||
View::share('mainTitleIcon', 'fa-repeat');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param TransactionJournalLink $link
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function delete(TransactionJournalLink $link)
|
||||
{
|
||||
$subTitleIcon = 'fa-link';
|
||||
$subTitle = trans('breadcrumbs.delete_journal_link');
|
||||
$this->rememberPreviousUri('journal_links.delete.uri');
|
||||
|
||||
return view('transactions.links.delete', compact('link', 'subTitle', 'subTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
* @param TransactionJournalLink $link
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function destroy(LinkTypeRepositoryInterface $repository, TransactionJournalLink $link)
|
||||
{
|
||||
$repository->destroyLink($link);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.deleted_link')));
|
||||
Preferences::mark();
|
||||
|
||||
return redirect(strval(session('journal_links.delete.uri')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalLinkRequest $request
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
* @param JournalRepositoryInterface $journalRepository
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function store(
|
||||
JournalLinkRequest $request, LinkTypeRepositoryInterface $repository, JournalRepositoryInterface $journalRepository, TransactionJournal $journal
|
||||
) {
|
||||
$linkInfo = $request->getLinkInfo();
|
||||
if ($linkInfo['transaction_journal_id'] === 0) {
|
||||
Session::flash('error', trans('firefly.invalid_link_selection'));
|
||||
|
||||
return redirect(route('transactions.show', [$journal->id]));
|
||||
}
|
||||
$linkType = $repository->find($linkInfo['link_type_id']);
|
||||
$other = $journalRepository->find($linkInfo['transaction_journal_id']);
|
||||
$alreadyLinked = $repository->findLink($journal, $other);
|
||||
if ($alreadyLinked) {
|
||||
Session::flash('error', trans('firefly.journals_error_linked'));
|
||||
|
||||
return redirect(route('transactions.show', [$journal->id]));
|
||||
}
|
||||
Log::debug(sprintf('Journal is %d, opposing is %d', $journal->id, $other->id));
|
||||
|
||||
$journalLink = new TransactionJournalLink;
|
||||
$journalLink->linkType()->associate($linkType);
|
||||
if ($linkInfo['direction'] === 'inward') {
|
||||
Log::debug(sprintf('Link type is inwards ("%s"), so %d is source and %d is destination.', $linkType->inward, $other->id, $journal->id));
|
||||
$journalLink->source()->associate($other);
|
||||
$journalLink->destination()->associate($journal);
|
||||
}
|
||||
|
||||
if ($linkInfo['direction'] === 'outward') {
|
||||
Log::debug(sprintf('Link type is inwards ("%s"), so %d is source and %d is destination.', $linkType->outward, $journal->id, $other->id));
|
||||
$journalLink->source()->associate($journal);
|
||||
$journalLink->destination()->associate($other);
|
||||
}
|
||||
|
||||
$journalLink->comment = $linkInfo['comments'];
|
||||
$journalLink->save();
|
||||
Session::flash('success', trans('firefly.journals_linked'));
|
||||
|
||||
return redirect(route('transactions.show', [$journal->id]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
* @param TransactionJournalLink $link
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function switch(LinkTypeRepositoryInterface $repository, TransactionJournalLink $link)
|
||||
{
|
||||
|
||||
$repository->switchLink($link);
|
||||
|
||||
return redirect(URL::previous());
|
||||
}
|
||||
|
||||
}
|
@@ -86,7 +86,7 @@ class MassController extends Controller
|
||||
foreach ($ids as $journalId) {
|
||||
/** @var TransactionJournal $journal */
|
||||
$journal = $repository->find(intval($journalId));
|
||||
if (!is_null($journal->id) && $journalId === $journal->id) {
|
||||
if (!is_null($journal->id) && intval($journalId) === $journal->id) {
|
||||
$set->push($journal);
|
||||
}
|
||||
}
|
||||
@@ -131,10 +131,9 @@ class MassController extends Controller
|
||||
$filtered = new Collection;
|
||||
$messages = [];
|
||||
/**
|
||||
* @var int $index
|
||||
* @var TransactionJournal $journal
|
||||
*/
|
||||
foreach ($journals as $index => $journal) {
|
||||
foreach ($journals as $journal) {
|
||||
$sources = $journal->sourceAccountList();
|
||||
$destinations = $journal->destinationAccountList();
|
||||
if ($sources->count() > 1) {
|
||||
|
@@ -14,12 +14,14 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Controllers\Transaction;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use ExpandedForm;
|
||||
use FireflyIII\Events\StoredTransactionJournal;
|
||||
use FireflyIII\Events\UpdatedTransactionJournal;
|
||||
use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\JournalFormRequest;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
@@ -115,7 +117,7 @@ class SingleController extends Controller
|
||||
'foreign_amount' => $foreignAmount,
|
||||
'native_amount' => $foreignAmount,
|
||||
'amount_currency_id_amount' => $transaction->foreign_currency_id ?? 0,
|
||||
'date' => $journal->date->format('Y-m-d'),
|
||||
'date' => (new Carbon())->format('Y-m-d'),
|
||||
'budget_id' => $budgetId,
|
||||
'category' => $categoryName,
|
||||
'tags' => $tags,
|
||||
@@ -142,7 +144,7 @@ class SingleController extends Controller
|
||||
{
|
||||
$what = strtolower($what);
|
||||
$uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
|
||||
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getActiveAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
|
||||
$assetAccounts = $this->groupedActiveAccountList();
|
||||
$budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets());
|
||||
$piggyBanks = $this->piggyBanks->getPiggyBanksWithAmount();
|
||||
$piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks);
|
||||
@@ -199,10 +201,10 @@ class SingleController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param TransactionJournal $transactionJournal
|
||||
* @param TransactionJournal $transactionJournal
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @internal param JournalRepositoryInterface $repository
|
||||
*/
|
||||
public function destroy(TransactionJournal $transactionJournal)
|
||||
{
|
||||
@@ -238,7 +240,7 @@ class SingleController extends Controller
|
||||
}
|
||||
|
||||
$what = strtolower($journal->transactionTypeStr());
|
||||
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
|
||||
$assetAccounts = $this->groupedAccountList();
|
||||
$budgetList = ExpandedForm::makeSelectListWithEmpty($this->budgets->getBudgets());
|
||||
|
||||
// view related code
|
||||
@@ -408,6 +410,46 @@ class SingleController extends Controller
|
||||
return redirect($this->getPreviousUri('transactions.edit.uri'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function groupedAccountList(): array
|
||||
{
|
||||
$accounts = $this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$return = [];
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$type = $account->getMeta('accountRole');
|
||||
if (strlen($type) === 0) {
|
||||
$type = 'no_account_type';
|
||||
}
|
||||
$key = strval(trans('firefly.opt_group_' . $type));
|
||||
$return[$key][$account->id] = $account->name;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function groupedActiveAccountList(): array
|
||||
{
|
||||
$accounts = $this->accounts->getActiveAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$return = [];
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$type = $account->getMeta('accountRole');
|
||||
if (strlen($type) === 0) {
|
||||
$type = 'no_account_type';
|
||||
}
|
||||
$key = strval(trans('firefly.opt_group_' . $type));
|
||||
$return[$key][$account->id] = $account->name;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
|
@@ -18,6 +18,8 @@ use ExpandedForm;
|
||||
use FireflyIII\Events\UpdatedTransactionJournal;
|
||||
use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\SplitJournalFormRequest;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
@@ -94,13 +96,23 @@ class SplitController extends Controller
|
||||
|
||||
$uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
|
||||
$currencies = $this->currencies->get();
|
||||
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
|
||||
$accountList = $this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$assetAccounts = ExpandedForm::makeSelectList($accountList);
|
||||
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
|
||||
$budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets());
|
||||
$preFilled = $this->arrayFromJournal($request, $journal);
|
||||
$subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
|
||||
$subTitleIcon = 'fa-pencil';
|
||||
|
||||
$accountArray = [];
|
||||
// account array to display currency info:
|
||||
/** @var Account $account */
|
||||
foreach ($accountList as $account) {
|
||||
$accountArray[$account->id] = $account;
|
||||
$accountArray[$account->id]['currency_id'] = intval($account->getMeta('currency_id'));
|
||||
}
|
||||
|
||||
|
||||
Session::flash('gaEventCategory', 'transactions');
|
||||
Session::flash('gaEventAction', 'edit-split-' . $preFilled['what']);
|
||||
|
||||
@@ -114,26 +126,25 @@ class SplitController extends Controller
|
||||
'transactions.split.edit',
|
||||
compact(
|
||||
'subTitleIcon', 'currencies', 'optionalFields',
|
||||
'preFilled', 'subTitle', 'amount', 'sourceAccounts', 'uploadSize', 'destinationAccounts', 'assetAccounts',
|
||||
'budgets', 'journal'
|
||||
'preFilled', 'subTitle', 'uploadSize', 'assetAccounts',
|
||||
'budgets', 'journal', 'accountArray', 'previous'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param SplitJournalFormRequest $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function update(Request $request, JournalRepositoryInterface $repository, TransactionJournal $journal)
|
||||
public function update(SplitJournalFormRequest $request, JournalRepositoryInterface $repository, TransactionJournal $journal)
|
||||
{
|
||||
if ($this->isOpeningBalance($journal)) {
|
||||
return $this->redirectToAccount($journal);
|
||||
}
|
||||
|
||||
$data = $this->arrayFromInput($request);
|
||||
$journal = $repository->updateSplitJournal($journal, $data);
|
||||
/** @var array $files */
|
||||
@@ -167,12 +178,13 @@ class SplitController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param SplitJournalFormRequest $request
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function arrayFromInput(Request $request): array
|
||||
private function arrayFromInput(SplitJournalFormRequest $request): array
|
||||
{
|
||||
$tags = is_null($request->get('tags')) ? '' : $request->get('tags');
|
||||
$array = [
|
||||
'journal_description' => $request->get('journal_description'),
|
||||
'journal_source_account_id' => $request->get('journal_source_account_id'),
|
||||
@@ -189,7 +201,7 @@ class SplitController extends Controller
|
||||
'invoice_date' => $request->get('invoice_date'),
|
||||
'internal_reference' => $request->get('internal_reference'),
|
||||
'notes' => $request->get('notes'),
|
||||
'tags' => explode(',', $request->get('tags')),
|
||||
'tags' => explode(',', $tags),
|
||||
|
||||
// transactions.
|
||||
'transactions' => $this->getTransactionDataFromRequest($request),
|
||||
@@ -200,8 +212,8 @@ class SplitController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param TransactionJournal $journal
|
||||
* @param SplitJournalFormRequest|Request $request
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
@@ -234,6 +246,8 @@ class SplitController extends Controller
|
||||
// transactions.
|
||||
'transactions' => $this->getTransactionDataFromJournal($journal),
|
||||
];
|
||||
// update transactions array with old request data.
|
||||
$array['transactions'] = $this->updateWithPrevious($array['transactions'], $request->old());
|
||||
|
||||
return $array;
|
||||
}
|
||||
@@ -282,11 +296,11 @@ class SplitController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param SplitJournalFormRequest|Request $request
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getTransactionDataFromRequest(Request $request): array
|
||||
private function getTransactionDataFromRequest(SplitJournalFormRequest $request): array
|
||||
{
|
||||
$return = [];
|
||||
$transactions = $request->get('transactions');
|
||||
@@ -312,5 +326,36 @@ class SplitController extends Controller
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $array
|
||||
* @param $old
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function updateWithPrevious($array, $old): array
|
||||
{
|
||||
if (count($old) === 0 || !isset($old['transactions'])) {
|
||||
return $array;
|
||||
}
|
||||
$old = $old['transactions'];
|
||||
foreach ($old as $index => $row) {
|
||||
if (isset($array[$index])) {
|
||||
$array[$index] = array_merge($array[$index], $row);
|
||||
continue;
|
||||
}
|
||||
// take some info from first transaction, that should at least exist.
|
||||
$array[$index] = $row;
|
||||
$array[$index]['transaction_currency_id'] = $array[0]['transaction_currency_id'];
|
||||
$array[$index]['transaction_currency_code'] = $array[0]['transaction_currency_code'];
|
||||
$array[$index]['transaction_currency_symbol'] = $array[0]['transaction_currency_symbol'];
|
||||
$array[$index]['foreign_amount'] = round($array[0]['foreign_destination_amount'] ?? '0', 12);
|
||||
$array[$index]['foreign_currency_id'] = $array[0]['foreign_currency_id'];
|
||||
$array[$index]['foreign_currency_code'] = $array[0]['foreign_currency_code'];
|
||||
$array[$index]['foreign_currency_symbol'] = $array[0]['foreign_currency_symbol'];
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -17,9 +17,11 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Helpers\Filter\InternalTransferFilter;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
|
||||
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -27,7 +29,6 @@ use Log;
|
||||
use Navigation;
|
||||
use Preferences;
|
||||
use Response;
|
||||
use Steam;
|
||||
use View;
|
||||
|
||||
/**
|
||||
@@ -70,15 +71,13 @@ class TransactionController extends Controller
|
||||
// default values:
|
||||
$subTitleIcon = config('firefly.transactionIconsByWhat.' . $what);
|
||||
$types = config('firefly.transactionTypesByWhat.' . $what);
|
||||
$page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page'));
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
|
||||
$count = 0;
|
||||
$loop = 0;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = null;
|
||||
$end = null;
|
||||
$periods = new Collection;
|
||||
$path = '/transactions/' . $what;
|
||||
$path = route('transactions.index', [$what]);
|
||||
|
||||
// prep for "all" view.
|
||||
if ($moment === 'all') {
|
||||
@@ -86,14 +85,14 @@ class TransactionController extends Controller
|
||||
$first = $repository->first();
|
||||
$start = $first->date ?? new Carbon;
|
||||
$end = new Carbon;
|
||||
$path = '/transactions/' . $what . '/all/';
|
||||
$path = route('transactions.index', [$what, 'all']);
|
||||
}
|
||||
|
||||
// prep for "specific date" view.
|
||||
if (strlen($moment) > 0 && $moment !== 'all') {
|
||||
$start = new Carbon($moment);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
$path = '/transactions/' . $what . '/' . $moment;
|
||||
$path = route('transactions.index', [$what, $moment]);
|
||||
$subTitle = trans(
|
||||
'firefly.title_' . $what . '_between',
|
||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
@@ -111,32 +110,14 @@ class TransactionController extends Controller
|
||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
// grab journals, but be prepared to jump a period back to get the right ones:
|
||||
Log::info('Now at transaction loop start.');
|
||||
while ($count === 0 && $loop < 3) {
|
||||
$loop++;
|
||||
Log::info('Count is zero, search for journals.');
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount();
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath($path);
|
||||
$count = $journals->getCollection()->count();
|
||||
if ($count === 0 && $loop < 3) {
|
||||
$start->subDay();
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfPeriod($start, $range);
|
||||
Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
|
||||
if ($moment !== 'all' && $loop > 1) {
|
||||
$subTitle = trans(
|
||||
'firefly.title_' . $what . '_between',
|
||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
}
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($start, $end)->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount();
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$journals = $collector->getPaginatedJournals();
|
||||
$journals->setPath($path);
|
||||
|
||||
|
||||
return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'journals', 'periods', 'start', 'end', 'moment'));
|
||||
|
||||
@@ -170,23 +151,26 @@ class TransactionController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param JournalTaskerInterface $tasker
|
||||
* @param TransactionJournal $journal
|
||||
* @param JournalTaskerInterface $tasker
|
||||
*
|
||||
* @param LinkTypeRepositoryInterface $linkTypeRepository
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||
*/
|
||||
public function show(TransactionJournal $journal, JournalTaskerInterface $tasker)
|
||||
public function show(TransactionJournal $journal, JournalTaskerInterface $tasker, LinkTypeRepositoryInterface $linkTypeRepository)
|
||||
{
|
||||
if ($this->isOpeningBalance($journal)) {
|
||||
return $this->redirectToAccount($journal);
|
||||
}
|
||||
|
||||
$linkTypes = $linkTypeRepository->get();
|
||||
$links = $linkTypeRepository->getLinks($journal);
|
||||
$events = $tasker->getPiggyBankEvents($journal);
|
||||
$transactions = $tasker->getTransactionsOverview($journal);
|
||||
$what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
|
||||
$subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"';
|
||||
|
||||
return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions'));
|
||||
return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions', 'linkTypes', 'links'));
|
||||
|
||||
|
||||
}
|
||||
@@ -204,7 +188,7 @@ class TransactionController extends Controller
|
||||
$start = $first->date ?? new Carbon;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = Navigation::startOfPeriod($start, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range);
|
||||
$end = Navigation::endOfX(new Carbon, $range, null);
|
||||
$entries = new Collection;
|
||||
$types = config('firefly.transactionTypesByWhat.' . $what);
|
||||
|
||||
@@ -230,35 +214,24 @@ class TransactionController extends Controller
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withOpposingAccount()->setTypes($types);
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$set = $collector->getJournals();
|
||||
$sum = $set->sum('transaction_amount');
|
||||
$journals = $set->count();
|
||||
$journals = $collector->getJournals();
|
||||
$sum = $journals->sum('transaction_amount');
|
||||
|
||||
// count per currency:
|
||||
$sums = $this->sumPerCurrency($journals);
|
||||
$dateStr = $end->format('Y-m-d');
|
||||
$dateName = Navigation::periodShow($end, $range);
|
||||
$array = [
|
||||
'string' => $dateStr,
|
||||
'name' => $dateName,
|
||||
'count' => $journals,
|
||||
'spent' => 0,
|
||||
'earned' => 0,
|
||||
'transferred' => 0,
|
||||
'date' => clone $end,
|
||||
'string' => $dateStr,
|
||||
'name' => $dateName,
|
||||
'sum' => $sum,
|
||||
'sums' => $sums,
|
||||
'date' => clone $end,
|
||||
];
|
||||
Log::debug(sprintf('What is %s', $what));
|
||||
switch ($what) {
|
||||
case 'withdrawal':
|
||||
$array['spent'] = $sum;
|
||||
break;
|
||||
case 'deposit':
|
||||
$array['earned'] = $sum;
|
||||
break;
|
||||
case 'transfers':
|
||||
case 'transfer':
|
||||
$array['transferred'] = Steam::positive($sum);
|
||||
break;
|
||||
|
||||
if ($journals->count() > 0) {
|
||||
$entries->push($array);
|
||||
}
|
||||
$entries->push($array);
|
||||
$end = Navigation::subtractPeriod($end, $range, 1);
|
||||
}
|
||||
Log::debug('End of loop');
|
||||
@@ -267,4 +240,41 @@ class TransactionController extends Controller
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $collection
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function sumPerCurrency(Collection $collection): array
|
||||
{
|
||||
$return = [];
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($collection as $transaction) {
|
||||
$currencyId = $transaction->transaction_currency_id;
|
||||
|
||||
// save currency information:
|
||||
if (!isset($return[$currencyId])) {
|
||||
$currencySymbol = $transaction->transaction_currency_symbol;
|
||||
$decimalPlaces = $transaction->transaction_currency_dp;
|
||||
$currencyCode = $transaction->transaction_currency_code;
|
||||
$return[$currencyId] = [
|
||||
'currency' => [
|
||||
'id' => $currencyId,
|
||||
'code' => $currencyCode,
|
||||
'symbol' => $currencySymbol,
|
||||
'dp' => $decimalPlaces,
|
||||
],
|
||||
'sum' => '0',
|
||||
'count' => 0,
|
||||
];
|
||||
}
|
||||
// save amount:
|
||||
$return[$currencyId]['sum'] = bcadd($return[$currencyId]['sum'], $transaction->transaction_amount);
|
||||
$return[$currencyId]['count']++;
|
||||
}
|
||||
asort($return);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,14 +1,15 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* Kernel.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http;
|
||||
|
||||
@@ -21,42 +22,24 @@ use FireflyIII\Http\Middleware\Range;
|
||||
use FireflyIII\Http\Middleware\RedirectIfAuthenticated;
|
||||
use FireflyIII\Http\Middleware\RedirectIfTwoFactorAuthenticated;
|
||||
use FireflyIII\Http\Middleware\Sandstorm;
|
||||
use FireflyIII\Http\Middleware\StartFireflySession;
|
||||
use FireflyIII\Http\Middleware\TrimStrings;
|
||||
use FireflyIII\Http\Middleware\TrustProxies;
|
||||
use FireflyIII\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
|
||||
use Illuminate\Auth\Middleware\Authorize;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
|
||||
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
|
||||
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
|
||||
/**
|
||||
* Class Kernel
|
||||
*
|
||||
* @package FireflyIII\Http
|
||||
*/
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
|
||||
/**
|
||||
* The bootstrap classes for the application.
|
||||
*
|
||||
* Next upgrade verify these are the same.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bootstrappers
|
||||
= [
|
||||
'Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables',
|
||||
'Illuminate\Foundation\Bootstrap\LoadConfiguration',
|
||||
'Illuminate\Foundation\Bootstrap\HandleExceptions',
|
||||
'Illuminate\Foundation\Bootstrap\RegisterFacades',
|
||||
'Illuminate\Foundation\Bootstrap\RegisterProviders',
|
||||
'Illuminate\Foundation\Bootstrap\BootProviders',
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
*
|
||||
@@ -67,6 +50,10 @@ class Kernel extends HttpKernel
|
||||
protected $middleware
|
||||
= [
|
||||
CheckForMaintenanceMode::class,
|
||||
ValidatePostSize::class,
|
||||
TrimStrings::class,
|
||||
ConvertEmptyStringsToNull::class,
|
||||
TrustProxies::class,
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -83,7 +70,7 @@ class Kernel extends HttpKernel
|
||||
Sandstorm::class,
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartFireflySession::class,
|
||||
StartSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
@@ -95,7 +82,7 @@ class Kernel extends HttpKernel
|
||||
Sandstorm::class,
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartFireflySession::class,
|
||||
StartSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
@@ -108,7 +95,7 @@ class Kernel extends HttpKernel
|
||||
Sandstorm::class,
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartFireflySession::class,
|
||||
StartSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
@@ -123,7 +110,7 @@ class Kernel extends HttpKernel
|
||||
Sandstorm::class,
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartFireflySession::class,
|
||||
StartSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
@@ -138,7 +125,7 @@ class Kernel extends HttpKernel
|
||||
Sandstorm::class,
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartFireflySession::class,
|
||||
StartSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
@@ -156,7 +143,7 @@ class Kernel extends HttpKernel
|
||||
Sandstorm::class,
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartFireflySession::class,
|
||||
StartSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
@@ -174,6 +161,7 @@ class Kernel extends HttpKernel
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* The application's route middleware.
|
||||
*
|
||||
@@ -189,6 +177,5 @@ class Kernel extends HttpKernel
|
||||
'can' => Authorize::class,
|
||||
'guest' => RedirectIfAuthenticated::class,
|
||||
'throttle' => ThrottleRequests::class,
|
||||
'range' => Range::class,
|
||||
];
|
||||
}
|
||||
|
@@ -44,8 +44,13 @@ class Authenticate
|
||||
return redirect()->guest('login');
|
||||
}
|
||||
if (intval(auth()->user()->blocked) === 1) {
|
||||
$message = strval(trans('firefly.block_account_logout'));
|
||||
if (auth()->user()->blocked_code === 'email_changed') {
|
||||
$message = strval(trans('firefly.email_changed_logout'));
|
||||
}
|
||||
|
||||
Session::flash('logoutMessage', $message);
|
||||
Auth::guard($guard)->logout();
|
||||
Session::flash('logoutMessage', trans('firefly.block_account_logout'));
|
||||
|
||||
return redirect()->guest('login');
|
||||
}
|
||||
|
@@ -1,25 +1,21 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* EncryptCookies.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Middleware;
|
||||
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies as BaseEncrypter;
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
||||
|
||||
/**
|
||||
* Class EncryptCookies
|
||||
*
|
||||
* @package FireflyIII\Http\Middleware
|
||||
*/
|
||||
class EncryptCookies extends BaseEncrypter
|
||||
class EncryptCookies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the cookies that should not be encrypted.
|
||||
|
@@ -98,7 +98,12 @@ class Range
|
||||
$locale = array_map('trim', $locale);
|
||||
|
||||
setlocale(LC_TIME, $locale);
|
||||
setlocale(LC_MONETARY, $locale);
|
||||
$moneyResult = setlocale(LC_MONETARY, $locale);
|
||||
|
||||
// send error to view if could not set money format
|
||||
if ($moneyResult === false) {
|
||||
View::share('invalidMonetaryLocale', true);
|
||||
}
|
||||
|
||||
|
||||
// save some formats:
|
||||
|
@@ -1,25 +1,21 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* RedirectIfAuthenticated.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
/**
|
||||
* Class RedirectIfAuthenticated
|
||||
*
|
||||
* @package FireflyIII\Http\Middleware
|
||||
*/
|
||||
class RedirectIfAuthenticated
|
||||
{
|
||||
/**
|
||||
@@ -34,7 +30,7 @@ class RedirectIfAuthenticated
|
||||
public function handle($request, Closure $next, $guard = null)
|
||||
{
|
||||
if (Auth::guard($guard)->check()) {
|
||||
return redirect('/');
|
||||
return redirect('/home');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
|
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* StartFireflySession.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\Session\SessionManager;
|
||||
|
||||
/**
|
||||
* Class StartFireflySession
|
||||
*
|
||||
* @package FireflyIII\Http\Middleware
|
||||
*/
|
||||
class StartFireflySession extends StartSession
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a new session middleware.
|
||||
*
|
||||
* @param \Illuminate\Session\SessionManager $manager
|
||||
*/
|
||||
public function __construct(SessionManager $manager)
|
||||
{
|
||||
parent::__construct($manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the current URL for the request if necessary.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Contracts\Session\Session $session
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function storeCurrentUrl(Request $request, $session)
|
||||
{
|
||||
$fullUrl = $request->fullUrl();
|
||||
if ($request->method() === 'GET' && $request->route() && !$request->ajax()) {
|
||||
if (strpos($fullUrl, '/javascript/') === false) {
|
||||
$session->setPreviousUrl($fullUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
30
app/Http/Middleware/TrimStrings.php
Normal file
30
app/Http/Middleware/TrimStrings.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* TrimStrings.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
|
||||
|
||||
class TrimStrings extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the attributes that should not be trimmed.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $except
|
||||
= [
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
}
|
40
app/Http/Middleware/TrustProxies.php
Normal file
40
app/Http/Middleware/TrustProxies.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* TrustProxies.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Http\Middleware;
|
||||
|
||||
use Fideloper\Proxy\TrustProxies as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TrustProxies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The current proxy header mappings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $headers
|
||||
= [
|
||||
Request::HEADER_FORWARDED => 'FORWARDED',
|
||||
Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR',
|
||||
Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
|
||||
Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
|
||||
Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
|
||||
];
|
||||
/**
|
||||
* The trusted proxies for this application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $proxies;
|
||||
}
|
@@ -1,27 +1,21 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* VerifyCsrfToken.php
|
||||
* Copyright (C) 2016 thegrumpydictator@gmail.com
|
||||
*
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Middleware;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
|
||||
|
||||
/**
|
||||
* Class VerifyCsrfToken
|
||||
*
|
||||
* @package FireflyIII\Http\Middleware
|
||||
*/
|
||||
class VerifyCsrfToken extends BaseVerifier
|
||||
class VerifyCsrfToken extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be excluded from CSRF verification.
|
||||
@@ -32,26 +26,4 @@ class VerifyCsrfToken extends BaseVerifier
|
||||
= [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* Add the CSRF token to the response cookies.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Symfony\Component\HttpFoundation\Response $response
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
protected function addCookieToResponse($request, $response)
|
||||
{
|
||||
$config = config('session');
|
||||
|
||||
$response->headers->setCookie(
|
||||
new Cookie(
|
||||
'XSRF-TOKEN', $request->session()->token(), Carbon::now()->getTimestamp() + 60 * $config['lifetime'],
|
||||
$config['path'], $config['domain'], $config['secure'], true
|
||||
)
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
@@ -75,13 +75,13 @@ class AccountFormRequest extends Request
|
||||
return [
|
||||
'id' => $idRule,
|
||||
'name' => $nameRule,
|
||||
'openingBalance' => 'numeric|required_with:openingBalanceDate',
|
||||
'openingBalanceDate' => 'date|required_with:openingBalance',
|
||||
'iban' => 'iban',
|
||||
'BIC' => 'bic',
|
||||
'virtualBalance' => 'numeric',
|
||||
'openingBalance' => 'numeric|required_with:openingBalanceDate|nullable',
|
||||
'openingBalanceDate' => 'date|required_with:openingBalance|nullable',
|
||||
'iban' => 'iban|nullable',
|
||||
'BIC' => 'bic|nullable',
|
||||
'virtualBalance' => 'numeric|nullable',
|
||||
'currency_id' => 'exists:transaction_currencies,id',
|
||||
'accountNumber' => 'between:1,255|uniqueAccountNumberForUser',
|
||||
'accountNumber' => 'between:1,255|uniqueAccountNumberForUser|nullable',
|
||||
'accountRole' => 'in:' . $accountRoles,
|
||||
'active' => 'boolean',
|
||||
'ccType' => 'in:' . $ccPaymentTypes,
|
||||
|
@@ -47,11 +47,11 @@ class AttachmentFormRequest extends Request
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
|
||||
// fixed
|
||||
return [
|
||||
'title' => 'between:1,255',
|
||||
'description' => 'between:1,65536',
|
||||
'notes' => 'between:1,65536',
|
||||
'title' => 'between:1,255|nullable',
|
||||
'description' => 'between:1,65536|nullable',
|
||||
'notes' => 'between:1,65536|nullable',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -61,7 +61,7 @@ class BillFormRequest extends Request
|
||||
$nameRule .= ',' . intval($this->get('id'));
|
||||
$matchRule .= ',' . intval($this->get('id'));
|
||||
}
|
||||
|
||||
// is OK
|
||||
$rules = [
|
||||
'name' => $nameRule,
|
||||
'match' => $matchRule,
|
||||
|
@@ -47,6 +47,7 @@ class BudgetFormRequest extends Request
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
// fixed
|
||||
/** @var BudgetRepositoryInterface $repository */
|
||||
$repository = app(BudgetRepositoryInterface::class);
|
||||
$nameRule = 'required|between:1,100|uniqueObjectForUser:budgets,name';
|
||||
|
@@ -35,8 +35,11 @@ class BudgetIncomeRequest extends Request
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
// fixed
|
||||
return [
|
||||
'amount' => 'numeric|required|min:0',
|
||||
'start' => 'required|date|before:end',
|
||||
'end' => 'required|date|after:start',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -54,6 +54,7 @@ class CategoryFormRequest extends Request
|
||||
$nameRule = 'required|between:1,100|uniqueObjectForUser:categories,name,' . intval($this->get('id'));
|
||||
}
|
||||
|
||||
// fixed
|
||||
return [
|
||||
'name' => $nameRule,
|
||||
];
|
||||
|
@@ -46,6 +46,7 @@ class ConfigurationRequest extends Request
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
// fixed
|
||||
$rules = [
|
||||
'single_user_mode' => 'between:0,1|numeric',
|
||||
'is_demo_site' => 'between:0,1|numeric',
|
||||
|
@@ -48,7 +48,7 @@ class CurrencyFormRequest extends Request
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
|
||||
// fixed
|
||||
$rules = [
|
||||
'name' => 'required|max:48|min:1|unique:transaction_currencies,name',
|
||||
'code' => 'required|min:3|max:3|unique:transaction_currencies,code',
|
||||
|
@@ -35,6 +35,7 @@ class DeleteAccountFormRequest extends Request
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
// fixed
|
||||
return [
|
||||
'password' => 'required',
|
||||
];
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user