Compare commits

...

229 Commits

Author SHA1 Message Date
James Cole
735222a8ed Merge branch 'release/4.7.0' 2018-01-31 07:14:15 +01:00
James Cole
66f299cd06 Update composer file. 2018-01-31 06:42:44 +01:00
James Cole
e3c5268143 Update changelog for Sandstorm. 2018-01-31 06:35:41 +01:00
James Cole
df32493d77 Final update for some translations. 2018-01-30 21:07:14 +01:00
James Cole
86faf44153 Small fixes in change log [skip ci] 2018-01-29 20:17:43 +01:00
James Cole
e2f3e4b555 Update sandstorm file list. 2018-01-29 19:56:57 +01:00
James Cole
e57ed6015c Updated composer lock file. 2018-01-29 19:31:36 +01:00
James Cole
9c34ca7fc4 Changelog for Sandstorm. 2018-01-29 19:14:58 +01:00
James Cole
7b94f4a441 Update change log. 2018-01-29 19:13:51 +01:00
James Cole
693f8d0738 Update language files. 2018-01-29 19:13:43 +01:00
James Cole
d6ecbc06bf Add Portuguese (Brazil) 2018-01-29 19:12:58 +01:00
James Cole
c31674fffc New version in Sandstorm file. 2018-01-29 19:10:29 +01:00
James Cole
b08de8cc00 Clean up config. 2018-01-29 19:09:52 +01:00
James Cole
4c503e4c7c Merge branch 'develop' of https://github.com/firefly-iii/firefly-iii into develop
* 'develop' of https://github.com/firefly-iii/firefly-iii:
  Workaround IE tab order issue w/ js initial select
2018-01-29 19:09:13 +01:00
James Cole
dd4158c6b4 Expand tests 2018-01-29 19:09:00 +01:00
James Cole
5fd7ea2b96 Add code coverage. 2018-01-29 19:08:49 +01:00
James Cole
b9ff80eb5a Update changelog. 2018-01-29 19:08:26 +01:00
James Cole
afc725bbc8 Merge pull request #1148 from devlearner/login-tab-order
Workaround IE tab order issue on js initial select
2018-01-28 21:13:56 +01:00
James Cole
0342c371cc Fix debug issue caught by @devlearner. 2018-01-28 20:59:26 +01:00
devlearner
2da4e6b048 Workaround IE tab order issue w/ js initial select 2018-01-28 14:23:06 +00:00
James Cole
f50550d79c Fix unit tests 2018-01-25 20:38:50 +01:00
James Cole
3fa39a6805 Clean up view and route. 2018-01-25 19:21:58 +01:00
James Cole
3dbe6d4870 Clean up config. 2018-01-25 19:21:46 +01:00
James Cole
59c48268ab Support more icons. 2018-01-25 19:21:40 +01:00
James Cole
1f83c5195d Add view method. Clean up repository links 2018-01-25 19:21:31 +01:00
James Cole
49a95a08fe More friendly demo user message. 2018-01-25 19:02:14 +01:00
James Cole
c86c5ccfe9 Add forgotten migration [skip ci] 2018-01-25 18:55:31 +01:00
James Cole
8a2497fc67 Expand mime type and upgrade version [skip ci] 2018-01-25 18:41:51 +01:00
James Cole
7c70732247 Some light refactoring. No changes. 2018-01-25 18:41:27 +01:00
James Cole
53fc4f2740 Better link to Spectre docs. [skip ci] 2018-01-24 15:23:27 +01:00
James Cole
f3ade5621e Merge pull request #1145 from devlearner/woff2
Include Woff fonts as well
2018-01-24 14:30:57 +01:00
devlearner
ec2e08e33a Update fonts for Source Sans Pro
(Bold Italic)
2018-01-24 21:04:03 +08:00
devlearner
b9a26faa4d Update css for Source Sans Pro
(Bold Italic)
2018-01-24 12:52:48 +00:00
James Cole
1a434d0c83 Expand readme with links to contribution pages. [skip ci] 2018-01-24 12:00:41 +01:00
James Cole
9a2c6c2967 Expand test coverage. 2018-01-24 11:09:21 +01:00
James Cole
602b35d589 Breadcrumb shows the correct title. 2018-01-24 11:09:05 +01:00
James Cole
f42cd0c7c3 Give correct info about import jobs. 2018-01-24 11:08:50 +01:00
James Cole
cb81855a17 Fix #1143 2018-01-24 11:05:00 +01:00
James Cole
89cf351ad0 add some fixes for #1111 2018-01-24 05:17:26 +01:00
devlearner
3f70a3f06c Added woff fonts 2018-01-24 01:10:04 +08:00
devlearner
9df360f010 Updated woff2 fonts
Roboto: Last modified 2017-10-16 (v18)
Lato: Last modified 2017-10-11 (v14)
2018-01-24 00:59:27 +08:00
James Cole
9a26d6d49f Fix #1140 2018-01-22 18:37:59 +01:00
James Cole
bc4d801c12 Fix #1141 2018-01-22 18:16:50 +01:00
James Cole
f2d8e13576 Fix #1142 2018-01-22 18:14:30 +01:00
James Cole
ec0b5db973 Update change log. 2018-01-21 20:04:53 +01:00
James Cole
46a0d1ce35 Update change log. 2018-01-21 20:03:29 +01:00
James Cole
cb81446ca6 Clean up debug code 2018-01-21 20:03:13 +01:00
James Cole
9350b4939c Make some file names lowercase. 2018-01-21 19:47:19 +01:00
James Cole
33e8c8c415 Make some file names lowercase. 2018-01-21 19:46:56 +01:00
James Cole
48fa86cc54 Improve some test coverage. 2018-01-21 18:06:57 +01:00
James Cole
d5e6d1c578 Remove reference to website from read me. 2018-01-21 11:10:08 +01:00
James Cole
0bc688795a Small update in update routine. 2018-01-21 11:09:55 +01:00
James Cole
eb76ed5591 New text in contributing [skip ci] 2018-01-21 09:10:36 +01:00
James Cole
839cbaf37a Updated read me file [skip ci] 2018-01-21 00:08:14 +01:00
James Cole
788fc9204d Update readme [skip ci] 2018-01-21 00:06:40 +01:00
James Cole
3e3e304ef3 Updated read me file [skip ci] 2018-01-20 23:59:10 +01:00
James Cole
447d453fdc Update composer lock file [skip ci] 2018-01-20 07:15:39 +01:00
James Cole
36fd7884f3 Update demo pages. 2018-01-20 07:15:26 +01:00
James Cole
54da08b2f2 Change settings so demo user can use Spectre. 2018-01-20 06:40:23 +01:00
James Cole
3f02072ae9 Update read me[skip ci] 2018-01-20 06:40:05 +01:00
James Cole
a9c117703b Update composer.json 2018-01-19 08:35:25 +01:00
James Cole
c137255155 Update .scrutinizer.yml 2018-01-19 08:23:31 +01:00
James Cole
e7829ecc38 Committed bad help JS. [skip ci] 2018-01-17 14:26:31 +01:00
James Cole
529bdafa31 Code climate file. 2018-01-17 13:04:43 +01:00
James Cole
e2af0caa41 Fix tests 2018-01-17 12:29:00 +01:00
James Cole
80f96abf08 Fix notes in link types. 2018-01-17 10:40:44 +01:00
James Cole
70da38193f Fix issue with budget chart. 2018-01-17 10:17:49 +01:00
James Cole
13df973873 Fix query cache. 2018-01-17 10:03:47 +01:00
James Cole
3ccb791674 Various code cleanup. [skip ci] 2018-01-17 09:32:18 +01:00
James Cole
ccf1a6c182 Fix #1134 2018-01-17 09:22:45 +01:00
James Cole
493543c1f5 Update language files [skip ci] 2018-01-17 06:43:04 +01:00
James Cole
5f5725e0e3 Explicit language tag in layout 2018-01-17 06:25:32 +01:00
James Cole
107dd42957 Update English language files [skip ci] 2018-01-17 06:24:50 +01:00
James Cole
a9f3fe4d3a Remove memcached experiment. 2018-01-16 22:01:55 +01:00
James Cole
3e62e17b9e Add some echo to Sandstorm scripts. 2018-01-16 21:34:36 +01:00
James Cole
57855b1930 Remove references to unused cache thing. 2018-01-16 21:09:27 +01:00
James Cole
aa9e8227bb Smal changes in Sandstorm configuration. [skip ci] #1130 2018-01-15 17:48:20 +01:00
James Cole
a80f083b6e Catch errors in DB seeds. 2018-01-15 17:13:23 +01:00
James Cole
474e066d4a Expand debug message [skip ci] 2018-01-14 19:59:05 +01:00
James Cole
4428ccefbf Expand debug message [skip ci] 2018-01-14 19:58:39 +01:00
James Cole
d568a6c8a9 First version of actual update check. 2018-01-14 19:56:18 +01:00
James Cole
97e9ad6cb2 Small updates in read me. 2018-01-14 19:49:29 +01:00
James Cole
00607d2a6d Code for #1040 2018-01-14 19:36:24 +01:00
James Cole
c2a425121d Code for #1040 2018-01-14 16:32:26 +01:00
James Cole
435694e9ea Code for #989 2018-01-14 10:57:27 +01:00
James Cole
f59135a9ca Code for #989 2018-01-14 10:48:17 +01:00
James Cole
102b106402 Different “drop up” menu. 2018-01-13 18:52:06 +01:00
James Cole
5c27c8e633 Multi currency net worth box. 2018-01-13 18:40:28 +01:00
James Cole
edd5215b21 Different icon for view. [skip ci] 2018-01-13 18:07:25 +01:00
James Cole
94b173ae6b New language strings. 2018-01-13 18:02:41 +01:00
James Cole
7d96b281b6 Add buttons to views 2018-01-13 18:01:53 +01:00
James Cole
a5515ac89f Update tests. 2018-01-13 10:36:49 +01:00
James Cole
fb863b0bf2 Improve step count for spectre imports. 2018-01-13 07:52:35 +01:00
James Cole
50882f309b Make sure number of steps is always correct. 2018-01-13 07:36:44 +01:00
James Cole
ce854fbb43 Catch error when trying to read (non-existent) logs. 2018-01-12 21:43:04 +01:00
James Cole
6799268ec4 Add intval just in case. 2018-01-12 21:31:39 +01:00
James Cole
6fe5ce0485 Expand budget report #1106 2018-01-12 21:08:59 +01:00
James Cole
cbeaf8e16a Expand tag report #1106 2018-01-12 21:02:27 +01:00
James Cole
04de4c9b36 Expand category report #1106 2018-01-12 20:53:18 +01:00
James Cole
517731cb59 Extra buttons 2018-01-12 20:37:56 +01:00
James Cole
e34e43173c Fix #1132 2018-01-12 20:37:39 +01:00
James Cole
79d6055a78 Fix #1131 2018-01-12 20:32:09 +01:00
James Cole
7ac4d2a2f4 Various new strings [skip ci] 2018-01-12 18:44:59 +01:00
James Cole
4984eda320 Clean up view HTML 2018-01-12 18:42:48 +01:00
James Cole
89e0791e2f Add button to create transaction. 2018-01-12 18:42:25 +01:00
James Cole
922d487821 Update icon [skip ci] 2018-01-11 21:27:24 +01:00
James Cole
4b789979ac Fix 2FA check #1125 2018-01-11 20:56:42 +01:00
James Cole
554b38ccff Code for #1126 2018-01-11 20:49:55 +01:00
James Cole
9614310208 Extra button for #1124 2018-01-11 19:08:01 +01:00
James Cole
d9ec3ac354 Code for #1124 2018-01-11 19:06:46 +01:00
James Cole
f326f08f7b Fix #1088 2018-01-11 18:58:33 +01:00
James Cole
0ae8418f32 Fix tests. 2018-01-10 20:07:47 +01:00
James Cole
309f9cd076 Add new roles 2018-01-10 19:59:40 +01:00
James Cole
61f5ed3874 Fix check for column roles. 2018-01-10 19:06:27 +01:00
James Cole
91178d2604 Various cleanup in import. 2018-01-10 18:18:49 +01:00
James Cole
87dae6ea18 Expand some code for Spectre import. 2018-01-10 16:49:32 +01:00
James Cole
2e495c38d1 Make env files more readable. 2018-01-10 14:37:40 +01:00
James Cole
c2987aaf4c Update docker config #1081 2018-01-10 13:15:12 +01:00
James Cole
f4f4eecb7b Add routes to ignore. [skip ci] 2018-01-10 12:42:32 +01:00
James Cole
84d9287251 Remove unused route [skip ci] 2018-01-10 12:42:17 +01:00
James Cole
b71f334744 Small rewrite in readme [skip ci] 2018-01-10 12:42:01 +01:00
James Cole
ad306e4d01 Strings for #1116 (help text) 2018-01-10 08:03:37 +01:00
James Cole
e40d4ef829 Add strings for #1116 to intro page. 2018-01-10 07:57:36 +01:00
James Cole
48c16c3dcc Clean up binders. 2018-01-10 07:51:47 +01:00
James Cole
c045193246 Remove unnecessary routes. 2018-01-10 07:29:55 +01:00
James Cole
a816e59a97 Small update in readme [skip ci] 2018-01-10 07:19:28 +01:00
James Cole
892074eaf2 Final touches on readme [skipci] 2018-01-09 20:24:45 +01:00
James Cole
3e501e429d Fix link to softalucous [skip ci] 2018-01-09 20:23:31 +01:00
James Cole
0f40accb4c Expand links. [skipci] 2018-01-09 20:22:46 +01:00
James Cole
3dae6c99a4 Fix badges. [skipci] 2018-01-09 20:17:58 +01:00
James Cole
b185c83597 Update read me file. [skip ci] 2018-01-09 20:16:17 +01:00
James Cole
ef6b4120f1 Lower case some file names. 2018-01-09 19:27:12 +01:00
James Cole
a43eef01fc Lower case some file names. 2018-01-09 19:26:49 +01:00
James Cole
2cb9aa537f Experimental support for CodeCov. 2018-01-09 17:52:18 +01:00
James Cole
2edd49a8b4 First version that supports Spectre. 2018-01-08 20:20:45 +01:00
James Cole
a57554d380 Merge branch 'develop' of https://github.com/firefly-iii/firefly-iii into develop
* 'develop' of https://github.com/firefly-iii/firefly-iii:
  Fix charts in IE
2018-01-08 20:20:14 +01:00
James Cole
c89486b6d9 Expand Spectre import code. 2018-01-08 19:21:00 +01:00
James Cole
f737cb7235 Update language files for #1109 2018-01-08 19:20:41 +01:00
James Cole
f1fe169553 Disable Spectre again. 2018-01-08 19:19:17 +01:00
James Cole
2fc760780e Add support for Russian. 2018-01-08 19:19:03 +01:00
James Cole
8c3290bf6f Merge pull request #1107 from devlearner/patch-1
Fix charts in Internet Explorer
2018-01-08 10:21:09 +01:00
devlearner
495158b9c9 Fix charts in IE
since IE apparently doesn't support arrow function expression (and throws a syntax error)
2018-01-08 07:49:32 +00:00
James Cole
f9fc9b1889 Spectre should not be enabled. [skip ci] 2018-01-07 16:49:49 +01:00
James Cole
11ff2ab9d1 Debug controller cannot be accessed by demo user. 2018-01-07 12:53:20 +01:00
James Cole
52b138e6b2 Fix error in read me [skip ci] 2018-01-07 12:43:02 +01:00
James Cole
3562ec1f79 Merge branch 'release/4.6.13' 2018-01-07 12:40:54 +01:00
James Cole
943b4de6dc Update Sandstorm file list. 2018-01-07 12:29:18 +01:00
James Cole
4097f51f69 Updated composer file. 2018-01-07 12:28:43 +01:00
James Cole
cad5b2cae9 Final translations before release. 2018-01-07 12:20:05 +01:00
James Cole
8bb4b0b9b2 Make sure log stays small. 2018-01-06 20:26:21 +01:00
James Cole
9bcd1ed807 Make sure file content is trimmed. 2018-01-06 19:17:47 +01:00
James Cole
d7b1892bda Catch when no amount info is present. 2018-01-06 19:17:35 +01:00
James Cole
ea0980ba83 Update Sandstorm file list. 2018-01-06 16:27:20 +01:00
James Cole
b479a3ef99 Update change log, composer files. 2018-01-06 16:10:02 +01:00
James Cole
600e15d42f Jump back a version. 2018-01-06 15:48:44 +01:00
James Cole
824959e71a Fix form thing [skip ci] 2018-01-06 14:19:30 +01:00
James Cole
cfd8d231fb Update logging for #1089 [skip ci] 2018-01-06 12:21:24 +01:00
James Cole
87c3dc2ecc Improve comparisons for #1089 2018-01-06 12:20:02 +01:00
James Cole
590f0a83ea Code for #1098 2018-01-06 11:56:20 +01:00
James Cole
65e518856b Add warning message to index. 2018-01-06 10:25:21 +01:00
James Cole
469313eca7 Commit merge. 2018-01-06 10:12:43 +01:00
James Cole
f13f378d7f Merge pull request #1093 from jinformatique/patch-1
create .htaccess file
2018-01-06 10:10:41 +01:00
James Cole
76d8017be5 Small extension on import account, for testing mostly. 2018-01-06 09:33:28 +01:00
James Cole
af1bdc0c2c Expand debug view. 2018-01-06 09:33:06 +01:00
James Cole
6ac1e05e60 Clear cache sooner for #1096 2018-01-06 07:42:03 +01:00
James Cole
7fa937e80b Fixed tests to account for issue fixed by #1097 2018-01-06 07:33:30 +01:00
James Cole
ed0e14aee5 Merge pull request #1097 from kelvinhammond/patch-1
Fix issue with accounts in select map not matching
2018-01-06 07:23:21 +01:00
Kelvin
e300c1aab4 Fix issue with accounts in select map not matching
When mapping Opposing Accounts the select input's values are incorrect and do not match account ids.
2018-01-06 00:40:17 -05:00
James Cole
9e1586878b Test some preprocessors. 2018-01-05 17:51:05 +01:00
James Cole
5d54939bd4 Expand read me. 2018-01-05 17:29:57 +01:00
James Cole
3e9f98b43e Expand test coverage. 2018-01-05 17:29:42 +01:00
James Cole
c329ffa545 Updated tests and views 2018-01-05 07:54:10 +01:00
James Cole
d3cad66b69 Improve tests for import. 2018-01-05 07:41:26 +01:00
James Cole
246af608d3 Update repository to fix #972 2018-01-04 22:44:47 +01:00
James Cole
e7debc5466 Rewrote importer to be more clean about the stage it is in. 2018-01-04 19:33:16 +01:00
James Cole
5866300ac1 Rename and move some files. 2018-01-04 18:34:51 +01:00
J'informatique
c1221aef4b create .htaccess file
- To force HTTPS
- To hide directory listing
- To prevent access to .env and other files
2018-01-04 12:29:33 +01:00
James Cole
bc879a3872 Updated language files [skip ci] 2018-01-04 10:31:49 +01:00
James Cole
2b4a3d463b Update composer files. 2018-01-04 10:25:24 +01:00
James Cole
6b59b6de6e Fix some things in import routine. 2018-01-04 08:59:39 +01:00
James Cole
3ce5ccb98a Another fix for #1092 2018-01-04 08:09:26 +01:00
James Cole
a1b83def4f New strings for translation. [skip ci] 2018-01-03 20:37:11 +01:00
James Cole
5177619301 Lots of new code for Spectre import. 2018-01-03 19:17:30 +01:00
James Cole
4e0319bacc Fix for #1092 2018-01-03 07:35:29 +01:00
James Cole
30f64af55f Experimental cross OS command for #1083 [skip ci] 2018-01-02 20:08:56 +01:00
James Cole
cdeabaaf9a Extra fix to cover for #1091 2018-01-02 19:53:22 +01:00
James Cole
fa676f60fb Internationalise several forms for #1090 2018-01-02 17:25:59 +01:00
James Cole
4ec123a19a Add some logging for #1089 2018-01-02 16:25:47 +01:00
James Cole
de35bde048 Some updated language strings. 2018-01-02 15:18:22 +01:00
James Cole
3f786856f3 Expand code for Spectre login. 2018-01-02 15:17:31 +01:00
James Cole
d49aecdf49 Update tests. 2018-01-02 07:14:00 +01:00
James Cole
6713597621 Fix #1087 2018-01-02 06:39:34 +01:00
James Cole
09bd55675d Fix Turkish string [skip ci] 2018-01-01 16:46:56 +01:00
James Cole
d87dda40ee Clean up some JS and views. 2018-01-01 16:46:41 +01:00
James Cole
06cfc391a1 Add cast to string to fix chart. [skip ci] 2018-01-01 16:46:15 +01:00
James Cole
bea06a06a1 Various updated sentences and strings in Polish. 2018-01-01 15:38:09 +01:00
James Cole
06fa2ab7a1 Update string in Spectre import, as suggested by @pkoziol 2018-01-01 15:37:15 +01:00
James Cole
4926011f3f Fix #1085 2018-01-01 15:33:38 +01:00
James Cole
0455f8658d Move debug to new controller, add some fields. 2018-01-01 15:33:24 +01:00
James Cole
4773021ff0 Expand debug controller. 2017-12-31 11:38:21 +01:00
James Cole
e2bd1f6544 Update breadcrumbs for PHP 7.2 #1079 2017-12-31 11:10:36 +01:00
James Cole
b73884160a Fix account controller + coverage. 2017-12-31 10:40:27 +01:00
James Cole
8bcfa729b6 Updated language files. 2017-12-31 09:36:36 +01:00
James Cole
f8dda5b4a6 Added Turkish. 2017-12-31 09:10:38 +01:00
James Cole
c0961d438d Rename a view. 2017-12-31 09:01:27 +01:00
James Cole
d9dd00eb39 Fix #1074 2017-12-30 21:04:04 +01:00
James Cole
6d9baaa499 Updated language files. [skip ci] 2017-12-30 20:43:18 +01:00
James Cole
b37ed5ab23 Code for #1077 2017-12-30 20:07:49 +01:00
James Cole
3fba741f1b Update views and routes for #1078 2017-12-30 14:25:11 +01:00
James Cole
73051d7d42 Add budget list, expand view. #1078 2017-12-30 13:05:19 +01:00
James Cole
717e101b80 Start updating view. 2017-12-30 12:54:19 +01:00
James Cole
08f28d48af Refer to new views. 2017-12-30 12:44:10 +01:00
James Cole
ec2f693575 Update breadcrumb #1078 2017-12-30 12:43:57 +01:00
James Cole
3cc1bf52cb Move view. #1078 2017-12-30 12:43:13 +01:00
James Cole
5da9f0cfb6 Fix #1080 2017-12-30 12:33:44 +01:00
James Cole
64624bea1d Rename language string #1078 2017-12-30 12:24:28 +01:00
James Cole
dc95395f42 Clean up buttons #1078 2017-12-30 12:23:26 +01:00
James Cole
273f188101 Fix error in route (my error) #1078 2017-12-30 12:23:16 +01:00
James Cole
a6b22b5156 Remove unused language string. #1078 2017-12-30 12:23:03 +01:00
James Cole
9d8a4cd934 Clean up JS code #1078 (also old code removed) 2017-12-30 12:22:49 +01:00
James Cole
c73dd5f664 Update routes to reflect change. #1078 2017-12-30 12:14:10 +01:00
James Cole
7ff8c5e966 Move bulk methods to their own controller. #1078 2017-12-30 12:13:13 +01:00
James Cole
1c11aa13b8 Merge commit for PR #1078 2017-12-30 11:56:02 +01:00
James Cole
0c2ec58366 Merge pull request #1078 from vicmosin/issues/509
Introduced bulk edit endpoint for bulk edit of categories and tags
2017-12-30 11:54:06 +01:00
Victor Mosin
2955d7148f Merge branch 'develop' into issues/509
# Conflicts:
#	public/js/ff/transactions/list.js
2017-12-30 11:49:42 +01:00
James Cole
f9d87cb9f6 Remove unused translations. 2017-12-30 09:47:37 +01:00
Victor Mosin
50fcc42e99 Fixed wrong counter value 2017-12-30 09:39:47 +01:00
Victor Mosin
805456d032 Introduced bulk edit endpoint for bulk edit of categories and tags 2017-12-30 09:21:28 +01:00
James Cole
a9df7906eb Update JS and CSS libraries. 2017-12-30 06:51:16 +01:00
500 changed files with 18182 additions and 4730 deletions

12
.codeclimate.yml Normal file
View File

@@ -0,0 +1,12 @@
---
exclude_patterns:
- public/lib/
- public/js/lib/
- public/fonts/
- public/css/jquery-ui/
- public/css/bootstrap-multiselect.css
- public/css/bootstrap-sortable.css
- public/css/bootstrap-tagsinput.css
- public/css/daterangepicker.css
- public/css/google-fonts.css
- .sandstorm/

View File

@@ -1,12 +1,24 @@
# You can leave this on "local". If you change it to production most console commands will ask for extra confirmation.
# Never set it to "testing".
APP_ENV=${FF_APP_ENV}
APP_DEBUG=false
APP_NAME=FireflyIII
APP_KEY=${FF_APP_KEY}
APP_LOG=daily
APP_LOG_LEVEL=warning
APP_URL=http://localhost
TRUSTED_PROXIES=
# Set to true if you want to see debug information in error screens.
APP_DEBUG=false
# This should be your email address
SITE_OWNER=mail@example.com
# The encryption key for your database and sessions. Keep this very secure.
# If you generate a new one all existing data must be considered LOST.
# Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it
APP_KEY=${FF_APP_KEY}
# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy.
APP_URL=${APP_URL}
TRUSTED_PROXIES=${TRUSTED_PROXIES}
# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III
# If you use SQLite, set connection to `sqlite` and remove the database, username and password settings.
DB_CONNECTION=mysql
DB_HOST=${FF_DB_HOST}
DB_PORT=3306
@@ -14,19 +26,27 @@ DB_DATABASE=${FF_DB_NAME}
DB_USERNAME=${FF_DB_USER}
DB_PASSWORD=${FF_DB_PASSWORD}
BROADCAST_DRIVER=log
# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/.
# Several other options exist. You can use 'single' for one big fat error log (not recommended).
# Also available are 'syslog' and 'errorlog' which will log to the system itself.
APP_LOG=daily
# Log level. You can set this from least severe to most severe:
# debug, info, notice, warning, error, critical, alert, emergency
# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably
# nothing will get logged, ever.
APP_LOG_LEVEL=warning
# If you're looking for performance improvements, you could install memcached.
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
# Cookie settings. Should not be necessary to change these.
COOKIE_PATH="/"
COOKIE_DOMAIN=
COOKIE_SECURE=false
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
# If you want Firefly III to mail you, update these settings
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
@@ -35,26 +55,36 @@ MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
# Firefly III can send you the following messages
SEND_REGISTRATION_MAIL=true
SEND_ERROR_MESSAGE=true
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
MAPBOX_API_KEY=
# If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here.
ANALYTICS_ID=
SITE_OWNER=mail@example.com
# Most parts of the database are encrypted by default, but you can turn this off if you want to.
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
USE_ENCRYPTION=true
# Leave the following configuration vars as is.
# Unless you like to tinker and know what you're doing.
APP_NAME=FireflyIII
BROADCAST_DRIVER=log
QUEUE_DRIVER=sync
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_ID=
DEMO_USERNAME=
DEMO_PASSWORD=
IS_DOCKER=true
IS_SANDSTORM=false
IS_HEROKU=false

View File

@@ -1,12 +1,24 @@
# You can leave this on "local". If you change it to production most console commands will ask for extra confirmation.
# Never set it to "testing".
APP_ENV=local
# Set to true if you want to see debug information in error screens.
APP_DEBUG=false
APP_NAME=FireflyIII
# This should be your email address
SITE_OWNER=mail@example.com
# The encryption key for your database and sessions. Keep this very secure.
# If you generate a new one all existing data must be considered LOST.
# Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it
APP_KEY=SomeRandomStringOf32CharsExactly
APP_LOG=daily
APP_LOG_LEVEL=notice
# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy.
APP_URL=http://localhost
TRUSTED_PROXIES=
# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III
# If you use SQLite, set connection to `sqlite` and remove the database, username and password settings.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
@@ -14,19 +26,27 @@ DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
BROADCAST_DRIVER=log
# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/.
# Several other options exist. You can use 'single' for one big fat error log (not recommended).
# Also available are 'syslog' and 'errorlog' which will log to the system itself.
APP_LOG=daily
# Log level. You can set this from least severe to most severe:
# debug, info, notice, warning, error, critical, alert, emergency
# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably
# nothing will get logged, ever.
APP_LOG_LEVEL=notice
# If you're looking for performance improvements, you could install memcached.
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
# Cookie settings. Should not be necessary to change these.
COOKIE_PATH="/"
COOKIE_DOMAIN=
COOKIE_SECURE=false
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
# If you want Firefly III to mail you, update these settings
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
@@ -35,26 +55,36 @@ MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
# Firefly III can send you the following messages
SEND_REGISTRATION_MAIL=true
SEND_ERROR_MESSAGE=true
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
MAPBOX_API_KEY=
# If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here.
ANALYTICS_ID=
SITE_OWNER=mail@example.com
# Most parts of the database are encrypted by default, but you can turn this off if you want to.
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
USE_ENCRYPTION=true
# Leave the following configuration vars as is.
# Unless you like to tinker and know what you're doing.
APP_NAME=FireflyIII
BROADCAST_DRIVER=log
QUEUE_DRIVER=sync
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_ID=
DEMO_USERNAME=
DEMO_PASSWORD=
IS_DOCKER=false
IS_SANDSTORM=false
IS_HEROKU=false

View File

@@ -1,12 +1,24 @@
# You can leave this on "local". If you change it to production most console commands will ask for extra confirmation.
# Never set it to "testing".
APP_ENV=heroku
APP_DEBUG=true
APP_NAME=FireflyIII
APP_KEY=7ahyYVPVsmxjdhsweWCauGeJfwc92NP2
APP_LOG=errorlog
APP_LOG_LEVEL=debug
APP_URL=http://localhost
TRUSTED_PROXIES=*
# Set to true if you want to see debug information in error screens.
APP_DEBUG=false
# This should be your email address
SITE_OWNER=heroku@example.com
# The encryption key for your database and sessions. Keep this very secure.
# If you generate a new one all existing data must be considered LOST.
# Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it
APP_KEY=7ahyYVPVsmxjdhsweWCauGeJfwc92NP2
# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy.
APP_URL=http://localhost
TRUSTED_PROXIES=
# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III
# If you use SQLite, set connection to `sqlite` and remove the database, username and password settings.
DB_CONNECTION=pgsql
@@ -14,19 +26,27 @@ DB_CONNECTION=pgsql
BROADCAST_DRIVER=log
# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/.
# Several other options exist. You can use 'single' for one big fat error log (not recommended).
# Also available are 'syslog' and 'errorlog' which will log to the system itself.
APP_LOG=errorlog
# Log level. You can set this from least severe to most severe:
# debug, info, notice, warning, error, critical, alert, emergency
# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably
# nothing will get logged, ever.
APP_LOG_LEVEL=debug
# If you're looking for performance improvements, you could install memcached.
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
# Cookie settings. Should not be necessary to change these.
COOKIE_PATH="/"
COOKIE_DOMAIN=
COOKIE_SECURE=false
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
# If you want Firefly III to mail you, update these settings
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
@@ -35,26 +55,36 @@ MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
# Firefly III can send you the following messages
SEND_REGISTRATION_MAIL=true
SEND_ERROR_MESSAGE=true
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
MAPBOX_API_KEY=
# If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here.
ANALYTICS_ID=
SITE_OWNER=heroku@example.com
# Most parts of the database are encrypted by default, but you can turn this off if you want to.
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
USE_ENCRYPTION=true
# Leave the following configuration vars as is.
# Unless you like to tinker and know what you're doing.
APP_NAME=FireflyIII
BROADCAST_DRIVER=log
QUEUE_DRIVER=sync
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_ID=
DEMO_USERNAME=
DEMO_PASSWORD=
IS_DOCKER=false
IS_SANDSTORM=false
IS_HEROKU=true

View File

@@ -1,12 +1,24 @@
# You can leave this on "local". If you change it to production most console commands will ask for extra confirmation.
# Never set it to "testing".
APP_ENV=local
# Set to true if you want to see debug information in error screens.
APP_DEBUG=false
APP_NAME=FireflyIII
# This should be your email address
SITE_OWNER=sandstorm@example.com
# The encryption key for your database and sessions. Keep this very secure.
# If you generate a new one all existing data must be considered LOST.
# Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it
APP_KEY=SomeRandomStringOf32CharsExactly
APP_LOG=syslog
APP_LOG_LEVEL=info
# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy.
APP_URL=http://localhost
TRUSTED_PROXIES=
# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III
# If you use SQLite, set connection to `sqlite` and remove the database, username and password settings.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
@@ -14,19 +26,27 @@ DB_DATABASE=firefly
DB_USERNAME=firefly
DB_PASSWORD=firefly
BROADCAST_DRIVER=log
# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/.
# Several other options exist. You can use 'single' for one big fat error log (not recommended).
# Also available are 'syslog' and 'errorlog' which will log to the system itself.
APP_LOG=syslog
# Log level. You can set this from least severe to most severe:
# debug, info, notice, warning, error, critical, alert, emergency
# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably
# nothing will get logged, ever.
APP_LOG_LEVEL=info
# If you're looking for performance improvements, you could install memcached.
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
# Cookie settings. Should not be necessary to change these.
COOKIE_PATH="/"
COOKIE_DOMAIN=
COOKIE_SECURE=false
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
# If you want Firefly III to mail you, update these settings
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
@@ -35,26 +55,36 @@ MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
# Firefly III can send you the following messages
SEND_REGISTRATION_MAIL=true
SEND_ERROR_MESSAGE=true
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
MAPBOX_API_KEY=
# If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here.
ANALYTICS_ID=
SITE_OWNER=mail@example.com
# Most parts of the database are encrypted by default, but you can turn this off if you want to.
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
USE_ENCRYPTION=true
# Leave the following configuration vars as is.
# Unless you like to tinker and know what you're doing.
APP_NAME=FireflyIII
BROADCAST_DRIVER=log
QUEUE_DRIVER=sync
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_ID=
DEMO_USERNAME=
DEMO_PASSWORD=
IS_DOCKER=false
IS_SANDSTORM=true
IS_HEROKU=false

View File

@@ -4,7 +4,7 @@
## Feature requests
I am always interested in expanding Firefly III's many features. If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.org/requested-features/).
I am always interested in expanding Firefly III's many features. Just open a ticket or [drop me a line](mailto:thegrumpydictator@gmail.com).
## Pull requests

14
.htaccess Normal file
View File

@@ -0,0 +1,14 @@
# Optional: force HTTPS:
# <IfModule mod_rewrite.c>
# RewriteEngine On
# RewriteCond %{HTTPS} off
# RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
# </IfModule>
# To hide directory listing
Options All -Indexes
# To prevent access to .env and other files
<Files .*>
Deny from all
</Files>

View File

@@ -3,10 +3,9 @@
# This script only runs once, when the app connects to sandstorm.
set -euo pipefail
echo "In build.sh"
cd /opt/app
cp .env.sandstorm .env
if [ -f /opt/app/composer.json ] ; then

View File

@@ -1,3 +1,50 @@
# 4.7.0
- Support for Russian and Portuguese (Brazil)
- Support for the Spectre API (Salt Edge)
- Many strings now translatable thanks to [Nik-vr](https://github.com/Nik-vr) ([issue 1118](https://github.com/firefly-iii/firefly-iii/issues/1118), [issue 1116](https://github.com/firefly-iii/firefly-iii/issues/1116), [issue 1109](https://github.com/firefly-iii/firefly-iii/issues/1109), )
- Many buttons to quickly create stuff
- Sum of tables in reports, requested by [MacPaille](https://github.com/MacPaille) ([issue 1106](https://github.com/firefly-iii/firefly-iii/issues/1106))
- Future versions of Firefly III will notify you there is a new version, as suggested by [8bitgentleman](https://github.com/8bitgentleman) in [issue 1050](https://github.com/firefly-iii/firefly-iii/issues/1050)
- Improved net worth box [issue 1101](https://github.com/firefly-iii/firefly-iii/issues/1101) ([Nik-vr](https://github.com/Nik-vr))
- Nice dropdown in transaction list [issue 1082](https://github.com/firefly-iii/firefly-iii/issues/1082)
- Better support for local fonts thanks to [devlearner](https://github.com/devlearner) ([issue 1145](https://github.com/firefly-iii/firefly-iii/issues/1145))
- Improve attachment support and view capabilities (suggested by [trinhit](https://github.com/trinhit) in [issue 1146](https://github.com/firefly-iii/firefly-iii/issues/1146))
- Whole new [read me file](https://github.com/firefly-iii/firefly-iii/blob/master/readme.md), [new end user documentation](https://firefly-iii.readthedocs.io/en/latest/) and an [updated website](https://www.firefly-iii.org/)!
- Many charts and info-blocks now scale property ([issue 989](https://github.com/firefly-iii/firefly-iii/issues/989) and [issue 1040](https://github.com/firefly-iii/firefly-iii/issues/1040))
- Charts work in IE thanks to [devlearner](https://github.com/devlearner) ([issue 1107](https://github.com/firefly-iii/firefly-iii/issues/1107))
- Various fixes in import routine
- Bug that left charts empty ([issue 1088](https://github.com/firefly-iii/firefly-iii/issues/1088)), reported by various users amongst which [jinformatique](https://github.com/jinformatique)
- [Issue 1124](https://github.com/firefly-iii/firefly-iii/issues/1124), as reported by [gavu](https://github.com/gavu)
- [Issue 1125](https://github.com/firefly-iii/firefly-iii/issues/1125), as reported by [gavu](https://github.com/gavu)
- [Issue 1126](https://github.com/firefly-iii/firefly-iii/issues/1126), as reported by [gavu](https://github.com/gavu)
- [Issue 1131](https://github.com/firefly-iii/firefly-iii/issues/1131), as reported by [dp87](https://github.com/dp87)
- [Issue 1129](https://github.com/firefly-iii/firefly-iii/issues/1129), as reported by [gavu](https://github.com/gavu)
- [Issue 1132](https://github.com/firefly-iii/firefly-iii/issues/1132), as reported by [gavu](https://github.com/gavu)
- Issue with cache in Sandstorm ([issue 1130](https://github.com/firefly-iii/firefly-iii/issues/1130))
- [Issue 1134](https://github.com/firefly-iii/firefly-iii/issues/1134)
- [Issue 1140](https://github.com/firefly-iii/firefly-iii/issues/1140)
- [Issue 1141](https://github.com/firefly-iii/firefly-iii/issues/1141), reported by [ErikFontanel](https://github.com/ErikFontanel)
- [Issue 1142](https://github.com/firefly-iii/firefly-iii/issues/1142)
- Removed many access rights from the demo user
# 4.6.13
- [Issue 1074](https://github.com/firefly-iii/firefly-iii/issues/1074), suggested by [MacPaille](https://github.com/MacPaille)
- [Issue 1077](https://github.com/firefly-iii/firefly-iii/issues/1077), suggested by [wtercato](https://github.com/wtercato)
- Bulk edit of transactions thanks to [vicmosin](https://github.com/vicmosin) ([issue 1078](https://github.com/firefly-iii/firefly-iii/issues/1078))
- Support for Turkish.
- [Issue 1090](https://github.com/firefly-iii/firefly-iii/issues/1090), suggested by [Findus23](https://github.com/Findus23)
- [Issue 1097](https://github.com/firefly-iii/firefly-iii/issues/1097), suggested by [kelvinhammond](https://github.com/kelvinhammond)
- [Issue 1093](https://github.com/firefly-iii/firefly-iii/issues/1093), suggested by [jinformatique](https://github.com/jinformatique)
- [Issue 1098](https://github.com/firefly-iii/firefly-iii/issues/1098), suggested by [Nik-vr](https://github.com/Nik-vr)
- [Issue 972](https://github.com/firefly-iii/firefly-iii/issues/972), reported by [pjotrvdh](https://github.com/pjotrvdh)
- [Issue 1079](https://github.com/firefly-iii/firefly-iii/issues/1079), reported by [gavu](https://github.com/gavu)
- [Issue 1080](https://github.com/firefly-iii/firefly-iii/issues/1080), reported by [zjean](https://github.com/zjean)
- [Issue 1083](https://github.com/firefly-iii/firefly-iii/issues/1083), reported by [skuzzle](https://github.com/skuzzle)
- [Issue 1085](https://github.com/firefly-iii/firefly-iii/issues/1085), reported by [nicoschreiner](https://github.com/nicoschreiner)
- [Issue 1087](https://github.com/firefly-iii/firefly-iii/issues/1087), reported by [4oo4](https://github.com/4oo4)
- [Issue 1089](https://github.com/firefly-iii/firefly-iii/issues/1089), reported by [robin5210](https://github.com/robin5210)
- [Issue 1092](https://github.com/firefly-iii/firefly-iii/issues/1092), reported by [kelvinhammond](https://github.com/kelvinhammond)
- [Issue 1096](https://github.com/firefly-iii/firefly-iii/issues/1096), reported by [wtercato](https://github.com/wtercato)
# 4.6.12
- Support for Indonesian.

View File

@@ -1,6 +1,7 @@
#!/bin/bash
# Runs every time we create a new grain!
echo "Now in launcher.sh"
# Create a bunch of folders under the clean /var that php, nginx, and mysql expect to exist
mkdir -p /var/lib/mysql
@@ -30,7 +31,6 @@ mkdir -p /var/storage/framework/views
mkdir -p /var/storage/logs
mkdir -p /var/storage/upload
# Ensure mysql tables created
HOME=/etc/mysql /usr/bin/mysql_install_db --force
@@ -58,5 +58,9 @@ echo "Migrating..."
php /opt/app/artisan migrate --seed --force
echo "Done!"
echo "Clear cache.."
php /opt/app/artisan cache:clear
echo "Done"
# Start nginx.
/usr/sbin/nginx -c /opt/app/.sandstorm/service-config/nginx.conf -g "daemon off;"

View File

@@ -200,14 +200,23 @@ lib/x86_64-linux-gnu/libwrap.so.0.7.6
lib/x86_64-linux-gnu/libz.so.1
lib/x86_64-linux-gnu/libz.so.1.2.8
lib64/ld-linux-x86-64.so.2
opt/app/.dockerignore
opt/app/.codeclimate.yml
opt/app/.env
opt/app/.env.docker
opt/app/.env.example
opt/app/.env.heroku
opt/app/.env.sandstorm
opt/app/.gitattributes
opt/app/.htaccess
opt/app/.sandstorm/.gitattributes
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/action_provision
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/action_set_name
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/creator_uid
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/id
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/index_uuid
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/private_key
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/synced_folders
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/vagrant_cwd
opt/app/.sandstorm/Vagrantfile
opt/app/.sandstorm/app-graphics/firefly-iii-128.png
opt/app/.sandstorm/app-graphics/firefly-iii-150.png
@@ -229,12 +238,7 @@ opt/app/.sandstorm/service-config/mime.types
opt/app/.sandstorm/service-config/nginx.conf
opt/app/.sandstorm/setup.sh
opt/app/.sandstorm/stack
opt/app/CHANGELOG.md
opt/app/CODE_OF_CONDUCT.md
opt/app/Dockerfile
opt/app/LICENSE
opt/app/Procfile
opt/app/README.md
opt/app/app.json
opt/app/app/Console/Commands/CreateExport.php
opt/app/app/Console/Commands/CreateImport.php
@@ -359,6 +363,7 @@ opt/app/app/Http/Controllers/Chart/ReportController.php
opt/app/app/Http/Controllers/Chart/TagReportController.php
opt/app/app/Http/Controllers/Controller.php
opt/app/app/Http/Controllers/CurrencyController.php
opt/app/app/Http/Controllers/DebugController.php
opt/app/app/Http/Controllers/ExportController.php
opt/app/app/Http/Controllers/HelpController.php
opt/app/app/Http/Controllers/HomeController.php
@@ -389,6 +394,7 @@ opt/app/app/Http/Controllers/RuleController.php
opt/app/app/Http/Controllers/RuleGroupController.php
opt/app/app/Http/Controllers/SearchController.php
opt/app/app/Http/Controllers/TagController.php
opt/app/app/Http/Controllers/Transaction/BulkController.php
opt/app/app/Http/Controllers/Transaction/ConvertController.php
opt/app/app/Http/Controllers/Transaction/LinkController.php
opt/app/app/Http/Controllers/Transaction/MassController.php
@@ -416,6 +422,7 @@ opt/app/app/Http/Requests/AttachmentFormRequest.php
opt/app/app/Http/Requests/BillFormRequest.php
opt/app/app/Http/Requests/BudgetFormRequest.php
opt/app/app/Http/Requests/BudgetIncomeRequest.php
opt/app/app/Http/Requests/BulkEditJournalRequest.php
opt/app/app/Http/Requests/CategoryFormRequest.php
opt/app/app/Http/Requests/ConfigurationRequest.php
opt/app/app/Http/Requests/CurrencyFormRequest.php
@@ -625,10 +632,22 @@ opt/app/app/Services/Github/Request/GithubRequest.php
opt/app/app/Services/Github/Request/UpdateRequest.php
opt/app/app/Services/Password/PwndVerifier.php
opt/app/app/Services/Password/Verifier.php
opt/app/app/Services/Spectre/Exception/DuplicatedCustomerException.php
opt/app/app/Services/Spectre/Exception/SpectreException.php
opt/app/app/Services/Spectre/Object/Account.php
opt/app/app/Services/Spectre/Object/Attempt.php
opt/app/app/Services/Spectre/Object/Customer.php
opt/app/app/Services/Spectre/Object/Holder.php
opt/app/app/Services/Spectre/Object/Login.php
opt/app/app/Services/Spectre/Object/SpectreObject.php
opt/app/app/Services/Spectre/Object/Token.php
opt/app/app/Services/Spectre/Object/Transaction.php
opt/app/app/Services/Spectre/Object/TransactionExtra.php
opt/app/app/Services/Spectre/Request/CreateTokenRequest.php
opt/app/app/Services/Spectre/Request/ListAccountsRequest.php
opt/app/app/Services/Spectre/Request/ListCustomersRequest.php
opt/app/app/Services/Spectre/Request/ListLoginsRequest.php
opt/app/app/Services/Spectre/Request/ListTransactionsRequest.php
opt/app/app/Services/Spectre/Request/NewCustomerRequest.php
opt/app/app/Services/Spectre/Request/SpectreRequest.php
opt/app/app/Support/Amount.php
@@ -657,7 +676,8 @@ opt/app/app/Support/Import/Configuration/ConfigurationInterface.php
opt/app/app/Support/Import/Configuration/File/Initial.php
opt/app/app/Support/Import/Configuration/File/Map.php
opt/app/app/Support/Import/Configuration/File/Roles.php
opt/app/app/Support/Import/Configuration/File/Upload.php
opt/app/app/Support/Import/Configuration/File/UploadConfig.php
opt/app/app/Support/Import/Configuration/Spectre/HaveAccounts.php
opt/app/app/Support/Import/Information/BunqInformation.php
opt/app/app/Support/Import/Information/InformationInterface.php
opt/app/app/Support/Models/TransactionJournalTrait.php
@@ -666,7 +686,6 @@ opt/app/app/Support/Preferences.php
opt/app/app/Support/Search/Modifier.php
opt/app/app/Support/Search/Search.php
opt/app/app/Support/Search/SearchInterface.php
opt/app/app/Support/SingleCacheProperties.php
opt/app/app/Support/Steam.php
opt/app/app/Support/Twig/AmountFormat.php
opt/app/app/Support/Twig/Extension/Transaction.php
@@ -741,6 +760,7 @@ opt/app/artisan
opt/app/bootstrap/app.php
opt/app/bootstrap/cache/packages.php
opt/app/bootstrap/cache/services.php
opt/app/changelog.md
opt/app/composer.json
opt/app/composer.lock
opt/app/composer.phar
@@ -763,7 +783,6 @@ opt/app/config/session.php
opt/app/config/twigbridge.php
opt/app/config/upgrade.php
opt/app/config/view.php
opt/app/crowdin.yml
opt/app/database/factories/ModelFactory.php
opt/app/database/migrations/2016_06_16_000000_create_support_tables.php
opt/app/database/migrations/2016_06_16_000001_create_users_table.php
@@ -786,8 +805,7 @@ opt/app/database/seeds/PermissionSeeder.php
opt/app/database/seeds/TransactionCurrencySeeder.php
opt/app/database/seeds/TransactionTypeSeeder.php
opt/app/docker-compose.yml
opt/app/nginx_app.conf
opt/app/phpunit.coverage.xml
opt/app/index.php
opt/app/public/.htaccess
opt/app/public/android-chrome-192x192.png
opt/app/public/android-chrome-512x512.png
@@ -810,56 +828,121 @@ opt/app/public/css/jquery-ui/jquery-ui.theme.min.css
opt/app/public/favicon-16x16.png
opt/app/public/favicon-32x32.png
opt/app/public/favicon.ico
opt/app/public/fonts/SourceSansPro-Bold-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-Bold-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-Bold-cyrillic.woff
opt/app/public/fonts/SourceSansPro-Bold-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-Bold-greek-ext.woff
opt/app/public/fonts/SourceSansPro-Bold-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-Bold-greek.woff
opt/app/public/fonts/SourceSansPro-Bold-greek.woff2
opt/app/public/fonts/SourceSansPro-Bold-latin-ext.woff
opt/app/public/fonts/SourceSansPro-Bold-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-Bold-latin.woff
opt/app/public/fonts/SourceSansPro-Bold-latin.woff2
opt/app/public/fonts/SourceSansPro-Bold-vietnamese.woff
opt/app/public/fonts/SourceSansPro-Bold-vietnamese.woff2
opt/app/public/fonts/SourceSansPro-BoldItalic-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-BoldItalic-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-BoldItalic-cyrillic.woff
opt/app/public/fonts/SourceSansPro-BoldItalic-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-BoldItalic-greek-ext.woff
opt/app/public/fonts/SourceSansPro-BoldItalic-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-BoldItalic-greek.woff
opt/app/public/fonts/SourceSansPro-BoldItalic-greek.woff2
opt/app/public/fonts/SourceSansPro-BoldItalic-latin-ext.woff
opt/app/public/fonts/SourceSansPro-BoldItalic-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-BoldItalic-latin.woff
opt/app/public/fonts/SourceSansPro-BoldItalic-latin.woff2
opt/app/public/fonts/SourceSansPro-BoldItalic-vietnamese.woff
opt/app/public/fonts/SourceSansPro-BoldItalic-vietnamese.woff2
opt/app/public/fonts/SourceSansPro-Italic-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-Italic-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-Italic-cyrillic.woff
opt/app/public/fonts/SourceSansPro-Italic-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-Italic-greek-ext.woff
opt/app/public/fonts/SourceSansPro-Italic-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-Italic-greek.woff
opt/app/public/fonts/SourceSansPro-Italic-greek.woff2
opt/app/public/fonts/SourceSansPro-Italic-latin-ext.woff
opt/app/public/fonts/SourceSansPro-Italic-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-Italic-latin.woff
opt/app/public/fonts/SourceSansPro-Italic-latin.woff2
opt/app/public/fonts/SourceSansPro-Italic-vietnamese.woff
opt/app/public/fonts/SourceSansPro-Italic-vietnamese.woff2
opt/app/public/fonts/SourceSansPro-Light-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-Light-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-Light-cyrillic.woff
opt/app/public/fonts/SourceSansPro-Light-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-Light-greek-ext.woff
opt/app/public/fonts/SourceSansPro-Light-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-Light-greek.woff
opt/app/public/fonts/SourceSansPro-Light-greek.woff2
opt/app/public/fonts/SourceSansPro-Light-latin-ext.woff
opt/app/public/fonts/SourceSansPro-Light-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-Light-latin.woff
opt/app/public/fonts/SourceSansPro-Light-latin.woff2
opt/app/public/fonts/SourceSansPro-Light-vietnamese.woff
opt/app/public/fonts/SourceSansPro-Light-vietnamese.woff2
opt/app/public/fonts/SourceSansPro-LightItalic-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-LightItalic-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-LightItalic-cyrillic.woff
opt/app/public/fonts/SourceSansPro-LightItalic-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-LightItalic-greek-ext.woff
opt/app/public/fonts/SourceSansPro-LightItalic-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-LightItalic-greek.woff
opt/app/public/fonts/SourceSansPro-LightItalic-greek.woff2
opt/app/public/fonts/SourceSansPro-LightItalic-latin-ext.woff
opt/app/public/fonts/SourceSansPro-LightItalic-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-LightItalic-latin.woff
opt/app/public/fonts/SourceSansPro-LightItalic-latin.woff2
opt/app/public/fonts/SourceSansPro-LightItalic-vietnamese.woff
opt/app/public/fonts/SourceSansPro-LightItalic-vietnamese.woff2
opt/app/public/fonts/SourceSansPro-Regular-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-Regular-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-Regular-cyrillic.woff
opt/app/public/fonts/SourceSansPro-Regular-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-Regular-greek-ext.woff
opt/app/public/fonts/SourceSansPro-Regular-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-Regular-greek.woff
opt/app/public/fonts/SourceSansPro-Regular-greek.woff2
opt/app/public/fonts/SourceSansPro-Regular-latin-ext.woff
opt/app/public/fonts/SourceSansPro-Regular-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-Regular-latin.woff
opt/app/public/fonts/SourceSansPro-Regular-latin.woff2
opt/app/public/fonts/SourceSansPro-Regular-vietnamese.woff
opt/app/public/fonts/SourceSansPro-Regular-vietnamese.woff2
opt/app/public/fonts/SourceSansPro-SemiBold-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-SemiBold-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-SemiBold-cyrillic.woff
opt/app/public/fonts/SourceSansPro-SemiBold-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-SemiBold-greek-ext.woff
opt/app/public/fonts/SourceSansPro-SemiBold-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-SemiBold-greek.woff
opt/app/public/fonts/SourceSansPro-SemiBold-greek.woff2
opt/app/public/fonts/SourceSansPro-SemiBold-latin-ext.woff
opt/app/public/fonts/SourceSansPro-SemiBold-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-SemiBold-latin.woff
opt/app/public/fonts/SourceSansPro-SemiBold-latin.woff2
opt/app/public/fonts/SourceSansPro-SemiBold-vietnamese.woff
opt/app/public/fonts/SourceSansPro-SemiBold-vietnamese.woff2
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-cyrillic-ext.woff
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-cyrillic-ext.woff2
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-cyrillic.woff
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-cyrillic.woff2
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-greek-ext.woff
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-greek-ext.woff2
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-greek.woff
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-greek.woff2
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-latin-ext.woff
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-latin-ext.woff2
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-latin.woff
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-latin.woff2
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-vietnamese.woff
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-vietnamese.woff2
opt/app/public/fonts/lato-100.woff
opt/app/public/fonts/lato-100.woff2
opt/app/public/fonts/roboto-light-300.woff
opt/app/public/fonts/roboto-light-300.woff2
opt/app/public/images/error.png
opt/app/public/images/image.png
@@ -920,6 +1003,7 @@ opt/app/public/js/ff/tags/create-edit.js
opt/app/public/js/ff/tags/index.js
opt/app/public/js/ff/tags/show.js
opt/app/public/js/ff/transactions/list.js
opt/app/public/js/ff/transactions/mass/edit-bulk.js
opt/app/public/js/ff/transactions/mass/edit.js
opt/app/public/js/ff/transactions/show.js
opt/app/public/js/ff/transactions/single/common.js
@@ -935,8 +1019,6 @@ opt/app/public/js/lib/bootstrap-tagsinput.min.js.map
opt/app/public/js/lib/bootstrap3-typeahead.min.js
opt/app/public/js/lib/daterangepicker.js
opt/app/public/js/lib/html5shiv.min.js
opt/app/public/js/lib/jquery-3.1.1.min.js
opt/app/public/js/lib/jquery-3.1.1.min.map
opt/app/public/js/lib/jquery-3.2.1.min.js
opt/app/public/js/lib/jquery-3.2.1.min.map
opt/app/public/js/lib/jquery-ui.min.js
@@ -986,6 +1068,7 @@ opt/app/public/report.html
opt/app/public/robots.txt
opt/app/public/safari-pinned-tab.svg
opt/app/public/web.config
opt/app/readme.md
opt/app/resources/lang/de_DE/auth.php
opt/app/resources/lang/de_DE/bank.php
opt/app/resources/lang/de_DE/breadcrumbs.php
@@ -1074,6 +1157,48 @@ opt/app/resources/lang/pl_PL/list.php
opt/app/resources/lang/pl_PL/pagination.php
opt/app/resources/lang/pl_PL/passwords.php
opt/app/resources/lang/pl_PL/validation.php
opt/app/resources/lang/pt_BR/auth.php
opt/app/resources/lang/pt_BR/bank.php
opt/app/resources/lang/pt_BR/breadcrumbs.php
opt/app/resources/lang/pt_BR/config.php
opt/app/resources/lang/pt_BR/csv.php
opt/app/resources/lang/pt_BR/demo.php
opt/app/resources/lang/pt_BR/firefly.php
opt/app/resources/lang/pt_BR/form.php
opt/app/resources/lang/pt_BR/import.php
opt/app/resources/lang/pt_BR/intro.php
opt/app/resources/lang/pt_BR/list.php
opt/app/resources/lang/pt_BR/pagination.php
opt/app/resources/lang/pt_BR/passwords.php
opt/app/resources/lang/pt_BR/validation.php
opt/app/resources/lang/ru_RU/auth.php
opt/app/resources/lang/ru_RU/bank.php
opt/app/resources/lang/ru_RU/breadcrumbs.php
opt/app/resources/lang/ru_RU/config.php
opt/app/resources/lang/ru_RU/csv.php
opt/app/resources/lang/ru_RU/demo.php
opt/app/resources/lang/ru_RU/firefly.php
opt/app/resources/lang/ru_RU/form.php
opt/app/resources/lang/ru_RU/import.php
opt/app/resources/lang/ru_RU/intro.php
opt/app/resources/lang/ru_RU/list.php
opt/app/resources/lang/ru_RU/pagination.php
opt/app/resources/lang/ru_RU/passwords.php
opt/app/resources/lang/ru_RU/validation.php
opt/app/resources/lang/tr_TR/auth.php
opt/app/resources/lang/tr_TR/bank.php
opt/app/resources/lang/tr_TR/breadcrumbs.php
opt/app/resources/lang/tr_TR/config.php
opt/app/resources/lang/tr_TR/csv.php
opt/app/resources/lang/tr_TR/demo.php
opt/app/resources/lang/tr_TR/firefly.php
opt/app/resources/lang/tr_TR/form.php
opt/app/resources/lang/tr_TR/import.php
opt/app/resources/lang/tr_TR/intro.php
opt/app/resources/lang/tr_TR/list.php
opt/app/resources/lang/tr_TR/pagination.php
opt/app/resources/lang/tr_TR/passwords.php
opt/app/resources/lang/tr_TR/validation.php
opt/app/resources/stubs/binary.bin
opt/app/resources/stubs/csv.csv
opt/app/resources/stubs/demo-configuration.json
@@ -1137,7 +1262,6 @@ opt/app/resources/views/demo/accounts/index.twig
opt/app/resources/views/demo/budgets/index.twig
opt/app/resources/views/demo/currencies/index.twig
opt/app/resources/views/demo/home.twig
opt/app/resources/views/demo/import/configure.twig
opt/app/resources/views/demo/import/index.twig
opt/app/resources/views/demo/index.twig
opt/app/resources/views/demo/no-demo-text.twig
@@ -1194,13 +1318,11 @@ opt/app/resources/views/import/bunq/prerequisites.twig
opt/app/resources/views/import/file/initial.twig
opt/app/resources/views/import/file/map.twig
opt/app/resources/views/import/file/roles.twig
opt/app/resources/views/import/file/upload.twig
opt/app/resources/views/import/file/upload-config.twig
opt/app/resources/views/import/index.twig
opt/app/resources/views/import/spectre/input-fields.twig
opt/app/resources/views/import/spectre/accounts.twig
opt/app/resources/views/import/spectre/prerequisites.twig
opt/app/resources/views/import/spectre/redirect.twig
opt/app/resources/views/import/spectre/select-country.twig
opt/app/resources/views/import/spectre/select-provider.twig
opt/app/resources/views/import/status.twig
opt/app/resources/views/index.twig
opt/app/resources/views/javascript/accounts.twig
@@ -1219,6 +1341,7 @@ opt/app/resources/views/list/piggy-bank-events.twig
opt/app/resources/views/list/piggy-banks.twig
opt/app/resources/views/new-user/index.twig
opt/app/resources/views/partials/boxes.twig
opt/app/resources/views/partials/breadcrumbs.twig
opt/app/resources/views/partials/control-bar.twig
opt/app/resources/views/partials/empty.twig
opt/app/resources/views/partials/favicons.twig
@@ -1297,10 +1420,11 @@ opt/app/resources/views/tags/edit.twig
opt/app/resources/views/tags/index.twig
opt/app/resources/views/tags/show.twig
opt/app/resources/views/test/test.twig
opt/app/resources/views/transactions/bulk/edit.twig
opt/app/resources/views/transactions/convert.twig
opt/app/resources/views/transactions/index.twig
opt/app/resources/views/transactions/links/delete.twig
opt/app/resources/views/transactions/mass-delete.twig
opt/app/resources/views/transactions/mass/delete.twig
opt/app/resources/views/transactions/mass/edit.twig
opt/app/resources/views/transactions/show.twig
opt/app/resources/views/transactions/single/create.twig
@@ -2280,10 +2404,8 @@ opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/boots
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/none-stubs/app.js
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/react-stubs/Example.js
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/react-stubs/app.js
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/react-stubs/webpack.mix.js
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/vue-stubs/ExampleComponent.vue
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/vue-stubs/app.js
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/vue-stubs/webpack.mix.js
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/ProviderMakeCommand.php
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/QueuedCommand.php
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/RequestMakeCommand.php
@@ -2801,6 +2923,7 @@ opt/app/vendor/league/commonmark/src/Block/Element/Heading.php
opt/app/vendor/league/commonmark/src/Block/Element/HtmlBlock.php
opt/app/vendor/league/commonmark/src/Block/Element/IndentedCode.php
opt/app/vendor/league/commonmark/src/Block/Element/InlineContainer.php
opt/app/vendor/league/commonmark/src/Block/Element/InlineContainerInterface.php
opt/app/vendor/league/commonmark/src/Block/Element/ListBlock.php
opt/app/vendor/league/commonmark/src/Block/Element/ListData.php
opt/app/vendor/league/commonmark/src/Block/Element/ListItem.php
@@ -2833,7 +2956,6 @@ opt/app/vendor/league/commonmark/src/Context.php
opt/app/vendor/league/commonmark/src/ContextInterface.php
opt/app/vendor/league/commonmark/src/Converter.php
opt/app/vendor/league/commonmark/src/Cursor.php
opt/app/vendor/league/commonmark/src/CursorState.php
opt/app/vendor/league/commonmark/src/Delimiter/Delimiter.php
opt/app/vendor/league/commonmark/src/Delimiter/DelimiterStack.php
opt/app/vendor/league/commonmark/src/DocParser.php
@@ -2932,6 +3054,7 @@ opt/app/vendor/league/flysystem/docs/adapter/aws-s3-v2.md
opt/app/vendor/league/flysystem/docs/adapter/aws-s3-v3.md
opt/app/vendor/league/flysystem/docs/adapter/azure.md
opt/app/vendor/league/flysystem/docs/adapter/copy.md
opt/app/vendor/league/flysystem/docs/adapter/digitalocean-spaces.md
opt/app/vendor/league/flysystem/docs/adapter/dropbox.md
opt/app/vendor/league/flysystem/docs/adapter/ftp.md
opt/app/vendor/league/flysystem/docs/adapter/gridfs.md
@@ -2951,10 +3074,15 @@ opt/app/vendor/league/flysystem/docs/creating-an-adapter.md
opt/app/vendor/league/flysystem/docs/index.md
opt/app/vendor/league/flysystem/docs/installation.md
opt/app/vendor/league/flysystem/docs/integrations.md
opt/app/vendor/league/flysystem/docs/logo/become_a_patron_button.png
opt/app/vendor/league/flysystem/docs/logo/become_a_patron_button@2x.png
opt/app/vendor/league/flysystem/docs/logo/become_a_patron_button@3x.png
opt/app/vendor/league/flysystem/docs/logo/laravel.svg
opt/app/vendor/league/flysystem/docs/mount-manager.md
opt/app/vendor/league/flysystem/docs/performance.md
opt/app/vendor/league/flysystem/docs/plugins.md
opt/app/vendor/league/flysystem/docs/recipes.md
opt/app/vendor/league/flysystem/docs/sponsors.md
opt/app/vendor/league/flysystem/docs/upgrade-to-1.0.0.md
opt/app/vendor/league/flysystem/src/Adapter/AbstractAdapter.php
opt/app/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php
@@ -3002,6 +3130,7 @@ opt/app/vendor/league/flysystem/src/Util.php
opt/app/vendor/league/flysystem/src/Util/ContentListingFormatter.php
opt/app/vendor/league/flysystem/src/Util/MimeType.php
opt/app/vendor/league/flysystem/src/Util/StreamHasher.php
opt/app/vendor/league/flysystem/wait_for_ftp_service.php
opt/app/vendor/monolog/monolog/.php_cs
opt/app/vendor/monolog/monolog/CHANGELOG.md
opt/app/vendor/monolog/monolog/LICENSE
@@ -3336,10 +3465,10 @@ opt/app/vendor/pragmarx/google2fa-laravel/src/config/config.php
opt/app/vendor/pragmarx/google2fa-laravel/tests/spec/Support/AuthenticatorSpec.php
opt/app/vendor/pragmarx/google2fa-laravel/upgrading.md
opt/app/vendor/pragmarx/google2fa/LICENSE
opt/app/vendor/pragmarx/google2fa/README.md
opt/app/vendor/pragmarx/google2fa/changelog.md
opt/app/vendor/pragmarx/google2fa/composer.json
opt/app/vendor/pragmarx/google2fa/docs/playground.jpg
opt/app/vendor/pragmarx/google2fa/readme.md
opt/app/vendor/pragmarx/google2fa/src/Exceptions/IncompatibleWithGoogleAuthenticatorException.php
opt/app/vendor/pragmarx/google2fa/src/Exceptions/InvalidCharactersException.php
opt/app/vendor/pragmarx/google2fa/src/Exceptions/SecretKeyTooShortException.php
@@ -3348,6 +3477,7 @@ opt/app/vendor/pragmarx/google2fa/src/Support/Base32.php
opt/app/vendor/pragmarx/google2fa/src/Support/Constants.php
opt/app/vendor/pragmarx/google2fa/src/Support/QRCode.php
opt/app/vendor/pragmarx/google2fa/src/Support/Url.php
opt/app/vendor/pragmarx/google2fa/tests/Constants.php
opt/app/vendor/pragmarx/google2fa/tests/Google2FATest.php
opt/app/vendor/pragmarx/google2fa/tests/bootstrap.php
opt/app/vendor/pragmarx/google2fa/upgrading.md
@@ -3382,6 +3512,9 @@ opt/app/vendor/ramsey/uuid/CONTRIBUTING.md
opt/app/vendor/ramsey/uuid/LICENSE
opt/app/vendor/ramsey/uuid/README.md
opt/app/vendor/ramsey/uuid/composer.json
opt/app/vendor/ramsey/uuid/docs/Makefile
opt/app/vendor/ramsey/uuid/docs/conf.py
opt/app/vendor/ramsey/uuid/docs/index.rst
opt/app/vendor/ramsey/uuid/src/BinaryUtils.php
opt/app/vendor/ramsey/uuid/src/Builder/DefaultUuidBuilder.php
opt/app/vendor/ramsey/uuid/src/Builder/DegradedUuidBuilder.php
@@ -4049,6 +4182,8 @@ opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_7
opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_8.txt
opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_9.txt
opt/app/vendor/symfony/console/Tests/Fixtures/TestCommand.php
opt/app/vendor/symfony/console/Tests/Fixtures/TestTiti.php
opt/app/vendor/symfony/console/Tests/Fixtures/TestToto.php
opt/app/vendor/symfony/console/Tests/Fixtures/application_1.json
opt/app/vendor/symfony/console/Tests/Fixtures/application_1.md
opt/app/vendor/symfony/console/Tests/Fixtures/application_1.txt
@@ -4335,6 +4470,8 @@ opt/app/vendor/symfony/debug/Tests/Fixtures/reallyNotPsr0.php
opt/app/vendor/symfony/debug/Tests/Fixtures2/RequiredTwice.php
opt/app/vendor/symfony/debug/Tests/HeaderMock.php
opt/app/vendor/symfony/debug/Tests/MockExceptionHandler.php
opt/app/vendor/symfony/debug/Tests/phpt/debug_class_loader.phpt
opt/app/vendor/symfony/debug/Tests/phpt/decorate_exception_hander.phpt
opt/app/vendor/symfony/debug/Tests/phpt/exception_rethrown.phpt
opt/app/vendor/symfony/debug/Tests/phpt/fatal_with_nested_handlers.phpt
opt/app/vendor/symfony/debug/composer.json
@@ -4751,6 +4888,8 @@ opt/app/vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/SaveSessionListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/SessionListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php
opt/app/vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php
@@ -4962,6 +5101,7 @@ opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php
opt/app/vendor/symfony/routing/Tests/Fixtures/CustomCompiledRoute.php
opt/app/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php
opt/app/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php
opt/app/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/AnonymousClassInTrait.php
opt/app/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php
opt/app/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php
opt/app/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php
@@ -6161,7 +6301,6 @@ opt/app/vendor/watson/validating/src/ValidatingModel.php
opt/app/vendor/watson/validating/src/ValidatingObserver.php
opt/app/vendor/watson/validating/src/ValidatingTrait.php
opt/app/vendor/watson/validating/src/ValidationException.php
opt/app/webpack.mix.js
proc/cpuinfo
sandstorm-http-bridge
sandstorm-http-bridge-config

View File

@@ -15,8 +15,8 @@ const pkgdef :Spk.PackageDefinition = (
manifest = (
appTitle = (defaultText = "Firefly III"),
appVersion = 6,
appMarketingVersion = (defaultText = "4.6.12"),
appVersion = 8,
appMarketingVersion = (defaultText = "4.7.0"),
actions = [
# Define your "new document" handlers here.

View File

@@ -57,6 +57,7 @@ http {
fastcgi_index index.php;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_read_timeout 900;
fastcgi_param QUERY_STRING $query_string;

View File

@@ -2,6 +2,7 @@
# When you change this file, you must take manual action. Read this doc:
# - https://docs.sandstorm.io/en/latest/vagrant-spk/customizing/#setupsh
echo "Now in setup.sh"
set -euo pipefail
@@ -14,8 +15,11 @@ apt-get install -y python-software-properties software-properties-common
# install all languages
sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# id_ID.UTF-8 UTF-8/id_ID.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# nl_NL.UTF-8 UTF-8/nl_NL.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# pl_PL.UTF-8 UTF-8/pl_PL.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# ru_RU.UTF-8 UTF-8/ru_RU.UTF-8 UTF-8/g' /etc/locale.gen
sed -i 's/# tr_TR.UTF-8 UTF-8/tr_TR.UTF-8 UTF-8/g' /etc/locale.gen
dpkg-reconfigure --frontend=noninteractive locales

View File

@@ -1,51 +1,58 @@
# .scrutinizer.yml
tools:
external_code_coverage: false
filter:
paths:
---
build:
nodes:
analysis:
project_setup:
override: true
tests:
override:
- php-scrutinizer-run
checks:
javascript: true
php:
align_assignments: true
avoid_fixme_comments: true
avoid_multiple_statements_on_same_line: true
avoid_perl_style_comments: true
avoid_todo_comments: true
duplication: false
encourage_single_quotes: true
newline_at_end_of_file: true
no_goto: true
no_long_variable_names:
maximum: "20"
no_short_method_names:
minimum: "3"
no_short_variable_names:
minimum: "3"
optional_parameters_at_the_end: true
parameter_doc_comments: true
remove_extra_empty_lines: true
return_doc_comment_if_not_inferrable: true
return_doc_comments: true
uppercase_constants: true
use_self_instead_of_fqcn: true
coding_style:
php:
spaces:
around_operators:
concatenation: true
other:
after_type_cast: false
filter:
excluded_paths:
- database/migrations/*
- bootstrap/*
- config/*
- docker/*
- public/js/lib/*
- public/lib/adminlte/js/*
- public/lib/bootstrap/js/*
- resources/*
- routes/*
- storage/*
paths:
- app/*
- public/js/ff/*
excluded_paths:
- "database/migrations/*"
- "bootstrap/*"
- "config/*"
- "docker/*"
- "public/js/lib/*"
- "public/lib/adminlte/js/*"
- "public/lib/bootstrap/js/*"
- "resources/*"
- "routes/*"
- "storage/*"
checks:
php:
use_self_instead_of_fqcn: true
uppercase_constants: true
return_doc_comments: true
return_doc_comment_if_not_inferrable: true
remove_extra_empty_lines: true
parameter_doc_comments: true
optional_parameters_at_the_end: true
no_short_variable_names:
minimum: '3'
no_short_method_names:
minimum: '3'
no_long_variable_names:
maximum: '20'
no_goto: true
newline_at_end_of_file: true
encourage_single_quotes: true
avoid_todo_comments: true
avoid_perl_style_comments: true
avoid_fixme_comments: true
avoid_multiple_statements_on_same_line: true
align_assignments: true
duplication: false
javascript: true
coding_style:
php:
spaces:
around_operators:
concatenation: true
other:
after_type_cast: false
tools:
external_code_coverage: false

View File

@@ -24,6 +24,7 @@ script:
after_success:
- travis_retry php vendor/bin/php-coveralls -x storage/build/clover-all.xml
- bash <(curl -s https://codecov.io/bash) -f storage/build/clover-all.xml
# safelist
branches:

View File

@@ -1,85 +0,0 @@
# Firefly III: A personal finances manager
[![Requires PHP7.1](https://img.shields.io/badge/php-7.1-red.svg)](https://secure.php.net/downloads.php) [![Latest Stable Version](https://poser.pugx.org/grumpydictator/firefly-iii/v/stable)](https://packagist.org/packages/grumpydictator/firefly-iii) [![License](https://img.shields.io/badge/license-GPL-lightgrey.svg)](https://www.gnu.org/licenses/gpl.html) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA)
[![The index of Firefly III](https://firefly-iii.org/static/screenshots/4.6.12/tiny/index.png)](https://firefly-iii.org/static/screenshots/4.6.12/index.png) [![The account overview of Firefly III](https://firefly-iii.org/static/screenshots/4.6.12/tiny/account.png)](https://firefly-iii.org/static/screenshots/4.6.12/account.png)
[![Overview of all budgets](https://firefly-iii.org/static/screenshots/4.6.12/tiny/budget.png)](https://firefly-iii.org/static/screenshots/4.6.12/budget.png) [![Overview of a category](https://firefly-iii.org/static/screenshots/4.6.12/tiny/category.png)](https://firefly-iii.org/static/screenshots/4.6.12/category.png)
[![View of a report](https://firefly-iii.org/static/screenshots/4.6.12/tiny/report1.png)](https://firefly-iii.org/static/screenshots/4.6.12/report1.png) [![View of another report](https://firefly-iii.org/static/screenshots/4.6.12/tiny/report2.png)](https://firefly-iii.org/static/screenshots/4.6.12/report2.png)
"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.
## Want to try Firefly III?
There is a **[demo site](https://demo.firefly-iii.org)** with an example financial administration already present. You can use Docker, Heroku or Sandstorm.io (see below) to quickly setup your own instance.
## Install Firefly III
### Using docker
You can use docker-compose to [set up your personal secure](https://firefly-iii.org/using-docker.html) Firefly III environment. Advanced users can use the Dockerfile directly.
### Using vagrant (or other VMs)
You can install Firefly III on any Linux or Windows machine. You'll need a web server (preferrably on Linux) and access to the command line. Please read the [installation guide](https://firefly-iii.org/using-installing.html).
### Using Heroku
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/firefly-iii/firefly-iii/tree/master)
Register for a free Heroku account and instantly run Firefly III on your very own cloud instance.
### Using Sandstorm.io
You can find Firefly III in [the Sandstorm.io marketplace](https://apps.sandstorm.io/app/uws252ya9mep4t77tevn85333xzsgrpgth8q4y1rhknn1hammw70). You can run it on your own installation or on Oasis.
## 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.
Firefly works on the principle that if you know where you're money is going, you can stop it from going there.
### Some advantages of using Firefly
- Firefly can import any CSV file, so migrating from other systems is easy.
- Firefly runs on your own server, so you are fully in control of your data. Remember, there is no such thing as "the cloud", its just somebody elses computer!
- 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 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 website](https://firefly-iii.org/).
### Contributing
Please read [CONTRIBUTING.md](https://github.com/firefly-iii/firefly-iii/blob/master/.github/CONTRIBUTING.md) for details on contributing, and the process for submitting pull requests. Please check out the [code of conduct](https://github.com/firefly-iii/firefly-iii/blob/master/CODE_OF_CONDUCT.md) as well.
### Versioning
We use [SemVer](http://semver.org/) for versioning. For the versions available, see [the tags](https://github.com/firefly-iii/firefly-iii/tags) on this repository.
### Authors
* James Cole
* Over time, [many people have contributed to Firefly III](https://github.com/firefly-iii/firefly-iii/graphs/contributors).
### License
This work [is licensed](https://github.com/firefly-iii/firefly-iii/blob/master/LICENSE) under the [GPL v3](https://www.gnu.org/licenses/gpl.html).
### Other stuff
If you like Firefly III and if it helps you save lots of money, why not send me [a dime for every dollar saved](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) (this is a joke, although the Paypal form works just fine, try it!)
If you want to contact me, please open an issue or [email me](mailto:thegrumpydictator@gmail.com).
### Alternatives
If you are looking for alternatives, check out [Kickball's Awesome-Selfhosted list](https://github.com/Kickball/awesome-selfhosted) which features not only Firefly III but also noteworthy alternatives such as [Silverstrike](https://github.com/agstrike/silverstrike).
[![Build Status](https://travis-ci.org/firefly-iii/firefly-iii.svg?branch=master)](https://travis-ci.org/firefly-iii/firefly-iii) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/?branch=master) [![Coverage Status](https://coveralls.io/repos/github/firefly-iii/firefly-iii/badge.svg?branch=master)](https://coveralls.io/github/firefly-iii/firefly-iii?branch=master)

View File

@@ -178,7 +178,7 @@ class CreateImport extends Command
$cwd = getcwd();
$validTypes = config('import.options.file.import_formats');
$type = strtolower($this->option('type'));
if (null === $user->id) {
if (null === $user) {
$this->error(sprintf('There is no user with ID %d.', $this->option('user')));
return false;

View File

@@ -146,6 +146,7 @@ class UpgradeDatabase extends Command
// both 0? set to default currency:
if (0 === $accountCurrency && 0 === $obCurrency) {
AccountMeta::where('account_id', $account->id)->where('name', 'currency_id')->forceDelete();
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));
@@ -218,8 +219,8 @@ class UpgradeDatabase extends Command
}
// when mismatch in transaction:
if ($transaction->transaction_currency_id !== $currency->id) {
$transaction->foreign_currency_id = $transaction->transaction_currency_id;
if (!(intval($transaction->transaction_currency_id) === intval($currency->id))) {
$transaction->foreign_currency_id = intval($transaction->transaction_currency_id);
$transaction->foreign_amount = $transaction->amount;
$transaction->transaction_currency_id = $currency->id;
$transaction->save();
@@ -401,24 +402,24 @@ class UpgradeDatabase extends Command
// has no currency ID? Must have, so fill in using account preference:
if (null === $transaction->transaction_currency_id) {
$transaction->transaction_currency_id = $currency->id;
$transaction->transaction_currency_id = intval($currency->id);
Log::debug(sprintf('Transaction #%d has no currency setting, now set to %s', $transaction->id, $currency->code));
$transaction->save();
}
// 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 && null === $transaction->foreign_amount) {
if (!(intval($transaction->transaction_currency_id) === intval($currency->id)) && 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 #%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->transaction_currency_id = intval($currency->id);
$transaction->save();
}
@@ -436,7 +437,7 @@ class UpgradeDatabase extends Command
}
// 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) {
if (intval($opposingCurrency->id) === intval($currency->id)) {
// update both transactions to match:
$transaction->foreign_amount = null;
$transaction->foreign_currency_id = null;
@@ -450,7 +451,7 @@ class UpgradeDatabase extends Command
return;
}
// if destination account currency is different, both transactions must have this currency as foreign currency id.
if ($opposingCurrency->id !== $currency->id) {
if (!(intval($opposingCurrency->id) === intval($currency->id))) {
$transaction->foreign_currency_id = $opposingCurrency->id;
$opposing->foreign_currency_id = $opposingCurrency->id;
$transaction->save();
@@ -500,4 +501,5 @@ class UpgradeDatabase extends Command
return;
}
}

View File

@@ -36,7 +36,7 @@ trait VerifiesAccessToken
/**
* Abstract method to make sure trait knows about method "option".
*
* @param null $key
* @param string|null $key
*
* @return mixed
*/
@@ -55,7 +55,7 @@ trait VerifiesAccessToken
$repository = app(UserRepositoryInterface::class);
$user = $repository->find($userId);
if (null === $user->id) {
if (null === $user) {
Log::error(sprintf('verifyAccessToken(): no such user for input "%d"', $userId));
return false;

View File

@@ -52,7 +52,7 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
*/
public function __construct()
{
// @var AttachmentRepositoryInterface repository
/** @var AttachmentRepositoryInterface repository */
$this->repository = app(AttachmentRepositoryInterface::class);
// make storage:
$this->uploadDisk = Storage::disk('upload');

View File

@@ -26,6 +26,7 @@ use Carbon\Carbon;
use FireflyIII\Generator\Report\ReportGeneratorInterface;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use Illuminate\Support\Collection;
use Steam;
@@ -173,7 +174,7 @@ class MonthReportGenerator implements ReportGeneratorInterface
$startBalance = $dayBeforeBalance;
$currency = $currencyRepos->find(intval($account->getMeta('currency_id')));
// @var Transaction $journal
/** @var Transaction $transaction */
foreach ($journals as $transaction) {
$transaction->before = $startBalance;
$transactionAmount = $transaction->transaction_amount;

View File

@@ -194,7 +194,9 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
*/
private function summarizeByBudget(Collection $collection): array
{
$result = [];
$result = [
'sum' => '0',
];
/** @var Transaction $transaction */
foreach ($collection as $transaction) {
$jrnlBudId = intval($transaction->transaction_journal_budget_id);
@@ -202,6 +204,7 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
$budgetId = max($jrnlBudId, $transBudId);
$result[$budgetId] = $result[$budgetId] ?? '0';
$result[$budgetId] = bcadd($transaction->transaction_amount, $result[$budgetId]);
$result['sum'] = bcadd($result['sum'], $transaction->transaction_amount);
}
return $result;

View File

@@ -102,7 +102,12 @@ class Support
*/
protected function getObjectSummary(array $spent, array $earned): array
{
$return = [];
$return = [
'sum' => [
'spent' => '0',
'earned' => '0',
],
];
/**
* @var int
@@ -110,10 +115,11 @@ class Support
*/
foreach ($spent as $objectId => $entry) {
if (!isset($return[$objectId])) {
$return[$objectId] = ['spent' => 0, 'earned' => 0];
$return[$objectId] = ['spent' => '0', 'earned' => '0'];
}
$return[$objectId]['spent'] = $entry;
$return['sum']['spent'] = bcadd($return['sum']['spent'], $entry);
}
unset($entry);
@@ -123,10 +129,11 @@ class Support
*/
foreach ($earned as $objectId => $entry) {
if (!isset($return[$objectId])) {
$return[$objectId] = ['spent' => 0, 'earned' => 0];
$return[$objectId] = ['spent' => '0', 'earned' => '0'];
}
$return[$objectId]['earned'] = $entry;
$return['sum']['earned'] = bcadd($return['sum']['earned'], $entry);
}
return $return;
@@ -139,12 +146,15 @@ class Support
*/
protected function summarizeByAccount(Collection $collection): array
{
$result = [];
$result = [
'sum' => '0',
];
/** @var Transaction $transaction */
foreach ($collection as $transaction) {
$accountId = $transaction->account_id;
$result[$accountId] = $result[$accountId] ?? '0';
$result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]);
$result['sum'] = bcadd($result['sum'], $transaction->transaction_amount);
}
return $result;

View File

@@ -25,11 +25,11 @@ namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\RegisteredUser;
use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Events\UserChangedEmail;
use FireflyIII\Factories\RoleFactory;
use FireflyIII\Mail\ConfirmEmailChangeMail;
use FireflyIII\Mail\RegisteredUser as RegisteredUserMail;
use FireflyIII\Mail\RequestedNewPassword as RequestedNewPasswordMail;
use FireflyIII\Mail\UndoEmailChangeMail;
use FireflyIII\Models\Role;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Auth\Events\Login;
@@ -74,11 +74,12 @@ class UserEventHandler
*/
public function checkSingleUserIsAdmin(Login $event): bool
{
Log::debug('In checkSingleUserIsAdmin');
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
/** @var User $user */
$user = $event->user;
$count = User::count();
$count = $repository->count();
if ($count > 1) {
// if more than one user, do nothing.
@@ -93,17 +94,16 @@ class UserEventHandler
return true;
}
// user is the only user but does not have role "owner".
$role = Role::where('name', 'owner')->first();
$role = $repository->getRole('owner');
if (is_null($role)) {
// create role, does not exist. Very strange situation so let's raise a big fuss about it.
$role = Role::create(['name' => 'owner', 'display_name' => 'Site Owner', 'description' => 'User runs this instance of FF3']);
$role = $repository->createRole('owner', 'Site Owner', 'User runs this instance of FF3');
Log::error('Could not find role "owner". This is weird.');
}
Log::info(sprintf('Gave user #%d role #%d ("%s")', $user->id, $role->id, $role->name));
// give user the role
$user->attachRole($role);
$user->save();
$repository->attachRole($user, 'owner');
return true;
}

View File

@@ -25,6 +25,9 @@ namespace FireflyIII\Handlers\Events;
use FireflyConfig;
use FireflyIII\Events\RequestedVersionCheckStatus;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Services\Github\Object\Release;
use FireflyIII\Services\Github\Request\UpdateRequest;
use FireflyIII\User;
use Log;
@@ -45,6 +48,7 @@ class VersionCheckEventHandler
return;
}
/** @var User $user */
$user = $event->user;
if (!$user->hasRole('owner')) {
@@ -55,7 +59,7 @@ class VersionCheckEventHandler
$lastCheckTime = FireflyConfig::get('last_update_check', time());
$now = time();
if ($now - $lastCheckTime->data < 604800) {
Log::debug('Checked for updates less than a week ago.');
Log::debug(sprintf('Checked for updates less than a week ago (on %s).', date('Y-m-d H:i:s', $lastCheckTime->data)));
return;
@@ -71,8 +75,42 @@ class VersionCheckEventHandler
return;
}
// actually check for update and inform the user.
$current = config('firefly.version');
/** @var UpdateRequest $request */
$request = app(UpdateRequest::class);
$check = -2;
$first = new Release(['id' => '0', 'title' => '0', 'updated' => '2017-01-01', 'content' => '']);
try {
$request->call();
$releases = $request->getReleases();
// first entry should be the latest entry:
/** @var Release $first */
$first = reset($releases);
$check = version_compare($current, $first->getTitle());
FireflyConfig::set('last_update_check', time());
} catch (FireflyException $e) {
Log::error(sprintf('Could not check for updates: %s', $e->getMessage()));
}
$string = 'no result: ' . $check;
if ($check === -2) {
$string = strval(trans('firefly.update_check_error'));
}
if ($check === -1) {
// there is a new FF version!
$monthAndDayFormat = (string)trans('config.month_and_day');
$string = strval(
trans(
'firefly.update_new_version_alert',
['your_version' => $current, 'new_version' => $first->getTitle(), 'date' => $first->getUpdated()->formatLocalized($monthAndDayFormat)]
)
);
}
if ($check !== 0) {
// flash info
session()->flash('info', $string);
}
return;
}
}

View File

@@ -37,6 +37,7 @@ use FireflyIII\Models\Category;
use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\JoinClause;
@@ -232,7 +233,7 @@ class JournalCollector implements JournalCollectorInterface
$countQuery->getQuery()->groups = null;
$countQuery->getQuery()->orders = null;
$countQuery->groupBy('accounts.user_id');
$this->count = $countQuery->count();
$this->count = intval($countQuery->count());
return $this->count;
}
@@ -243,6 +244,17 @@ class JournalCollector implements JournalCollectorInterface
public function getJournals(): Collection
{
$this->run = true;
// find query set in cache.
$hash = hash('sha256', $this->query->toSql() . serialize($this->query->getBindings()));
$key = 'query-' . substr($hash, -8);
$cache = new CacheProperties;
$cache->addProperty($key);
if ($cache->has()) {
Log::debug(sprintf('Return cache of query with ID "%s".', $key));
return $cache->get(); // @codeCoverageIgnore
}
/** @var Collection $set */
$set = $this->query->get(array_values($this->fields));
@@ -263,6 +275,8 @@ class JournalCollector implements JournalCollectorInterface
$transaction->opposing_account_iban = app('steam')->tryDecrypt($transaction->opposing_account_iban);
}
);
Log::debug(sprintf('Cached query with ID "%s".', $key));
$cache->store($set);
return $set;
}
@@ -773,7 +787,8 @@ class JournalCollector implements JournalCollectorInterface
$this->query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id');
$this->query->leftJoin('categories as transaction_categories', 'transaction_categories.id', '=', 'category_transaction.category_id');
$this->query->whereNull('transaction_journal_categories.deleted_at');
$this->query->whereNull('transaction_categories.deleted_at');
$this->fields[] = 'category_transaction_journal.category_id as transaction_journal_category_id';
$this->fields[] = 'transaction_journal_categories.encrypted as transaction_journal_category_encrypted';
$this->fields[] = 'transaction_journal_categories.name as transaction_journal_category_name';

View File

@@ -27,6 +27,7 @@ use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Tag;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;

View File

@@ -136,7 +136,7 @@ class PopupReport implements PopupReportInterface
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($attributes['accounts'])->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
->setRange($attributes['startDate'], $attributes['endDate'])
->setRange($attributes['startDate'], $attributes['endDate'])->withOpposingAccount()
->setCategory($category);
$journals = $collector->getJournals();

View File

@@ -29,6 +29,7 @@ use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Requests\AccountFormRequest;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Note;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
@@ -38,7 +39,6 @@ use FireflyIII\Support\CacheProperties;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Log;
use Preferences;
use Steam;
use View;
@@ -50,6 +50,13 @@ use View;
*/
class AccountController extends Controller
{
/** @var CurrencyRepositoryInterface */
private $currencyRepos;
/** @var JournalRepositoryInterface */
private $journalRepos;
/** @var AccountRepositoryInterface */
private $repository;
/**
*
*/
@@ -63,6 +70,10 @@ class AccountController extends Controller
app('view')->share('mainTitleIcon', 'fa-credit-card');
app('view')->share('title', trans('firefly.accounts'));
$this->repository = app(AccountRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
$this->journalRepos = app(JournalRepositoryInterface::class);
return $next($request);
}
);
@@ -76,9 +87,7 @@ class AccountController extends Controller
*/
public function create(Request $request, string $what = 'asset')
{
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$allCurrencies = $repository->get();
$allCurrencies = $this->currencyRepos->get();
$currencySelectList = ExpandedForm::makeSelectList($allCurrencies);
$defaultCurrency = app('amount')->getDefaultCurrency();
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
@@ -101,16 +110,15 @@ class AccountController extends Controller
}
/**
* @param AccountRepositoryInterface $repository
* @param Account $account
* @param Account $account
*
* @return View
*/
public function delete(AccountRepositoryInterface $repository, Account $account)
public function delete(Account $account)
{
$typeName = config('firefly.shortNamesByFullName.' . $account->accountType->type);
$subTitle = trans('firefly.delete_' . $typeName . '_account', ['name' => $account->name]);
$accountList = ExpandedForm::makeSelectListWithEmpty($repository->getAccountsByType([$account->accountType->type]));
$accountList = ExpandedForm::makeSelectListWithEmpty($this->repository->getAccountsByType([$account->accountType->type]));
unset($accountList[$account->id]);
// put previous url in session
@@ -126,14 +134,14 @@ class AccountController extends Controller
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function destroy(Request $request, AccountRepositoryInterface $repository, Account $account)
public function destroy(Request $request, Account $account)
{
$type = $account->accountType->type;
$typeName = config('firefly.shortNamesByFullName.' . $type);
$name = $account->name;
$moveTo = $repository->find(intval($request->get('move_account_before_delete')));
$moveTo = $this->repository->find(intval($request->get('move_account_before_delete')));
$repository->destroy($account, $moveTo);
$this->repository->destroy($account, $moveTo);
$request->session()->flash('success', strval(trans('firefly.' . $typeName . '_deleted', ['name' => $name])));
Preferences::mark();
@@ -153,17 +161,13 @@ class AccountController extends Controller
* @return View
*
* @throws FireflyException
* @throws FireflyException
* @throws FireflyException
*/
public function edit(Request $request, Account $account)
{
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$what = config('firefly.shortNamesByFullName')[$account->accountType->type];
$subTitle = trans('firefly.edit_' . $what . '_account', ['name' => $account->name]);
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
$allCurrencies = $repository->get();
$allCurrencies = $this->currencyRepos->get();
$currencySelectList = ExpandedForm::makeSelectList($allCurrencies);
$roles = [];
foreach (config('firefly.accountRoles') as $role) {
@@ -183,7 +187,7 @@ class AccountController extends Controller
$openingBalanceAmount = '0' === $account->getOpeningBalanceAmount() ? '' : $openingBalanceAmount;
$openingBalanceDate = $account->getOpeningBalanceDate();
$openingBalanceDate = 1900 === $openingBalanceDate->year ? null : $openingBalanceDate->format('Y-m-d');
$currency = $repository->find(intval($account->getMeta('currency_id')));
$currency = $this->currencyRepos->find(intval($account->getMeta('currency_id')));
$preFilled = [
'accountNumber' => $account->getMeta('accountNumber'),
@@ -195,7 +199,15 @@ class AccountController extends Controller
'openingBalance' => $openingBalanceAmount,
'virtualBalance' => $account->virtual_balance,
'currency_id' => $currency->id,
'notes' => '',
];
/** @var Note $note */
$note = $this->repository->getNote($account);
if (null !== $note) {
$preFilled['notes'] = $note->text;
}
$request->session()->flash('preFilled', $preFilled);
return view(
@@ -215,19 +227,18 @@ class AccountController extends Controller
}
/**
* @param Request $request
* @param AccountRepositoryInterface $repository
* @param string $what
* @param Request $request
* @param string $what
*
* @return View
*/
public function index(Request $request, AccountRepositoryInterface $repository, string $what)
public function index(Request $request, string $what)
{
$what = $what ?? 'asset';
$subTitle = trans('firefly.' . $what . '_accounts');
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
$types = config('firefly.accountTypesByIdentifier.' . $what);
$collection = $repository->getAccountsByType($types);
$collection = $this->repository->getAccountsByType($types);
$total = $collection->count();
$page = 0 === intval($request->get('page')) ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
@@ -263,10 +274,9 @@ class AccountController extends Controller
/**
* Show an account.
*
* @param Request $request
* @param JournalRepositoryInterface $repository
* @param Account $account
* @param string $moment
* @param Request $request
* @param Account $account
* @param string $moment
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*
@@ -275,23 +285,21 @@ class AccountController extends Controller
*
* @throws FireflyException
*/
public function show(Request $request, JournalRepositoryInterface $repository, Account $account, string $moment = '')
public function show(Request $request, Account $account, string $moment = '')
{
if (AccountType::INITIAL_BALANCE === $account->accountType->type) {
return $this->redirectToOriginalAccount($account);
}
/** @var CurrencyRepositoryInterface $currencyRepos */
$currencyRepos = app(CurrencyRepositoryInterface::class);
$range = Preferences::get('viewRange', '1M')->data;
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$page = intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$chartUri = route('chart.account.single', [$account->id]);
$start = null;
$end = null;
$periods = new Collection;
$currencyId = intval($account->getMeta('currency_id'));
$currency = $currencyRepos->find($currencyId);
$range = Preferences::get('viewRange', '1M')->data;
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$page = intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$chartUri = route('chart.account.single', [$account->id]);
$start = null;
$end = null;
$periods = new Collection;
$currencyId = intval($account->getMeta('currency_id'));
$currency = $this->currencyRepos->find($currencyId);
if (0 === $currencyId) {
$currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore
}
@@ -300,7 +308,7 @@ class AccountController extends Controller
if ('all' === $moment) {
$subTitle = trans('firefly.all_journals_for_account', ['name' => $account->name]);
$chartUri = route('chart.account.all', [$account->id]);
$first = $repository->first();
$first = $this->journalRepos->first();
$start = $first->date ?? new Carbon;
$end = new Carbon;
}
@@ -308,12 +316,13 @@ class AccountController extends Controller
// prep for "specific date" view.
if (strlen($moment) > 0 && 'all' !== $moment) {
$start = new Carbon($moment);
$start = app('navigation')->startOfPeriod($start, $range);
$end = app('navigation')->endOfPeriod($start, $range);
$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);
$periods = $this->getPeriodOverview($account, $start);
}
// prep for current period view
@@ -323,7 +332,7 @@ class AccountController extends Controller
$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);
$periods = $this->getPeriodOverview($account, null);
}
// grab journals:
@@ -342,15 +351,14 @@ class AccountController extends Controller
}
/**
* @param AccountFormRequest $request
* @param AccountRepositoryInterface $repository
* @param AccountFormRequest $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function store(AccountFormRequest $request, AccountRepositoryInterface $repository)
public function store(AccountFormRequest $request)
{
$data = $request->getAccountData();
$account = $repository->store($data);
$account = $this->repository->store($data);
$request->session()->flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name])));
Preferences::mark();
@@ -381,10 +389,10 @@ class AccountController extends Controller
*
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function update(AccountFormRequest $request, AccountRepositoryInterface $repository, Account $account)
public function update(AccountFormRequest $request, Account $account)
{
$data = $request->getAccountData();
$repository->update($account, $data);
$this->repository->update($account, $data);
$request->session()->flash('success', strval(trans('firefly.updated_account', ['name' => $account->name])));
Preferences::mark();
@@ -426,56 +434,55 @@ class AccountController extends Controller
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
private function getPeriodOverview(Account $account): Collection
private function getPeriodOverview(Account $account, ?Carbon $date): Collection
{
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$start = $repository->oldestJournalDate($account);
$range = Preferences::get('viewRange', '1M')->data;
$start = app('navigation')->startOfPeriod($start, $range);
$end = app('navigation')->endOfX(new Carbon, $range, null);
$entries = new Collection;
$count = 0;
$range = Preferences::get('viewRange', '1M')->data;
$start = $this->repository->oldestJournalDate($account);
$end = $date ?? new Carbon;
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('account-show-period-entries');
$cache->addProperty($account->id);
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
Log::debug('Going to get period expenses and incomes.');
while ($end >= $start && $count < 90) {
$end = app('navigation')->startOfPeriod($end, $range);
$currentEnd = app('navigation')->endOfPeriod($end, $range);
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = new Collection;
// loop dates
foreach ($dates as $date) {
// 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($date['start'], $date['end'])->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();
$spent = strval($collector->getJournals()->sum('transaction_amount'));
$dateStr = $end->format('Y-m-d');
$dateName = app('navigation')->periodShow($end, $range);
$collector->setAccounts(new Collection([$account]))->setRange($date['start'], $date['end'])->setTypes([TransactionType::WITHDRAWAL])
->withOpposingAccount();
$spent = strval($collector->getJournals()->sum('transaction_amount'));
$dateStr = $date['end']->format('Y-m-d');
$dateName = app('navigation')->periodShow($date['start'], $date['period']);
$entries->push(
[
'string' => $dateStr,
'name' => $dateName,
'spent' => $spent,
'earned' => $earned,
'date' => clone $end,]
'date' => clone $date['end'],]
);
$end = app('navigation')->subtractPeriod($end, $range, 1);
++$count;
}
$cache->store($entries);
return $entries;

View File

@@ -125,7 +125,7 @@ class UserController extends Controller
$list = ['twoFactorAuthEnabled', 'twoFactorAuthSecret'];
$preferences = Preferences::getArrayForUser($user, $list);
$user->isAdmin = $user->hasRole('owner');
$is2faEnabled = true === $preferences['twoFactorAuthEnabled'];
$is2faEnabled = 1 === $preferences['twoFactorAuthEnabled'];
$has2faSecret = null !== $preferences['twoFactorAuthSecret'];
$user->has2FA = ($is2faEnabled && $has2faSecret) ? true : false;
$user->prefs = $preferences;

View File

@@ -22,7 +22,6 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use File;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Requests\AttachmentFormRequest;
use FireflyIII\Models\Attachment;
@@ -40,6 +39,9 @@ use View;
*/
class AttachmentController extends Controller
{
/** @var AttachmentRepositoryInterface */
private $repository;
/**
*
*/
@@ -52,6 +54,7 @@ class AttachmentController extends Controller
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-paperclip');
app('view')->share('title', trans('firefly.attachments'));
$this->repository = app(AttachmentRepositoryInterface::class);
return $next($request);
}
@@ -74,17 +77,16 @@ class AttachmentController extends Controller
}
/**
* @param Request $request
* @param AttachmentRepositoryInterface $repository
* @param Attachment $attachment
* @param Request $request
* @param Attachment $attachment
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function destroy(Request $request, AttachmentRepositoryInterface $repository, Attachment $attachment)
public function destroy(Request $request, Attachment $attachment)
{
$name = $attachment->filename;
$repository->destroy($attachment);
$this->repository->destroy($attachment);
$request->session()->flash('success', strval(trans('firefly.attachment_deleted', ['name' => $name])));
Preferences::mark();
@@ -93,17 +95,16 @@ class AttachmentController extends Controller
}
/**
* @param AttachmentRepositoryInterface $repository
* @param Attachment $attachment
* @param Attachment $attachment
*
* @return mixed
*
* @throws FireflyException
*/
public function download(AttachmentRepositoryInterface $repository, Attachment $attachment)
public function download(Attachment $attachment)
{
if ($repository->exists($attachment)) {
$content = $repository->getContent($attachment);
if ($this->repository->exists($attachment)) {
$content = $this->repository->getContent($attachment);
$quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\'));
/** @var LaravelResponse $response */
@@ -145,37 +146,15 @@ class AttachmentController extends Controller
}
/**
* @param Attachment $attachment
*
* @return \Illuminate\Http\Response
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function preview(Attachment $attachment)
{
$image = 'images/page_green.png';
if ('application/pdf' === $attachment->mime) {
$image = 'images/page_white_acrobat.png';
}
$file = public_path($image);
$response = Response::make(File::get($file));
$response->header('Content-Type', 'image/png');
return $response;
}
/**
* @param AttachmentFormRequest $request
* @param AttachmentRepositoryInterface $repository
* @param Attachment $attachment
* @param AttachmentFormRequest $request
* @param Attachment $attachment
*
* @return \Illuminate\Http\RedirectResponse
*/
public function update(AttachmentFormRequest $request, AttachmentRepositoryInterface $repository, Attachment $attachment)
public function update(AttachmentFormRequest $request, Attachment $attachment)
{
$data = $request->getAttachmentData();
$repository->update($attachment, $data);
$this->repository->update($attachment, $data);
$request->session()->flash('success', strval(trans('firefly.attachment_updated', ['name' => $attachment->filename])));
Preferences::mark();
@@ -191,4 +170,25 @@ class AttachmentController extends Controller
// redirect to previous URL.
return redirect($this->getPreviousUri('attachments.edit.uri'));
}
/**
* @param Attachment $attachment
*
* @return \Illuminate\Http\Response
* @throws FireflyException
*/
public function view(Attachment $attachment)
{
if ($this->repository->exists($attachment)) {
$content = $this->repository->getContent($attachment);
return Response::make(
$content, 200, [
'Content-Type' => $attachment->mime,
'Content-Disposition' => 'inline; filename="' . $attachment->filename . '"',
]
);
}
throw new FireflyException('Could not find the indicated attachment. The file is no longer there.');
}
}

View File

@@ -22,8 +22,13 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Auth;
use FireflyConfig;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
/**
* Class ForgotPasswordController
@@ -51,4 +56,57 @@ class ForgotPasswordController extends Controller
parent::__construct();
$this->middleware('guest');
}
/**
* Send a reset link to the given user.
*
* @param \Illuminate\Http\Request $request
*
* @param UserRepositoryInterface $repository
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
public function sendResetLinkEmail(Request $request, UserRepositoryInterface $repository)
{
$this->validateEmail($request);
// 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')]);
}
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->broker()->sendResetLink(
$request->only('email')
);
if ($response == Password::RESET_LINK_SENT) {
return back()->with('status', trans($response));
}
return back()->withErrors(['email' => trans($response)]); // @codeCoverageIgnore
}
/**
* @codeCoverageIgnore
* Display the form to request a password reset link.
*
* @return \Illuminate\Http\Response
*/
public function showLinkRequestForm()
{
// is allowed to?
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$userCount = User::count();
$allowRegistration = true;
if (true === $singleUserMode && $userCount > 0) {
$allowRegistration = false;
}
return view('auth.passwords.email')->with(compact('allowRegistration'));
}
}

View File

@@ -22,8 +22,11 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Auth;
use FireflyConfig;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\User;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
/**
* @codeCoverageIgnore
@@ -52,4 +55,28 @@ class ResetPasswordController extends Controller
parent::__construct();
$this->middleware('guest');
}
/**
* Display the password reset view for the given token.
*
* If no token is present, display the link request form.
*
* @param Request $request
* @param string|null $token
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showResetForm(Request $request, $token = null)
{
// is allowed to?
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$userCount = User::count();
$allowRegistration = true;
if (true === $singleUserMode && $userCount > 0) {
$allowRegistration = false;
}
return view('auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email, 'allowRegistration' => $allowRegistration]
);
}
}

View File

@@ -262,7 +262,7 @@ class BillController extends Controller
}
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, $lastPaidDate);
$hideBill = true;
$subTitle = e($bill->name);
$subTitle = $bill->name;
return view('bills.show', compact('transactions', 'yearAverage', 'overallAverage', 'year', 'hideBill', 'bill', 'subTitle'));
}

View File

@@ -611,21 +611,19 @@ class BudgetController extends Controller
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
Log::debug('Going to get period expenses and incomes.');
while ($end >= $start) {
$end = app('navigation')->startOfPeriod($end, $range);
$currentEnd = app('navigation')->endOfPeriod($end, $range);
$dates = app('navigation')->blockPeriods($start, $end, $range);
foreach ($dates as $date) {
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutBudget()->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL]);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutBudget()->withOpposingAccount()->setTypes(
[TransactionType::WITHDRAWAL]
);
$set = $collector->getJournals();
$sum = strval($set->sum('transaction_amount') ?? '0');
$journals = $set->count();
$dateStr = $end->format('Y-m-d');
$dateName = app('navigation')->periodShow($end, $range);
$entries->push(['string' => $dateStr, 'name' => $dateName, 'count' => $journals, 'sum' => $sum, 'date' => clone $end]);
$end = app('navigation')->subtractPeriod($end, $range, 1);
$dateStr = $date['end']->format('Y-m-d');
$dateName = app('navigation')->periodShow($date['end'], $date['period']);
$entries->push(['string' => $dateStr, 'name' => $dateName, 'count' => $journals, 'sum' => $sum, 'date' => clone $date['end']]);
}
$cache->store($entries);

View File

@@ -46,6 +46,13 @@ use View;
*/
class CategoryController extends Controller
{
/** @var AccountRepositoryInterface */
private $accountRepos;
/** @var JournalRepositoryInterface */
private $journalRepos;
/** @var CategoryRepositoryInterface */
private $repository;
/**
*
*/
@@ -57,6 +64,9 @@ class CategoryController extends Controller
function ($request, $next) {
app('view')->share('title', trans('firefly.categories'));
app('view')->share('mainTitleIcon', 'fa-bar-chart');
$this->journalRepos = app(JournalRepositoryInterface::class);
$this->repository = app(CategoryRepositoryInterface::class);
$this->accountRepos = app(AccountRepositoryInterface::class);
return $next($request);
}
@@ -95,16 +105,15 @@ class CategoryController extends Controller
}
/**
* @param Request $request
* @param CategoryRepositoryInterface $repository
* @param Category $category
* @param Request $request
* @param Category $category
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function destroy(Request $request, CategoryRepositoryInterface $repository, Category $category)
public function destroy(Request $request, Category $category)
{
$name = $category->name;
$repository->destroy($category);
$this->repository->destroy($category);
$request->session()->flash('success', strval(trans('firefly.deleted_category', ['name' => $name])));
Preferences::mark();
@@ -132,21 +141,21 @@ class CategoryController extends Controller
}
/**
* @param CategoryRepositoryInterface $repository
* @param Request $request
*
* @return View
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index(Request $request, CategoryRepositoryInterface $repository)
public function index(Request $request)
{
$page = 0 === intval($request->get('page')) ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$collection = $repository->getCategories();
$collection = $this->repository->getCategories();
$total = $collection->count();
$collection = $collection->slice(($page - 1) * $pageSize, $pageSize);
$collection->each(
function (Category $category) use ($repository) {
$category->lastActivity = $repository->lastUseDate($category, new Collection);
function (Category $category) {
$category->lastActivity = $this->repository->lastUseDate($category, new Collection);
}
);
@@ -158,13 +167,12 @@ class CategoryController extends Controller
}
/**
* @param Request $request
* @param JournalRepositoryInterface $repository
* @param string $moment
* @param Request $request
* @param string $moment
*
* @return View
*/
public function noCategory(Request $request, JournalRepositoryInterface $repository, string $moment = '')
public function noCategory(Request $request, string $moment = '')
{
// default values:
$range = Preferences::get('viewRange', '1M')->data;
@@ -177,27 +185,27 @@ class CategoryController extends Controller
// prep for "all" view.
if ('all' === $moment) {
$subTitle = trans('firefly.all_journals_without_category');
$first = $repository->first();
$first = $this->journalRepos->first();
$start = $first->date ?? new Carbon;
$end = new Carbon;
}
// prep for "specific date" view.
if (strlen($moment) > 0 && 'all' !== $moment) {
$start = new Carbon($moment);
$start = app('navigation')->startOfPeriod(new Carbon($moment), $range);
$end = app('navigation')->endOfPeriod($start, $range);
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
$periods = $this->getNoCategoryPeriodOverview();
$periods = $this->getNoCategoryPeriodOverview($start);
}
// prep for current period
if (0 === strlen($moment)) {
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
$periods = $this->getNoCategoryPeriodOverview();
$periods = $this->getNoCategoryPeriodOverview($start);
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
@@ -248,14 +256,14 @@ class CategoryController extends Controller
// prep for "specific date" view.
if (strlen($moment) > 0 && 'all' !== $moment) {
$start = new Carbon($moment);
$start = app('navigation')->startOfPeriod(new Carbon($moment), $range);
$end = app('navigation')->endOfPeriod($start, $range);
$subTitle = trans(
'firefly.journals_in_period_for_category',
['name' => $category->name,
'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat),]
);
$periods = $this->getPeriodOverview($category);
$periods = $this->getPeriodOverview($category, $start);
$path = route('categories.show', [$category->id, $moment]);
}
@@ -265,7 +273,7 @@ class CategoryController extends Controller
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
/** @var Carbon $end */
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
$periods = $this->getPeriodOverview($category);
$periods = $this->getPeriodOverview($category, $start);
$subTitle = trans(
'firefly.journals_in_period_for_category',
['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
@@ -336,38 +344,36 @@ class CategoryController extends Controller
}
/**
* @param Carbon $theDate
*
* @return Collection
*/
private function getNoCategoryPeriodOverview(): Collection
private function getNoCategoryPeriodOverview(Carbon $theDate): Collection
{
$repository = app(JournalRepositoryInterface::class);
$first = $repository->first();
$start = $first->date ?? new Carbon;
$range = Preferences::get('viewRange', '1M')->data;
$start = app('navigation')->startOfPeriod($start, $range);
$end = app('navigation')->endOfX(new Carbon, $range, null);
$entries = new Collection;
$range = Preferences::get('viewRange', '1M')->data;
$first = $this->journalRepos->first();
$start = $first->date ?? new Carbon;
$end = $theDate ?? new Carbon;
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('no-budget-period-entries');
$cache->addProperty('no-category-period-entries');
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
Log::debug(sprintf('Going to get period expenses and incomes between %s and %s.', $start->format('Y-m-d'), $end->format('Y-m-d')));
while ($end >= $start) {
Log::debug('Loop!');
$end = app('navigation')->startOfPeriod($end, $range);
$currentEnd = app('navigation')->endOfPeriod($end, $range);
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = new Collection;
foreach ($dates as $date) {
// count journals without category in this period:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()
->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$count = $collector->getJournals()->count();
@@ -375,7 +381,7 @@ class CategoryController extends Controller
// amount transferred
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$transferred = Steam::positive($collector->getJournals()->sum('transaction_amount'));
@@ -383,17 +389,20 @@ class CategoryController extends Controller
// amount spent
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL]);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes(
[TransactionType::WITHDRAWAL]
);
$spent = $collector->getJournals()->sum('transaction_amount');
// amount earned
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount()->setTypes([TransactionType::DEPOSIT]);
$earned = $collector->getJournals()->sum('transaction_amount');
$dateStr = $end->format('Y-m-d');
$dateName = app('navigation')->periodShow($end, $range);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes(
[TransactionType::DEPOSIT]
);
$earned = $collector->getJournals()->sum('transaction_amount');
$dateStr = $date['end']->format('Y-m-d');
$dateName = app('navigation')->periodShow($date['end'], $date['period']);
$entries->push(
[
'string' => $dateStr,
@@ -402,10 +411,9 @@ class CategoryController extends Controller
'spent' => $spent,
'earned' => $earned,
'transferred' => $transferred,
'date' => clone $end,
'date' => clone $date['end'],
]
);
$end = app('navigation')->subtractPeriod($end, $range, 1);
}
Log::debug('End of loops');
$cache->store($entries);
@@ -418,45 +426,39 @@ class CategoryController extends Controller
*
* @return Collection
*/
private function getPeriodOverview(Category $category): Collection
private function getPeriodOverview(Category $category, Carbon $date): Collection
{
/** @var CategoryRepositoryInterface $repository */
$repository = app(CategoryRepositoryInterface::class);
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$first = $repository->firstUseDate($category);
if (null === $first) {
$first = new Carbon; // @codeCoverageIgnore
}
$range = Preferences::get('viewRange', '1M')->data;
$first = app('navigation')->startOfPeriod($first, $range);
$end = app('navigation')->endOfX(new Carbon, $range, null);
$entries = new Collection;
$count = 0;
$range = Preferences::get('viewRange', '1M')->data;
$first = $this->journalRepos->first();
$start = $first->date ?? new Carbon;
$end = $date ?? new Carbon;
$accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
// properties for entries with their amounts.
$cache = new CacheProperties();
$cache->addProperty($first);
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($range);
$cache->addProperty('categories.entries');
$cache->addProperty($category->id);
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
}
while ($end >= $first && $count < 90) {
$end = app('navigation')->startOfPeriod($end, $range);
$currentEnd = app('navigation')->endOfPeriod($end, $range);
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $end, $currentEnd);
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $end, $currentEnd);
$dateStr = $end->format('Y-m-d');
$dateName = app('navigation')->periodShow($end, $range);
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = new Collection;
foreach ($dates as $date) {
$spent = $this->repository->spentInPeriod(new Collection([$category]), $accounts, $date['start'], $date['end']);
$earned = $this->repository->earnedInPeriod(new Collection([$category]), $accounts, $date['start'], $date['end']);
$dateStr = $date['end']->format('Y-m-d');
$dateName = app('navigation')->periodShow($date['end'], $date['period']);
// amount transferred
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->setCategory($category)
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->setCategory($category)
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class);
$transferred = Steam::positive($collector->getJournals()->sum('transaction_amount'));
@@ -469,11 +471,9 @@ class CategoryController extends Controller
'earned' => $earned,
'sum' => bcadd($earned, $spent),
'transferred' => $transferred,
'date' => clone $end,
'date' => clone $date['end'],
]
);
$end = app('navigation')->subtractPeriod($end, $range, 1);
++$count;
}
$cache->store($entries);

View File

@@ -76,21 +76,46 @@ class AccountController extends Controller
$repository = app(AccountRepositoryInterface::class);
$start = $repository->oldestJournalDate($account);
$end = new Carbon;
$format = (string)trans('config.month_and_day');
$range = Steam::balanceInRange($account, $start, $end);
$current = clone $start;
$previous = array_values($range)[0];
$chartData = [];
while ($end >= $current) {
$theDate = $current->format('Y-m-d');
$balance = $range[$theDate] ?? $previous;
$label = $current->formatLocalized($format);
$chartData[$label] = $balance;
$previous = $balance;
$current->addDay();
// depending on diff, do something with range of chart.
$step = '1D';
$months = $start->diffInMonths($end);
if ($months > 3) {
$step = '1W';
}
if ($months > 24) {
$step = '1M'; // @codeCoverageIgnore
}
if ($months > 100) {
$step = '1Y'; // @codeCoverageIgnore
}
$chartData = [];
$current = clone $start;
switch ($step) {
case '1D':
$format = (string)trans('config.month_and_day');
$range = Steam::balanceInRange($account, $start, $end);
$previous = array_values($range)[0];
while ($end >= $current) {
$theDate = $current->format('Y-m-d');
$balance = $range[$theDate] ?? $previous;
$label = $current->formatLocalized($format);
$chartData[$label] = floatval($balance);
$previous = $balance;
$current->addDay();
}
break;
case '1W':
case '1M': // @codeCoverageIgnore
case '1Y': // @codeCoverageIgnore
while ($end >= $current) {
$balance = floatval(Steam::balance($account, $current));
$label = app('navigation')->periodShow($current, $step);
$chartData[$label] = $balance;
$current = app('navigation')->addPeriod($current, $step, 1);
}
break;
}
$data = $this->generator->singleSet($account->name, $chartData);
$cache->store($data);

View File

@@ -37,7 +37,6 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Preferences;
use Response;
use Steam;
@@ -78,37 +77,47 @@ class BudgetController extends Controller
*/
public function budget(Budget $budget)
{
$first = $this->repository->firstUseDate($budget);
$range = Preferences::get('viewRange', '1M')->data;
$currentStart = app('navigation')->startOfPeriod($first, $range);
$last = session('end', new Carbon);
$cache = new CacheProperties();
$cache->addProperty($first);
$cache->addProperty($last);
$start = $this->repository->firstUseDate($budget);
$end = session('end', new Carbon);
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('chart.budget.budget');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
$final = clone $last;
$final->addYears(2);
// depending on diff, do something with range of chart.
$step = '1D';
$months = $start->diffInMonths($end);
if ($months > 3) {
$step = '1W';
}
if ($months > 24) {
$step = '1M';
}
if ($months > 60) {
$step = '1Y'; // @codeCoverageIgnore
}
$budgetCollection = new Collection([$budget]);
$last = app('navigation')->endOfX($last, $range, $final); // not to overshoot.
$entries = [];
while ($currentStart < $last) {
// periodspecific dates:
$currentEnd = app('navigation')->endOfPeriod($currentStart, $range);
// sub another day because reasons.
$currentEnd->subDay();
$spent = $this->repository->spentInPeriod($budgetCollection, new Collection, $currentStart, $currentEnd);
$format = app('navigation')->periodShow($currentStart, $range);
$entries[$format] = bcmul($spent, '-1');
$currentStart = clone $currentEnd;
$currentStart->addDays(2);
$chartData = [];
$current = clone $start;
$current = app('navigation')->startOfPeriod($current, $step);
while ($end >= $current) {
$currentEnd = app('navigation')->endOfPeriod($current, $step);
if ($step === '1Y') {
$currentEnd->subDay(); // @codeCoverageIgnore
}
$spent = $this->repository->spentInPeriod($budgetCollection, new Collection, $current, $currentEnd);
$label = app('navigation')->periodShow($current, $step);
$chartData[$label] = floatval(bcmul($spent, '-1'));
$current = clone $currentEnd;
$current->addDay();
}
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $entries);
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
$cache->store($data);

View File

@@ -0,0 +1,174 @@
<?php
/**
* DebugController.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use DB;
use Exception;
use FireflyIII\Http\Middleware\IsDemoUser;
use Illuminate\Http\Request;
use Log;
use Monolog\Handler\RotatingFileHandler;
/**
* Class DebugController
*/
class DebugController extends Controller
{
/**
* HomeController constructor.
*/
public function __construct()
{
parent::__construct();
$this->middleware(IsDemoUser::class);
}
/**
* @param Request $request
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index(Request $request)
{
$search = ['~', '#'];
$replace = ['\~', '# '];
$phpVersion = str_replace($search, $replace, PHP_VERSION);
$phpOs = str_replace($search, $replace, php_uname());
$interface = PHP_SAPI;
$now = Carbon::create()->format('Y-m-d H:i:s e');
$extensions = join(', ', get_loaded_extensions());
$drivers = join(', ', DB::availableDrivers());
$currentDriver = DB::getDriverName();
$userAgent = $request->header('user-agent');
$isSandstorm = var_export(env('IS_SANDSTORM', 'unknown'), true);
$isDocker = var_export(env('IS_DOCKER', 'unknown'), true);
$trustedProxies = env('TRUSTED_PROXIES', '(none)');
$displayErrors = ini_get('display_errors');
$errorReporting = $this->errorReporting(intval(ini_get('error_reporting')));
$appEnv = env('APP_ENV', '');
$appDebug = var_export(env('APP_DEBUG', false), true);
$appLog = env('APP_LOG', '');
$appLogLevel = env('APP_LOG_LEVEL', '');
$packages = $this->collectPackages();
$cacheDriver = env('CACHE_DRIVER', 'unknown');
// get latest log file:
$logger = Log::getMonolog();
$handlers = $logger->getHandlers();
$logContent = '';
foreach ($handlers as $handler) {
if ($handler instanceof RotatingFileHandler) {
$logFile = $handler->getUrl();
if (null !== $logFile) {
try {
$logContent = file_get_contents($logFile);
} catch (Exception $e) {
// don't care
}
}
}
}
// last few lines
$logContent = 'Truncated from this point <----|' . substr($logContent, -4096);
return view(
'debug',
compact(
'phpVersion',
'extensions',
'carbon',
'appEnv',
'appDebug',
'appLog',
'appLogLevel',
'now',
'packages',
'drivers',
'currentDriver',
'userAgent',
'displayErrors',
'errorReporting',
'phpOs',
'interface',
'logContent',
'cacheDriver',
'isDocker',
'isSandstorm',
'trustedProxies'
)
);
}
/**
* Some common combinations.
*
* @param int $value
*
* @return string
*/
protected function errorReporting(int $value): string
{
$array = [
-1 => 'ALL errors',
E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED => 'E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED',
E_ALL => 'E_ALL',
E_ALL & ~E_DEPRECATED & ~E_STRICT => 'E_ALL & ~E_DEPRECATED & ~E_STRICT',
E_ALL & ~E_NOTICE => 'E_ALL & ~E_NOTICE',
E_ALL & ~E_NOTICE & ~E_STRICT => 'E_ALL & ~E_NOTICE & ~E_STRICT',
E_COMPILE_ERROR | E_RECOVERABLE_ERROR | E_ERROR | E_CORE_ERROR => 'E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR',
];
if (isset($array[$value])) {
return $array[$value];
}
return strval($value); // @codeCoverageIgnore
}
/**
* @return array
*/
private function collectPackages(): array
{
$packages = [];
$file = realpath(__DIR__ . '/../../../vendor/composer/installed.json');
if (!($file === false) && file_exists($file)) {
// file exists!
$content = file_get_contents($file);
$json = json_decode($content, true);
foreach ($json as $package) {
$packages[]
= [
'name' => $package['name'],
'version' => $package['version'],
];
}
}
return $packages;
}
}

View File

@@ -24,7 +24,6 @@ namespace FireflyIII\Http\Controllers;
use Artisan;
use Carbon\Carbon;
use DB;
use Exception;
use FireflyIII\Events\RequestedVersionCheckStatus;
use FireflyIII\Exceptions\FireflyException;
@@ -38,7 +37,6 @@ use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use Illuminate\Support\Collection;
use Log;
use Monolog\Handler\RotatingFileHandler;
use Preferences;
use Response;
use Route as RouteFacade;
@@ -98,59 +96,6 @@ class HomeController extends Controller
return Response::json(['ok' => 'ok']);
}
/**
* @param Request $request
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function displayDebug(Request $request)
{
$phpVersion = str_replace('~', '\~', PHP_VERSION);
$phpOs = php_uname();
$interface = PHP_SAPI;
$now = Carbon::create()->format('Y-m-d H:i:s e');
$extensions = join(', ', get_loaded_extensions());
$drivers = join(', ', DB::availableDrivers());
$currentDriver = DB::getDriverName();
$userAgent = $request->header('user-agent');
$isSandstorm = var_export(env('IS_SANDSTORM', 'unknown'), true);
$isDocker = var_export(env('IS_DOCKER', 'unknown'), true);
$trustedProxies = env('TRUSTED_PROXIES', '(none)');
// get latest log file:
$logger = Log::getMonolog();
$handlers = $logger->getHandlers();
$logContent = '';
foreach ($handlers as $handler) {
if ($handler instanceof RotatingFileHandler) {
$logFile = $handler->getUrl();
if (null !== $logFile) {
$logContent = file_get_contents($logFile);
}
}
}
// last few lines
$logContent = 'Truncated from this point <----|' . substr($logContent, -4096);
return view(
'debug',
compact(
'phpVersion',
'extensions',
'carbon',
'now',
'drivers',
'currentDriver',
'userAgent',
'phpOs',
'interface',
'logContent',
'isDocker',
'isSandstorm',
'trustedProxies'
)
);
}
/**
* @throws FireflyException
@@ -255,20 +200,25 @@ class HomeController extends Controller
'login', 'logout', 'password.reset', 'profile.confirm-email-change', 'profile.undo-email-change',
'register', 'report.options', 'routes', 'rule-groups.down', 'rule-groups.up', 'rules.up', 'rules.down',
'rules.select', 'search.search', 'test-flash', 'transactions.link.delete', 'transactions.link.switch',
'two-factor.lost', 'report.options',
'two-factor.lost', 'reports.options', 'debug', 'import.create-job', 'import.download', 'import.start', 'import.status.json',
'preferences.delete-code', 'rules.test-triggers', 'piggy-banks.remove-money', 'piggy-banks.add-money',
'accounts.reconcile.transactions', 'accounts.reconcile.overview', 'export.download',
'transactions.clone', 'two-factor.index',
];
$return = '&nbsp;';
/** @var Route $route */
foreach ($set as $route) {
$name = $route->getName();
if (null !== $name && in_array('GET', $route->methods()) && strlen($name) > 0) {
$found = false;
foreach ($ignore as $string) {
if (false !== strpos($name, $string)) {
if (!(false === stripos($name, $string))) {
$found = true;
break;
}
}
if (!$found) {
if ($found === false) {
$return .= 'touch ' . $route->getName() . '.md;';
}
}

View File

@@ -55,7 +55,7 @@ class ConfigurationController extends Controller
return $next($request);
}
);
$this->middleware(IsDemoUser::class)->except(['index']);
$this->middleware(IsDemoUser::class);
}
/**
@@ -78,7 +78,9 @@ class ConfigurationController extends Controller
return redirect(route('import.status', [$job->key]));
}
$this->repository->updateStatus($job, 'configuring');
$view = $configurator->getNextView();
$data = $configurator->getNextData();
$subTitle = trans('firefly.import_config_bread_crumb');
@@ -135,6 +137,7 @@ class ConfigurationController extends Controller
if (null === $className || !class_exists($className)) {
throw new FireflyException(sprintf('Cannot find configurator class for job of type "%s".', $type)); // @codeCoverageIgnore
}
Log::debug(sprintf('Going to create class "%s"', $className));
/** @var ConfiguratorInterface $configurator */
$configurator = app($className);
$configurator->setJob($job);

View File

@@ -33,6 +33,7 @@ use Log;
use Response;
use View;
/**
* Class FileController.
*/
@@ -57,8 +58,7 @@ class IndexController extends Controller
return $next($request);
}
);
$this->middleware(IsDemoUser::class)->except(['create', 'index']);
$this->middleware(IsDemoUser::class)->except(['index']);
}
/**

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Import;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Response;
@@ -47,6 +48,7 @@ class StatusController extends Controller
return $next($request);
}
);
$this->middleware(IsDemoUser::class);
}
/**
@@ -93,20 +95,29 @@ class StatusController extends Controller
$result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0);
$result['show_percentage'] = true;
}
if ('finished' === $job->status) {
$tagId = $job->extended_status['tag'];
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$tag = $repository->find($tagId);
$result['finished'] = true;
$result['finishedText'] = trans('import.status_finished_job', ['link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]);
$result['finished'] = true;
$tagId = intval($job->extended_status['tag']);
if ($tagId !== 0) {
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$tag = $repository->find($tagId);
$count = $tag->transactionJournals()->count();
$result['finishedText'] = trans(
'import.status_finished_job', ['count' => $count, 'link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]
);
}
if ($tagId === 0) {
$result['finishedText'] = trans('import.status_finished_no_tag'); // @codeCoverageIgnore
}
}
if ('running' === $job->status) {
$result['started'] = true;
$result['running'] = true;
}
$result['percentage'] = $result['percentage'] > 100 ? 100 : $result['percentage'];
return Response::json($result);
}

View File

@@ -25,11 +25,13 @@ namespace FireflyIII\Http\Controllers\Json;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
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\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Response;
@@ -170,7 +172,7 @@ class BoxController extends Controller
*
* @return \Illuminate\Http\JsonResponse
*/
public function netWorth(AccountRepositoryInterface $repository)
public function netWorth(AccountRepositoryInterface $repository, CurrencyRepositoryInterface $currencyRepos)
{
$date = new Carbon(date('Y-m-d')); // needed so its per day.
/** @var Carbon $start */
@@ -193,16 +195,32 @@ class BoxController extends Controller
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
$netWorth = [];
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$currency = app('amount')->getDefaultCurrency();
$balances = app('steam')->balancesByAccounts($accounts, $date);
$sum = '0';
foreach ($balances as $entry) {
$sum = bcadd($sum, $entry);
/** @var Account $account */
foreach ($accounts as $account) {
$accountCurrency = $currency;
$balance = $balances[$account->id] ?? '0';
$currencyId = intval($account->getMeta('currency_id'));
if ($currencyId !== 0) {
$accountCurrency = $currencyRepos->find($currencyId);
}
if (!isset($netWorth[$accountCurrency->id])) {
$netWorth[$accountCurrency->id]['currency'] = $accountCurrency;
$netWorth[$accountCurrency->id]['sum'] = '0';
}
$netWorth[$accountCurrency->id]['sum'] = bcadd($netWorth[$accountCurrency->id]['sum'], $balance);
}
$return = [];
foreach ($netWorth as $currencyId => $data) {
$return[$currencyId] = app('amount')->formatAnything($data['currency'], $data['sum'], false);
}
$return = [
'net_worth' => app('amount')->formatAnything($currency, $sum, false),
'net_worths' => array_values($return),
];
$cache->store($return);

View File

@@ -383,7 +383,7 @@ class PiggyBankController extends Controller
{
$note = $piggyBank->notes()->first();
$events = $repository->getEvents($piggyBank);
$subTitle = e($piggyBank->name);
$subTitle = $piggyBank->name;
return view('piggy-banks.show', compact('piggyBank', 'events', 'subTitle', 'note'));
}

View File

@@ -59,16 +59,16 @@ class ReportController extends Controller
parent::__construct();
$this->middleware(
function ($request, $next) {
// @var AccountRepositoryInterface $repository
/** @var AccountRepositoryInterface accountRepository */
$this->accountRepository = app(AccountRepositoryInterface::class);
// @var BudgetRepositoryInterface $repository
/** @var BudgetRepositoryInterface budgetRepository */
$this->budgetRepository = app(BudgetRepositoryInterface::class);
// @var CategoryRepositoryInterface categoryRepository
/** @var CategoryRepositoryInterface categoryRepository */
$this->categoryRepository = app(CategoryRepositoryInterface::class);
// @var PopupReportInterface popupHelper
/** @var PopupReportInterface popupHelper */
$this->popupHelper = app(PopupReportInterface::class);
return $next($request);

View File

@@ -128,7 +128,7 @@ class PreferencesController extends Controller
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @SuppressWarnings(PHPMD.UnusedFormalParameter) // it's unused but the class does some validation.
*/
public function postCode(TokenFormRequest $request)
public function postCode(/** @scrutinizer ignore-unused */ TokenFormRequest $request)
{
Preferences::set('twoFactorAuthEnabled', 1);
Preferences::set('twoFactorAuthSecret', Session::get('two-factor-secret'));

View File

@@ -129,12 +129,12 @@ class CategoryController extends Controller
$report = [];
/** @var Category $category */
foreach ($categories as $category) {
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end);
if (0 !== bccomp($spent, '0')) {
$report[$category->id] = ['name' => $category->name, 'spent' => $spent, 'id' => $category->id];
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end);
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $end);
if (0 !== bccomp($spent, '0') || 0 !== bccomp($earned, '0')) {
$report[$category->id] = ['name' => $category->name, 'spent' => $spent, 'earned' => $earned, 'id' => $category->id];
}
}
// sort the result
// Obtain a list of columns
$sum = [];

View File

@@ -568,7 +568,7 @@ class ExpenseController extends Controller
];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$currencyId = intval($transaction->transaction_currency_id);
// if not set, set to zero:
if (!isset($sum['per_currency'][$currencyId])) {

View File

@@ -48,6 +48,9 @@ class ReportController extends Controller
/** @var ReportHelperInterface */
protected $helper;
/** @var BudgetRepositoryInterface */
private $repository;
/**
*
*/
@@ -55,13 +58,13 @@ class ReportController extends Controller
{
parent::__construct();
$this->helper = app(ReportHelperInterface::class);
$this->middleware(
function ($request, $next) {
app('view')->share('title', trans('firefly.reports'));
app('view')->share('mainTitleIcon', 'fa-line-chart');
View::share('subTitleIcon', 'fa-calendar');
$this->helper = app(ReportHelperInterface::class);
$this->repository = app(BudgetRepositoryInterface::class);
return $next($request);
}
@@ -87,6 +90,7 @@ class ReportController extends Controller
if ($start < session('first')) {
$start = session('first');
}
$this->repository->cleanupBudgets();
View::share(
'subTitle', trans(
@@ -120,6 +124,7 @@ class ReportController extends Controller
if ($start < session('first')) {
$start = session('first');
}
$this->repository->cleanupBudgets();
View::share(
'subTitle',
@@ -157,6 +162,7 @@ class ReportController extends Controller
if ($start < session('first')) {
$start = session('first');
}
$this->repository->cleanupBudgets();
View::share(
'subTitle',
@@ -195,6 +201,7 @@ class ReportController extends Controller
if ($start < session('first')) {
$start = session('first');
}
$this->repository->cleanupBudgets();
View::share(
'subTitle',
@@ -233,6 +240,7 @@ class ReportController extends Controller
if ($start < session('first')) {
$start = session('first');
}
$this->repository->cleanupBudgets();
View::share(
'subTitle',
@@ -265,6 +273,7 @@ class ReportController extends Controller
$customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$accountList = join(',', $accounts->pluck('id')->toArray());
$this->repository->cleanupBudgets();
return view('reports.index', compact('months', 'accounts', 'start', 'accountList', 'customFiscalYear'));
}
@@ -391,6 +400,7 @@ class ReportController extends Controller
if ($start < session('first')) {
$start = session('first');
}
$this->repository->cleanupBudgets();
View::share(
'subTitle',

View File

@@ -0,0 +1,178 @@
<?php
/**
* BulkController.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Transaction;
use ExpandedForm;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\BulkEditJournalRequest;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Log;
use Preferences;
use Session;
use View;
/**
* Class BulkController
*/
class BulkController extends Controller
{
/**
*
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
app('view')->share('title', trans('firefly.transactions'));
app('view')->share('mainTitleIcon', 'fa-repeat');
return $next($request);
}
);
}
/**
* @param Collection $journals
*
* @return View
*/
public function edit(Request $request, Collection $journals)
{
$subTitle = trans('firefly.mass_bulk_journals');
// skip transactions that have multiple destinations, multiple sources or are an opening balance.
$filtered = new Collection;
$messages = [];
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {
$sources = $journal->sourceAccountList();
$destinations = $journal->destinationAccountList();
if ($sources->count() > 1) {
$messages[] = trans('firefly.cannot_edit_multiple_source', ['description' => $journal->description, 'id' => $journal->id]);
continue;
}
if ($destinations->count() > 1) {
$messages[] = trans('firefly.cannot_edit_multiple_dest', ['description' => $journal->description, 'id' => $journal->id]);
continue;
}
if (TransactionType::OPENING_BALANCE === $journal->transactionType->type) {
$messages[] = trans('firefly.cannot_edit_opening_balance');
continue;
}
// cannot edit reconciled transactions / journals:
if ($journal->transactions->first()->reconciled) {
$messages[] = trans('firefly.cannot_edit_reconciled', ['description' => $journal->description, 'id' => $journal->id]);
continue;
}
$filtered->push($journal);
}
if (count($messages) > 0) {
$request->session()->flash('info', $messages);
}
// put previous url in session
$this->rememberPreviousUri('transactions.bulk-edit.uri');
// get list of budgets:
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$budgetList = ExpandedForm::makeSelectListWithEmpty($repository->getActiveBudgets());
// collect some useful meta data for the mass edit:
$filtered->each(
function (TransactionJournal $journal) {
$journal->transaction_count = $journal->transactions()->count();
}
);
if (0 === $filtered->count()) {
$request->session()->flash('error', trans('firefly.no_edit_multiple_left'));
}
$journals = $filtered;
return view('transactions.bulk.edit', compact('journals', 'subTitle', 'budgetList'));
}
/**
* @param BulkEditJournalRequest $request
* @param JournalRepositoryInterface $repository
*
* @return mixed
*/
public function update(BulkEditJournalRequest $request, JournalRepositoryInterface $repository)
{
$journalIds = $request->get('journals');
$ignoreCategory = intval($request->get('ignore_category')) === 1;
$ignoreBudget = intval($request->get('ignore_budget')) === 1;
$ignoreTags = intval($request->get('ignore_tags')) === 1;
$count = 0;
if (is_array($journalIds)) {
foreach ($journalIds as $journalId) {
$journal = $repository->find(intval($journalId));
if (!is_null($journal)) {
$count++;
Log::debug(sprintf('Found journal #%d', $journal->id));
// update category if not told to ignore
if ($ignoreCategory === false) {
Log::debug(sprintf('Set category to %s', $request->string('category')));
$repository->updateCategory($journal, $request->string('category'));
}
// update budget if not told to ignore (and is withdrawal)
if ($ignoreBudget === false) {
Log::debug(sprintf('Set budget to %d', $request->integer('budget_id')));
$repository->updateBudget($journal, $request->integer('budget_id'));
}
if ($ignoreTags === false) {
Log::debug(sprintf('Set tags to %s', $request->string('budget_id')));
$repository->updateTags($journal, explode(',', $request->string('tags')));
}
// update tags if not told to ignore (and is withdrawal)
}
}
}
Preferences::mark();
$request->session()->flash('success', trans('firefly.mass_edited_transactions_success', ['amount' => $count]));
// redirect to previous URL:
return redirect($this->getPreviousUri('transactions.bulk-edit.uri'));
}
}

View File

@@ -38,6 +38,11 @@ use URL;
*/
class LinkController extends Controller
{
/** @var JournalRepositoryInterface */
private $journalRepository;
/** @var LinkTypeRepositoryInterface */
private $repository;
/**
*
*/
@@ -50,6 +55,9 @@ class LinkController extends Controller
app('view')->share('title', trans('firefly.transactions'));
app('view')->share('mainTitleIcon', 'fa-repeat');
$this->journalRepository = app(JournalRepositoryInterface::class);
$this->repository = app(LinkTypeRepositoryInterface::class);
return $next($request);
}
);
@@ -70,14 +78,13 @@ class LinkController extends Controller
}
/**
* @param LinkTypeRepositoryInterface $repository
* @param TransactionJournalLink $link
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function destroy(LinkTypeRepositoryInterface $repository, TransactionJournalLink $link)
public function destroy(TransactionJournalLink $link)
{
$repository->destroyLink($link);
$this->repository->destroyLink($link);
Session::flash('success', strval(trans('firefly.deleted_link')));
Preferences::mark();
@@ -87,18 +94,13 @@ class LinkController extends Controller
/**
* @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
) {
public function store(JournalLinkRequest $request, TransactionJournal $journal)
{
Log::debug('We are here (store)');
$linkInfo = $request->getLinkInfo();
if (0 === $linkInfo['transaction_journal_id']) {
@@ -106,30 +108,28 @@ class LinkController extends Controller
return redirect(route('transactions.show', [$journal->id]));
}
$other = $journalRepository->find($linkInfo['transaction_journal_id']);
$alreadyLinked = $repository->findLink($journal, $other);
$other = $this->journalRepository->find($linkInfo['transaction_journal_id']);
$alreadyLinked = $this->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));
$repository->storeLink($linkInfo, $other, $journal);
$this->repository->storeLink($linkInfo, $other, $journal);
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 switchLink(LinkTypeRepositoryInterface $repository, TransactionJournalLink $link)
public function switchLink(TransactionJournalLink $link)
{
$repository->switchLink($link);
$this->repository->switchLink($link);
return redirect(URL::previous());
}

View File

@@ -26,6 +26,7 @@ use Carbon\Carbon;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\MassDeleteJournalRequest;
use FireflyIII\Http\Requests\MassEditJournalRequest;
use FireflyIII\Http\Requests\MassEditBulkJournalRequest;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
@@ -71,7 +72,7 @@ class MassController extends Controller
// put previous url in session
$this->rememberPreviousUri('transactions.mass-delete.uri');
return view('transactions.mass-delete', compact('journals', 'subTitle'));
return view('transactions.mass.delete', compact('journals', 'subTitle'));
}
/**
@@ -131,7 +132,7 @@ class MassController extends Controller
// skip transactions that have multiple destinations, multiple sources or are an opening balance.
$filtered = new Collection;
$messages = [];
// @var TransactionJournal
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {
$sources = $journal->sourceAccountList();
$destinations = $journal->destinationAccountList();
@@ -213,7 +214,7 @@ class MassController extends Controller
if (is_array($journalIds)) {
foreach ($journalIds as $journalId) {
$journal = $repository->find(intval($journalId));
if ($journal) {
if (!is_null($journal)) {
// get optional fields:
$what = strtolower($journal->transactionTypeStr());
$sourceAccountId = $request->get('source_account_id')[$journal->id] ?? 0;
@@ -264,4 +265,5 @@ class MassController extends Controller
// redirect to previous URL:
return redirect($this->getPreviousUri('transactions.mass-edit.uri'));
}
}

View File

@@ -272,7 +272,7 @@ class TransactionController extends Controller
$return = [];
/** @var Transaction $transaction */
foreach ($collection as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$currencyId = intval($transaction->transaction_currency_id);
// save currency information:
if (!isset($return[$currencyId])) {

View File

@@ -24,11 +24,10 @@ namespace FireflyIII\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Log;
use Preferences;
use Auth;
use Session;
/**
* Class AuthenticateTwoFactor.
*/
@@ -45,26 +44,14 @@ class AuthenticateTwoFactor
*/
public function handle(Request $request, Closure $next, $guard = null)
{
// do the usual auth, again:
if (Auth::guard($guard)->guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
}
return redirect()->guest('login');
}
if (1 === intval(auth()->user()->blocked)) {
Auth::guard($guard)->logout();
Session::flash('logoutMessage', trans('firefly.block_account_logout'));
return redirect()->guest('login');
}
$is2faEnabled = Preferences::get('twoFactorAuthEnabled', false)->data;
$has2faSecret = null !== Preferences::get('twoFactorAuthSecret');
// grab 2auth information from session.
$is2faAuthed = 'true' === $request->cookie('twoFactorAuthenticated');
$is2faAuthed = 'true' === $request->cookie('twoFactorAuthenticated');
if ($is2faEnabled && $has2faSecret && !$is2faAuthed) {
Log::debug('Does not seem to be 2 factor authed, redirect.');

View File

@@ -51,9 +51,9 @@ class IsDemoUser
/** @var User $user */
$user = auth()->user();
if ($user->hasRole('demo')) {
Session::flash('warning', strval(trans('firefly.not_available_demo_user')));
Session::flash('info', strval(trans('firefly.not_available_demo_user')));
return redirect(route('index'));
return redirect($request->session()->previousUrl());
}
return $next($request);

View File

@@ -60,8 +60,8 @@ class TrustProxies extends Middleware
public function __construct(Repository $config)
{
$trustedProxies = env('TRUSTED_PROXIES', null);
if (null !== $trustedProxies && strlen($trustedProxies) > 0) {
$this->proxies = $trustedProxies;
if (false !== $trustedProxies && null !== $trustedProxies && strlen($trustedProxies) > 0) {
$this->proxies = strval($trustedProxies);
}
parent::__construct($config);

View File

@@ -57,6 +57,7 @@ class AccountFormRequest extends Request
'openingBalanceDate' => $this->date('openingBalanceDate'),
'ccType' => $this->string('ccType'),
'ccMonthlyPaymentDate' => $this->string('ccMonthlyPaymentDate'),
'notes' => $this->string('notes'),
];
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* BulkEditJournalRequest.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Requests;
/**
* Class MassEditBulkJournalRequest.
*/
class BulkEditJournalRequest extends Request
{
/**
* @return bool
*/
public function authorize()
{
// Only allow logged in users
return auth()->check();
}
/**
* @return array
*/
public function rules()
{
// fixed
return [
'journals.*' => 'required|belongsToUser:transaction_journals,id',
];
}
}

View File

@@ -91,33 +91,33 @@ class JournalFormRequest extends Request
{
$what = $this->get('what');
$rules = [
'what' => 'required|in:withdrawal,deposit,transfer',
'date' => 'required|date',
'what' => 'required|in:withdrawal,deposit,transfer',
'date' => 'required|date',
'amount_currency_id_amount' => 'exists:transaction_currencies,id|required',
// then, custom fields:
'interest_date' => 'date|nullable',
'book_date' => 'date|nullable',
'process_date' => 'date|nullable',
'due_date' => 'date|nullable',
'payment_date' => 'date|nullable',
'invoice_date' => 'date|nullable',
'internal_reference' => 'min:1,max:255|nullable',
'notes' => 'min:1,max:50000|nullable',
'interest_date' => 'date|nullable',
'book_date' => 'date|nullable',
'process_date' => 'date|nullable',
'due_date' => 'date|nullable',
'payment_date' => 'date|nullable',
'invoice_date' => 'date|nullable',
'internal_reference' => 'min:1,max:255|nullable',
'notes' => 'min:1,max:50000|nullable',
// and then transaction rules:
'description' => 'required|between:1,255',
'amount' => 'numeric|required|more:0',
'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id|nullable',
'category' => 'between:1,255|nullable',
'source_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
'source_account_name' => 'between:1,255|nullable',
'destination_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
'destination_account_name' => 'between:1,255|nullable',
'piggy_bank_id' => 'between:1,255|nullable',
'description' => 'required|between:1,255',
'amount' => 'numeric|required|more:0',
'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id|nullable',
'category' => 'between:1,255|nullable',
'source_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
'source_account_name' => 'between:1,255|nullable',
'destination_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
'destination_account_name' => 'between:1,255|nullable',
'piggy_bank_id' => 'between:1,255|nullable',
// foreign currency amounts
'native_amount' => 'numeric|more:0|nullable',
'source_amount' => 'numeric|more:0|nullable',
'destination_amount' => 'numeric|more:0|nullable',
'native_amount' => 'numeric|more:0|nullable',
'source_amount' => 'numeric|more:0|nullable',
'destination_amount' => 'numeric|more:0|nullable',
];
// some rules get an upgrade depending on the type of data:

View File

@@ -48,7 +48,7 @@ class JournalLinkRequest extends Request
$parts = explode('_', $linkType);
$return['link_type_id'] = intval($parts[0]);
$return['transaction_journal_id'] = $this->integer('link_journal_id');
$return['comments'] = strlen($this->string('comments')) > 0 ? $this->string('comments') : null;
$return['notes'] = strlen($this->string('notes')) > 0 ? $this->string('notes') : '';
$return['direction'] = $parts[1];
if (0 === $return['transaction_journal_id'] && ctype_digit($this->string('link_other'))) {
$return['transaction_journal_id'] = $this->integer('link_other');

View File

@@ -143,7 +143,7 @@ class Request extends FormRequest
*
* @return int
*/
protected function integer(string $field): int
public function integer(string $field): int
{
return intval($this->get($field));
}

View File

@@ -24,11 +24,12 @@ namespace FireflyIII\Import\Configuration;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Support\Import\Configuration\ConfigurationInterface;
use FireflyIII\Support\Import\Configuration\File\Initial;
use FireflyIII\Support\Import\Configuration\File\Map;
use FireflyIII\Support\Import\Configuration\File\Roles;
use FireflyIII\Support\Import\Configuration\File\Upload;
use FireflyIII\Support\Import\Configuration\File\UploadConfig;
use Log;
/**
@@ -36,17 +37,41 @@ use Log;
*/
class FileConfigurator implements ConfiguratorInterface
{
/** @var array */
private $defaultConfig
= [
'stage' => 'initial',
'has-headers' => false, // assume
'date-format' => 'Ymd', // assume
'delimiter' => ',', // assume
'import-account' => 0, // none,
'specifics' => [], // none
'column-count' => 0, // unknown
'column-roles' => [], // unknown
'column-do-mapping' => [], // not yet set which columns must be mapped
'column-mapping-config' => [], // no mapping made yet.
'file-type' => 'csv', // assume
'has-config-file' => true,
'apply-rules' => true,
'match-bills' => false,
'auto-start' => false,
];
/** @var ImportJob */
private $job;
/** @var ImportJobRepositoryInterface */
private $repository;
// give job default config:
/** @var string */
private $warning = '';
/**
* ConfiguratorInterface constructor.
* FileConfigurator constructor.
*/
public function __construct()
{
Log::debug('Created FileConfigurator');
}
/**
@@ -60,11 +85,12 @@ class FileConfigurator implements ConfiguratorInterface
*/
public function configureJob(array $data): bool
{
$class = $this->getConfigurationClass();
$job = $this->job;
if (is_null($this->job)) {
throw new FireflyException('Cannot call configureJob() without a job.');
}
/** @var ConfigurationInterface $object */
$object = new $class($this->job);
$object->setJob($job);
$object = app($this->getConfigurationClass());
$object->setJob($this->job);
$result = $object->storeConfiguration($data);
$this->warning = $object->getWarningMessage();
@@ -80,11 +106,12 @@ class FileConfigurator implements ConfiguratorInterface
*/
public function getNextData(): array
{
$class = $this->getConfigurationClass();
$job = $this->job;
if (is_null($this->job)) {
throw new FireflyException('Cannot call getNextData() without a job.');
}
/** @var ConfigurationInterface $object */
$object = app($class);
$object->setJob($job);
$object = app($this->getConfigurationClass());
$object->setJob($this->job);
return $object->getData();
}
@@ -96,52 +123,56 @@ class FileConfigurator implements ConfiguratorInterface
*/
public function getNextView(): string
{
if (!$this->job->configuration['has-file-upload']) {
return 'import.file.upload';
if (is_null($this->job)) {
throw new FireflyException('Cannot call getNextView() without a job.');
}
if (!$this->job->configuration['initial-config-complete']) {
return 'import.file.initial';
$config = $this->getConfig();
$stage = $config['stage'] ?? 'initial';
switch ($stage) {
case 'initial': // has nothing, no file upload or anything.
return 'import.file.initial';
case 'upload-config': // has file, needs file config.
return 'import.file.upload-config';
case 'roles': // has configured file, needs roles.
return 'import.file.roles';
case 'map': // has roles, needs mapping.
return 'import.file.map';
}
if (!$this->job->configuration['column-roles-complete']) {
return 'import.file.roles';
}
if (!$this->job->configuration['column-mapping-complete']) {
return 'import.file.map';
}
throw new FireflyException('No view for state');
throw new FireflyException(sprintf('No view for stage "%s"', $stage));
}
/**
* Return possible warning to user.
*
* @return string
* @throws FireflyException
*/
public function getWarningMessage(): string
{
if (is_null($this->job)) {
throw new FireflyException('Cannot call getWarningMessage() without a job.');
}
return $this->warning;
}
/**
* @return bool
* @throws FireflyException
*/
public function isJobConfigured(): bool
{
$config = $this->job->configuration;
$config['has-file-upload'] = $config['has-file-upload'] ?? false;
$config['initial-config-complete'] = $config['initial-config-complete'] ?? false;
$config['column-roles-complete'] = $config['column-roles-complete'] ?? false;
$config['column-mapping-complete'] = $config['column-mapping-complete'] ?? false;
$this->job->configuration = $config;
$this->job->save();
if (is_null($this->job)) {
throw new FireflyException('Cannot call isJobConfigured() without a job.');
}
$config = $this->getConfig();
$stage = $config['stage'] ?? 'initial';
if ($stage === 'ready') {
Log::debug('isJobConfigured returns true');
if ($config['initial-config-complete']
&& $config['column-roles-complete']
&& $config['column-mapping-complete']
&& $config['has-file-upload']
) {
return true;
}
Log::debug('isJobConfigured returns false');
return false;
}
@@ -151,12 +182,30 @@ class FileConfigurator implements ConfiguratorInterface
*/
public function setJob(ImportJob $job)
{
$this->job = $job;
if (null === $this->job->configuration || 0 === count($this->job->configuration)) {
Log::debug(sprintf('Gave import job %s initial configuration.', $this->job->key));
$this->job->configuration = config('csv.default_config');
$this->job->save();
}
Log::debug(sprintf('FileConfigurator::setJob(#%d: %s)', $job->id, $job->key));
$this->job = $job;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($job->user);
// set number of steps to 100:
$extendedStatus = $this->getExtendedStatus();
$extendedStatus['steps'] = 6;
$extendedStatus['done'] = 0;
$this->setExtendedStatus($extendedStatus);
$config = $this->getConfig();
$newConfig = array_merge($this->defaultConfig, $config);
$this->repository->setConfiguration($job, $newConfig);
}
/**
* Short hand method.
*
* @return array
*/
private function getConfig(): array
{
return $this->repository->getConfiguration($this->job);
}
/**
@@ -166,18 +215,22 @@ class FileConfigurator implements ConfiguratorInterface
*/
private function getConfigurationClass(): string
{
$class = false;
switch (true) {
case !$this->job->configuration['has-file-upload']:
$class = Upload::class;
break;
case !$this->job->configuration['initial-config-complete']:
$config = $this->getConfig();
$stage = $config['stage'] ?? 'initial';
$class = false;
Log::debug(sprintf('Now in getConfigurationClass() for stage "%s"', $stage));
switch ($stage) {
case 'initial': // has nothing, no file upload or anything.
$class = Initial::class;
break;
case !$this->job->configuration['column-roles-complete']:
case 'upload-config': // has file, needs file config.
$class = UploadConfig::class;
break;
case 'roles': // has configured file, needs roles.
$class = Roles::class;
break;
case !$this->job->configuration['column-mapping-complete']:
case 'map': // has roles, needs mapping.
$class = Map::class;
break;
default:
@@ -185,12 +238,37 @@ class FileConfigurator implements ConfiguratorInterface
}
if (false === $class || 0 === strlen($class)) {
throw new FireflyException('Cannot handle current job state in getConfigurationClass().');
throw new FireflyException(sprintf('Cannot handle job stage "%s" in getConfigurationClass().', $stage));
}
if (!class_exists($class)) {
throw new FireflyException(sprintf('Class %s does not exist in getConfigurationClass().', $class));
throw new FireflyException(sprintf('Class %s does not exist in getConfigurationClass().', $class)); // @codeCoverageIgnore
}
Log::debug(sprintf('Configuration class is "%s"', $class));
return $class;
}
/**
* Shorthand method to return the extended status.
*
* @codeCoverageIgnore
* @return array
*/
private function getExtendedStatus(): array
{
return $this->repository->getExtendedStatus($this->job);
}
/**
* Shorthand method to set the extended status.
*
* @codeCoverageIgnore
* @param array $extended
*/
private function setExtendedStatus(array $extended): void
{
$this->repository->setExtendedStatus($this->job, $extended);
return;
}
}

View File

@@ -22,7 +22,11 @@ declare(strict_types=1);
namespace FireflyIII\Import\Configuration;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Support\Import\Configuration\Spectre\HaveAccounts;
use Log;
/**
* Class SpectreConfigurator.
@@ -32,6 +36,9 @@ class SpectreConfigurator implements ConfiguratorInterface
/** @var ImportJob */
private $job;
/** @var ImportJobRepositoryInterface */
private $repository;
/** @var string */
private $warning = '';
@@ -48,35 +55,96 @@ class SpectreConfigurator implements ConfiguratorInterface
* @param array $data
*
* @return bool
* @throws FireflyException
*/
public function configureJob(array $data): bool
{
die('cannot store config');
if (is_null($this->job)) {
throw new FireflyException('Cannot call configureJob() without a job.');
}
$stage = $this->getConfig()['stage'] ?? 'initial';
Log::debug(sprintf('in getNextData(), for stage "%s".', $stage));
switch ($stage) {
case 'have-accounts':
/** @var HaveAccounts $class */
$class = app(HaveAccounts::class);
$class->setJob($this->job);
$class->storeConfiguration($data);
// update job for next step and set to "configured".
$config = $this->getConfig();
$config['stage'] = 'have-account-mapping';
$this->repository->setConfiguration($this->job, $config);
return true;
default:
throw new FireflyException(sprintf('Cannot store configuration when job is in state "%s"', $stage));
break;
}
}
/**
* Return the data required for the next step in the job configuration.
*
* @return array
* @throws FireflyException
*/
public function getNextData(): array
{
// update config to tell Firefly we've redirected the user.
$config = $this->job->configuration;
$config['is-redirected'] = true;
$this->job->configuration = $config;
$this->job->status = 'configured';
$this->job->save();
if (is_null($this->job)) {
throw new FireflyException('Cannot call configureJob() without a job.');
}
$config = $this->getConfig();
$stage = $config['stage'] ?? 'initial';
return $this->job->configuration;
Log::debug(sprintf('in getNextData(), for stage "%s".', $stage));
switch ($stage) {
case 'has-token':
// simply redirect to Spectre.
$config['is-redirected'] = true;
$config['stage'] = 'user-logged-in';
$status = 'configured';
// update config and status:
$this->repository->setConfiguration($this->job, $config);
$this->repository->setStatus($this->job, $status);
return $this->repository->getConfiguration($this->job);
case 'have-accounts':
/** @var HaveAccounts $class */
$class = app(HaveAccounts::class);
$class->setJob($this->job);
$data = $class->getData();
return $data;
default:
return [];
}
}
/**
* @return string
* @throws FireflyException
*/
public function getNextView(): string
{
return 'import.spectre.redirect';
if (is_null($this->job)) {
throw new FireflyException('Cannot call configureJob() without a job.');
}
$stage = $this->getConfig()['stage'] ?? 'initial';
Log::debug(sprintf('in getNextView(), for stage "%s".', $stage));
switch ($stage) {
case 'has-token':
// redirect to Spectre.
Log::info('User is being redirected to Spectre.');
return 'import.spectre.redirect';
case 'have-accounts':
return 'import.spectre.accounts';
default:
return '';
}
}
/**
@@ -91,36 +159,75 @@ class SpectreConfigurator implements ConfiguratorInterface
/**
* @return bool
* @throws FireflyException
*/
public function isJobConfigured(): bool
{
// job is configured (and can start) when token is empty:
$config = $this->job->configuration;
if ($config['has-token'] === false) {
return true;
if (is_null($this->job)) {
throw new FireflyException('Cannot call configureJob() without a job.');
}
$stage = $this->getConfig()['stage'] ?? 'initial';
Log::debug(sprintf('in isJobConfigured(), for stage "%s".', $stage));
switch ($stage) {
case 'has-token':
case 'have-accounts':
Log::debug('isJobConfigured returns false');
return false;
return false;
default:
Log::debug('isJobConfigured returns true');
return true;
}
}
/**
* @param ImportJob $job
*/
public function setJob(ImportJob $job)
public function setJob(ImportJob $job): void
{
// make repository
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($job->user);
// set default config:
$defaultConfig = [
'has-token' => false,
'token' => '',
'token-expires' => 0,
'token-url' => '',
'is-redirected' => false,
'has-token' => false,
'token' => '',
'token-expires' => 0,
'token-url' => '',
'is-redirected' => false,
'customer' => null,
'login' => null,
'stage' => 'initial',
'accounts' => '',
'accounts-mapped' => '',
'auto-start' => true,
'apply-rules' => true,
'match-bills' => false,
];
$currentConfig = $this->repository->getConfiguration($job);
$finalConfig = array_merge($defaultConfig, $currentConfig);
$config = $job->configuration;
$finalConfig = array_merge($defaultConfig, $config);
$job->configuration = $finalConfig;
$job->save();
// set default extended status:
$extendedStatus = $this->repository->getExtendedStatus($job);
$extendedStatus['steps'] = 6;
// save to job:
$job = $this->repository->setConfiguration($job, $finalConfig);
$job = $this->repository->setExtendedStatus($job, $extendedStatus);
$this->job = $job;
return;
}
/**
* Shorthand method.
*
* @return array
*/
private function getConfig(): array
{
return $this->repository->getConfiguration($this->job);
}
}

View File

@@ -45,8 +45,10 @@ class Amount implements ConverterInterface
if (null === $value) {
return '0';
}
$value = strval($value);
Log::debug(sprintf('Start with amount "%s"', $value));
$original = $value;
$value = strval($value);
$value = $this->stripAmount($value);
$len = strlen($value);
$decimalPosition = $len - 3;
$altPosition = $len - 2;
@@ -75,33 +77,49 @@ class Amount implements ConverterInterface
Log::debug(sprintf('Searched from the left for "." in amount "%s", assume this is the decimal sign.', $value));
$decimal = '.';
}
unset($options, $res);
unset($res);
}
// if decimal is dot, replace all comma's and spaces with nothing. then parse as float (round to 4 pos)
if ('.' === $decimal) {
$search = [',', ' '];
$oldValue = $value;
$value = str_replace($search, '', $value);
Log::debug(sprintf('Converted amount from "%s" to "%s".', $oldValue, $value));
$search = [',', ' '];
$value = str_replace($search, '', $value);
Log::debug(sprintf('Converted amount from "%s" to "%s".', $original, $value));
}
if (',' === $decimal) {
$search = ['.', ' '];
$oldValue = $value;
$value = str_replace($search, '', $value);
$value = str_replace(',', '.', $value);
Log::debug(sprintf('Converted amount from "%s" to "%s".', $oldValue, $value));
$search = ['.', ' '];
$value = str_replace($search, '', $value);
$value = str_replace(',', '.', $value);
Log::debug(sprintf('Converted amount from "%s" to "%s".', $original, $value));
}
if (null === $decimal) {
// replace all:
$search = ['.', ' ', ','];
$oldValue = $value;
$value = str_replace($search, '', $value);
Log::debug(sprintf('No decimal character found. Converted amount from "%s" to "%s".', $oldValue, $value));
$search = ['.', ' ', ','];
$value = str_replace($search, '', $value);
Log::debug(sprintf('No decimal character found. Converted amount from "%s" to "%s".', $original, $value));
}
$number = strval(number_format(round(floatval($value), 12), 12, '.', ''));
return $number;
}
/**
* @param string $value
*
* @return string
*/
private function stripAmount(string $value): string
{
$str = preg_replace('/[^\-\(\)\.\,0-9 ]/', '', $value);
$len = strlen($str);
if ($str{0} === '(' && $str{$len - 1} === ')') {
$str = '-' . substr($str, 1, ($len - 2));
}
Log::debug(sprintf('Stripped "%s" away to "%s"', $value, $str));
return $str;
}
}

View File

@@ -26,7 +26,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Import\Object\ImportJournal;
use FireflyIII\Import\Specifics\SpecificInterface;
use FireflyIII\Models\ImportJob;
use FireflyIII\Models\TransactionJournalMeta;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use Illuminate\Support\Collection;
use Iterator;
use League\Csv\Reader;
@@ -43,6 +43,8 @@ class CsvProcessor implements FileProcessorInterface
private $job;
/** @var Collection */
private $objects;
/** @var ImportJobRepositoryInterface */
private $repository;
/** @var array */
private $validConverters = [];
/** @var array */
@@ -60,9 +62,14 @@ class CsvProcessor implements FileProcessorInterface
/**
* @return Collection
* @throws FireflyException
*/
public function getObjects(): Collection
{
if (is_null($this->job)) {
throw new FireflyException('Cannot call getObjects() without a job.');
}
return $this->objects;
}
@@ -73,12 +80,17 @@ class CsvProcessor implements FileProcessorInterface
*
* @throws \League\Csv\Exception
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
* @throws FireflyException
*/
public function run(): bool
{
if (is_null($this->job)) {
throw new FireflyException('Cannot call run() without a job.');
}
Log::debug('Now in CsvProcessor run(). Job is now running...');
$entries = new Collection($this->getImportArray());
$this->addStep();
Log::notice('Building importable objects from CSV file.');
Log::debug(sprintf('Number of entries: %d', $entries->count()));
$notImported = $entries->filter(
@@ -86,8 +98,7 @@ class CsvProcessor implements FileProcessorInterface
$row = array_values($row);
if ($this->rowAlreadyImported($row)) {
$message = sprintf('Row #%d has already been imported.', $index);
$this->job->addError($index, $message);
$this->job->addStepsDone(5); // all steps.
$this->repository->addError($this->job, $index, $message);
Log::info($message);
return null;
@@ -96,26 +107,31 @@ class CsvProcessor implements FileProcessorInterface
return $row;
}
);
$this->addStep();
Log::debug(sprintf('Number of entries left: %d', $notImported->count()));
// set (new) number of steps:
$status = $this->job->extended_status;
$status['steps'] = $notImported->count() * 5;
$this->job->extended_status = $status;
$this->job->save();
Log::debug(sprintf('Number of steps: %d', $notImported->count() * 5));
$notImported->each(
function (array $row, int $index) {
$journal = $this->importRow($index, $row);
$this->objects->push($journal);
$this->job->addStepsDone(1);
}
);
$this->addStep();
return true;
}
/**
* Shorthand method to set the extended status.
*
* @codeCoverageIgnore
* @param array $array
*/
public function setExtendedStatus(array $array)
{
$this->repository->setExtendedStatus($this->job, $array);
}
/**
* Set import job for this processor.
*
@@ -125,11 +141,23 @@ class CsvProcessor implements FileProcessorInterface
*/
public function setJob(ImportJob $job): FileProcessorInterface
{
$this->job = $job;
$this->job = $job;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($job->user);
return $this;
}
/**
* Shorthand method to add a step.
*
* @codeCoverageIgnore
*/
private function addStep()
{
$this->repository->addStepsDone($this->job, 1);
}
/**
* Add meta data to the individual value and verify that it can be handled in a later stage.
*
@@ -142,7 +170,7 @@ class CsvProcessor implements FileProcessorInterface
*/
private function annotateValue(int $index, string $value)
{
$config = $this->job->configuration;
$config = $this->getConfig();
$role = $config['column-roles'][$index] ?? '_ignore';
$mapped = $config['column-mapping-config'][$index][$value] ?? null;
@@ -160,25 +188,36 @@ class CsvProcessor implements FileProcessorInterface
return $entry;
}
/**
* Shorthand method to return configuration.
*
* @codeCoverageIgnore
* @return array
*/
private function getConfig(): array
{
return $this->repository->getConfiguration($this->job);
}
/**
* @return Iterator
*
* @throws \League\Csv\Exception
* @throws \League\Csv\Exception
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
private function getImportArray(): Iterator
{
$content = $this->job->uploadFileContents();
$config = $this->job->configuration;
$reader = Reader::createFromString($content);
$delimiter = $config['delimiter'];
$content = $this->repository->uploadFileContents($this->job);
$config = $this->getConfig();
$reader = Reader::createFromString($content);
$delimiter = $config['delimiter'] ?? ',';
$hasHeaders = isset($config['has-headers']) ? $config['has-headers'] : false;
if ('tab' === $delimiter) {
$delimiter = "\t";
$delimiter = "\t"; // @codeCoverageIgnore
}
$reader->setDelimiter($delimiter);
if ($config['has-headers']) {
$reader->setHeaderOffset(0);
if ($hasHeaders) {
$reader->setHeaderOffset(0); // @codeCoverageIgnore
}
$results = $reader->getRecords();
Log::debug('Created a CSV reader.');
@@ -191,6 +230,7 @@ class CsvProcessor implements FileProcessorInterface
*
* @param int $jsonError
*
* @codeCoverageIgnore
* @return string
*/
private function getJsonError(int $jsonError): string
@@ -230,7 +270,7 @@ class CsvProcessor implements FileProcessorInterface
$jsonError = json_last_error();
if (false === $json) {
throw new FireflyException(sprintf('Error while encoding JSON for CSV row: %s', $this->getJsonError($jsonError)));
throw new FireflyException(sprintf('Error while encoding JSON for CSV row: %s', $this->getJsonError($jsonError))); // @codeCoverageIgnore
}
$hash = hash('sha256', $json);
@@ -251,8 +291,9 @@ class CsvProcessor implements FileProcessorInterface
{
$row = array_values($row);
Log::debug(sprintf('Now at row %d', $index));
$row = $this->specifics($row);
$hash = $this->getRowHash($row);
$row = $this->specifics($row);
$hash = $this->getRowHash($row);
$config = $this->getConfig();
$journal = new ImportJournal;
$journal->setUser($this->job->user);
@@ -271,7 +312,8 @@ class CsvProcessor implements FileProcessorInterface
}
}
// set some extra info:
$journal->asset->setDefaultAccountId($this->job->configuration['import-account']);
$importAccount = intval($config['import-account'] ?? 0);
$journal->asset->setDefaultAccountId($importAccount);
return $journal;
}
@@ -288,12 +330,8 @@ class CsvProcessor implements FileProcessorInterface
private function rowAlreadyImported(array $array): bool
{
$hash = $this->getRowHash($array);
$json = json_encode($hash);
$entry = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
->where('data', $json)
->where('name', 'importHash')
->first();
if (null !== $entry) {
$count = $this->repository->countByHash($hash);
if ($count > 0) {
return true;
}
@@ -311,7 +349,7 @@ class CsvProcessor implements FileProcessorInterface
*/
private function specifics(array $row): array
{
$config = $this->job->configuration;
$config = $this->getConfig();
$names = array_keys($config['specifics'] ?? []);
foreach ($names as $name) {
if (!in_array($name, $this->validSpecifics)) {

View File

@@ -26,6 +26,7 @@ use Illuminate\Console\Command;
use Monolog\Handler\AbstractProcessingHandler;
/**
* @codeCoverageIgnore
* Class CommandHandler.
*/
class CommandHandler extends AbstractProcessingHandler

View File

@@ -44,18 +44,17 @@ class AssetAccountIbans implements MapperInterface
/** @var Account $account */
foreach ($set as $account) {
$iban = $account->iban ?? '';
$iban = $account->iban ?? '';
$accountId = intval($account->id);
if (strlen($iban) > 0) {
$topList[$account->id] = $account->iban . ' (' . $account->name . ')';
$topList[$accountId] = $account->iban . ' (' . $account->name . ')';
}
if (0 === strlen($iban)) {
$list[$account->id] = $account->name;
$list[$accountId] = $account->name;
}
}
asort($topList);
asort($list);
$list = $topList + $list;
asort($list);
$list = [0 => trans('import.map_do_not_map')] + $list;
return $list;

View File

@@ -43,16 +43,15 @@ class AssetAccounts implements MapperInterface
/** @var Account $account */
foreach ($set as $account) {
$name = $account->name;
$iban = $account->iban ?? '';
$accountId = intval($account->id);
$name = $account->name;
$iban = $account->iban ?? '';
if (strlen($iban) > 0) {
$name .= ' (' . $account->iban . ')';
$name .= ' (' . $iban . ')';
}
$list[$account->id] = $name;
$list[$accountId] = $name;
}
asort($list);
$list = [0 => trans('import.map_do_not_map')] + $list;
return $list;

View File

@@ -42,10 +42,10 @@ class Bills implements MapperInterface
/** @var Bill $bill */
foreach ($result as $bill) {
$list[$bill->id] = $bill->name . ' [' . $bill->match . ']';
$billId = intval($bill->id);
$list[$billId] = $bill->name . ' [' . $bill->match . ']';
}
asort($list);
$list = [0 => trans('import.map_do_not_map')] + $list;
return $list;

View File

@@ -37,15 +37,15 @@ class Budgets implements MapperInterface
{
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$result = $repository->getBudgets();
$result = $repository->getActiveBudgets();
$list = [];
/** @var Budget $budget */
foreach ($result as $budget) {
$list[$budget->id] = $budget->name;
$budgetId = intval($budget->id);
$list[$budgetId] = $budget->name;
}
asort($list);
$list = [0 => trans('import.map_do_not_map')] + $list;
return $list;

View File

@@ -42,10 +42,10 @@ class Categories implements MapperInterface
/** @var Category $category */
foreach ($result as $category) {
$list[$category->id] = $category->name;
$categoryId = intval($category->id);
$list[$categoryId] = $category->name;
}
asort($list);
$list = [0 => trans('import.map_do_not_map')] + $list;
return $list;

View File

@@ -50,18 +50,17 @@ class OpposingAccountIbans implements MapperInterface
/** @var Account $account */
foreach ($set as $account) {
$iban = $account->iban ?? '';
$iban = $account->iban ?? '';
$accountId = intval($account->id);
if (strlen($iban) > 0) {
$topList[$account->id] = $account->iban . ' (' . $account->name . ')';
$topList[$accountId] = $account->iban . ' (' . $account->name . ')';
}
if (0 === strlen($iban)) {
$list[$account->id] = $account->name;
$list[$accountId] = $account->name;
}
}
asort($topList);
asort($list);
$list = $topList + $list;
asort($list);
$list = [0 => trans('import.map_do_not_map')] + $list;
return $list;

View File

@@ -49,16 +49,15 @@ class OpposingAccounts implements MapperInterface
/** @var Account $account */
foreach ($set as $account) {
$name = $account->name;
$iban = $account->iban ?? '';
$accountId = intval($account->id);
$name = $account->name;
$iban = $account->iban ?? '';
if (strlen($iban) > 0) {
$name .= ' (' . $account->iban . ')';
$name .= ' (' . $iban . ')';
}
$list[$account->id] = $name;
$list[$accountId] = $name;
}
asort($list);
$list = [0 => trans('import.map_do_not_map')] + $list;
return $list;

View File

@@ -42,10 +42,10 @@ class Tags implements MapperInterface
/** @var Tag $tag */
foreach ($result as $tag) {
$list[$tag->id] = $tag->tag;
$tagId = intval($tag->id);
$list[$tagId] = $tag->tag;
}
asort($list);
$list = [0 => trans('import.map_do_not_map')] + $list;
return $list;

View File

@@ -22,7 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Import\Mapper;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
/**
* Class TransactionCurrencies.
@@ -34,12 +34,14 @@ class TransactionCurrencies implements MapperInterface
*/
public function getMap(): array
{
$currencies = TransactionCurrency::get();
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$currencies = $repository->get();
$list = [];
foreach ($currencies as $currency) {
$list[$currency->id] = $currency->name . ' (' . $currency->code . ')';
$currencyId = intval($currency->id);
$list[$currencyId] = $currency->name . ' (' . $currency->code . ')';
}
asort($list);
$list = [0 => trans('import.map_do_not_map')] + $list;

View File

@@ -34,6 +34,11 @@ class TagsComma implements PreProcessorInterface
*/
public function run(string $value): array
{
return explode(',', $value);
$set = explode(',', $value);
$set = array_map('trim', $set);
$set = array_filter($set, 'strlen');
$return = array_values($set);
return $return;
}
}

View File

@@ -34,6 +34,11 @@ class TagsSpace implements PreProcessorInterface
*/
public function run(string $value): array
{
return explode(' ', $value);
$set = explode(' ', $value);
$set = array_map('trim', $set);
$set = array_filter($set, 'strlen');
$return = array_values($set);
return $return;
}
}

View File

@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Import\Object;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
@@ -74,6 +75,7 @@ class ImportAccount
/**
* @return Account
* @throws FireflyException
*/
public function getAccount(): Account
{
@@ -85,6 +87,7 @@ class ImportAccount
}
/**
* @codeCoverageIgnore
* @return string
*/
public function getExpectedType(): string
@@ -93,6 +96,8 @@ class ImportAccount
}
/**
* @codeCoverageIgnore
*
* @param string $expectedType
*/
public function setExpectedType(string $expectedType)
@@ -101,6 +106,8 @@ class ImportAccount
}
/**
* @codeCoverageIgnore
*
* @param array $accountIban
*/
public function setAccountIban(array $accountIban)
@@ -109,6 +116,8 @@ class ImportAccount
}
/**
* @codeCoverageIgnore
*
* @param array $value
*/
public function setAccountId(array $value)
@@ -117,6 +126,8 @@ class ImportAccount
}
/**
* @codeCoverageIgnore
*
* @param array $accountName
*/
public function setAccountName(array $accountName)
@@ -125,6 +136,8 @@ class ImportAccount
}
/**
* @codeCoverageIgnore
*
* @param array $accountNumber
*/
public function setAccountNumber(array $accountNumber)
@@ -133,6 +146,8 @@ class ImportAccount
}
/**
* @codeCoverageIgnore
*
* @param int $defaultAccountId
*/
public function setDefaultAccountId(int $defaultAccountId)
@@ -141,6 +156,8 @@ class ImportAccount
}
/**
* @codeCoverageIgnore
*
* @param int $forbiddenAccountId
*/
public function setForbiddenAccountId(int $forbiddenAccountId)
@@ -149,6 +166,8 @@ class ImportAccount
}
/**
* @codeCoverageIgnore
*
* @param User $user
*/
public function setUser(User $user)
@@ -158,20 +177,21 @@ class ImportAccount
}
/**
* @return Account
* @return Account|null
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
private function findExistingObject(): Account
private function findExistingObject(): ?Account
{
Log::debug('In findExistingObject() for Account');
// 0: determin account type:
/** @var AccountType $accountType */
$accountType = AccountType::whereType($this->expectedType)->first();
$accountType = $this->repository->getAccountType($this->expectedType);
// 1: find by ID, iban or name (and type)
if (3 === count($this->accountId)) {
Log::debug(sprintf('Finding account of type %d and ID %d', $accountType->id, $this->accountId['value']));
/** @var Account $account */
$account = $this->user->accounts()->where('id', '!=', $this->forbiddenAccountId)->where('account_type_id', $accountType->id)->where(
'id',
$this->accountId['value']
@@ -233,13 +253,13 @@ class ImportAccount
// 4: do not search by account number.
Log::debug('Found NO existing accounts.');
return new Account;
return null;
}
/**
* @return Account
* @return Account|null
*/
private function findMappedObject(): Account
private function findMappedObject(): ?Account
{
Log::debug('In findMappedObject() for Account');
$fields = ['accountId', 'accountIban', 'accountNumber', 'accountName'];
@@ -248,7 +268,7 @@ class ImportAccount
Log::debug(sprintf('Find mapped account based on field "%s" with value', $field), $array);
// check if a pre-mapped object exists.
$mapped = $this->getMappedObject($array);
if (null !== $mapped->id) {
if (null !== $mapped) {
Log::debug(sprintf('Found account #%d!', $mapped->id));
return $mapped;
@@ -256,38 +276,38 @@ class ImportAccount
}
Log::debug('Found no account on mapped data or no map present.');
return new Account;
return null;
}
/**
* @param array $array
*
* @return Account
* @return Account|null
*/
private function getMappedObject(array $array): Account
private function getMappedObject(array $array): ?Account
{
Log::debug('In getMappedObject() for Account');
if (0 === count($array)) {
Log::debug('Array is empty, nothing will come of this.');
return new Account;
return null;
}
if (array_key_exists('mapped', $array) && null === $array['mapped']) {
Log::debug(sprintf('No map present for value "%s". Return NULL.', $array['value']));
return new Account;
return null;
}
Log::debug('Finding a mapped account based on', $array);
$search = intval($array['mapped']);
$search = intval($array['mapped'] ?? 0);
$account = $this->repository->find($search);
if (null === $account->id) {
Log::error(sprintf('There is no account with id #%d. Invalid mapping will be ignored!', $search));
return new Account;
return null;
}
// must be of the same type
// except when mapped is an asset, then it's fair game.
@@ -302,7 +322,7 @@ class ImportAccount
)
);
return new Account;
return null;
}
Log::debug(sprintf('Found account! #%d ("%s"). Return it', $account->id, $account->name));
@@ -312,19 +332,26 @@ class ImportAccount
/**
* @return bool
* @throws FireflyException
*/
private function store(): bool
{
if (is_null($this->user)) {
throw new FireflyException('ImportAccount cannot continue without user.');
}
if ((is_null($this->defaultAccountId) || intval($this->defaultAccountId) === 0) && AccountType::ASSET === $this->expectedType) {
throw new FireflyException('ImportAccount cannot continue without a default account to fall back on.');
}
// 1: find mapped object:
$mapped = $this->findMappedObject();
if (null !== $mapped->id) {
if (null !== $mapped) {
$this->account = $mapped;
return true;
}
// 2: find existing by given values:
$found = $this->findExistingObject();
if (null !== $found->id) {
if (null !== $found) {
$this->account = $found;
return true;
@@ -335,7 +362,7 @@ class ImportAccount
$oldExpectedType = $this->expectedType;
$this->expectedType = AccountType::ASSET;
$found = $this->findExistingObject();
if (null !== $found->id) {
if (null !== $found) {
Log::debug('Found asset account!');
$this->account = $found;

View File

@@ -48,6 +48,8 @@ class ImportJournal
public $currency;
/** @var string */
public $description = '';
/** @var ImportCurrency */
public $foreignCurrency;
/** @var string */
public $hash;
/** @var array */
@@ -71,6 +73,8 @@ class ImportJournal
/** @var string */
private $externalId = '';
/** @var array */
private $foreignAmount;
/** @var array */
private $modifiers = [];
/** @var User */
private $user;
@@ -80,12 +84,13 @@ class ImportJournal
*/
public function __construct()
{
$this->asset = new ImportAccount;
$this->opposing = new ImportAccount;
$this->bill = new ImportBill;
$this->category = new ImportCategory;
$this->budget = new ImportBudget;
$this->currency = new ImportCurrency;
$this->asset = new ImportAccount;
$this->opposing = new ImportAccount;
$this->bill = new ImportBill;
$this->category = new ImportCategory;
$this->budget = new ImportBudget;
$this->currency = new ImportCurrency;
$this->foreignCurrency = new ImportCurrency;
}
/**
@@ -179,6 +184,8 @@ class ImportJournal
*/
public function setValue(array $array)
{
$array['mapped'] = $array['mapped'] ?? null;
$array['value'] = $array['value'] ?? null;
switch ($array['role']) {
default:
throw new FireflyException(sprintf('ImportJournal cannot handle "%s" with value "%s".', $array['role'], $array['value']));
@@ -188,6 +195,12 @@ class ImportJournal
case 'amount':
$this->amount = $array;
break;
case 'amount_foreign':
$this->foreignAmount = $array;
break;
case 'foreign-currency-code':
$this->foreignCurrency->setId($array);
break;
case 'amount_debit':
$this->amountDebit = $array;
break;
@@ -302,6 +315,10 @@ class ImportJournal
if (0 === count($info)) {
throw new FireflyException('No amount information for this row.');
}
$class = $info['class'] ?? '';
if (strlen($class) === 0) {
throw new FireflyException('No amount information (conversion class) for this row.');
}
Log::debug(sprintf('Converter class is %s', $info['class']));
/** @var ConverterInterface $amountConverter */

View File

@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Import\Prerequisites;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\User;
use Illuminate\Http\Request;
use Illuminate\Support\MessageBag;
@@ -62,9 +63,13 @@ class FilePrerequisites implements PrerequisitesInterface
* True if prerequisites. False if not.
*
* @return bool
* @throws FireflyException
*/
public function hasPrerequisites(): bool
{
if($this->user->hasRole('demo')) {
throw new FireflyException('Apologies, the demo user cannot import files.');
}
return false;
}

View File

@@ -28,6 +28,7 @@ use FireflyIII\Import\FileProcessor\FileProcessorInterface;
use FireflyIII\Import\Storage\ImportStorage;
use FireflyIII\Models\ImportJob;
use FireflyIII\Models\Tag;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Illuminate\Support\Collection;
use Log;
@@ -46,6 +47,9 @@ class FileRoutine implements RoutineInterface
/** @var ImportJob */
private $job;
/** @var ImportJobRepositoryInterface */
private $repository;
/**
* ImportRoutine constructor.
*/
@@ -84,26 +88,32 @@ class FileRoutine implements RoutineInterface
*/
public function run(): bool
{
if ('configured' !== $this->job->status) {
Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->job->status));
if ('configured' !== $this->getStatus()) {
Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->getStatus()));
return false;
}
set_time_limit(0);
Log::info(sprintf('Start with import job %s', $this->job->key));
// total steps: 6
$this->setTotalSteps(6);
$importObjects = $this->getImportObjects();
$this->lines = $importObjects->count();
$this->addStep();
// total steps can now be extended. File has been scanned. 7 steps per line:
$this->addTotalSteps(7 * $this->lines);
// once done, use storage thing to actually store them:
Log::info(sprintf('Returned %d valid objects from file processor', $this->lines));
$storage = $this->storeObjects($importObjects);
$this->addStep();
Log::debug('Back in run()');
// update job:
$this->job->status = 'finished';
$this->job->save();
Log::debug('Updated job...');
Log::debug(sprintf('%d journals in $storage->journals', $storage->journals->count()));
@@ -114,6 +124,10 @@ class FileRoutine implements RoutineInterface
// create tag, link tag to all journals:
$this->createImportTag();
$this->addStep();
// update job:
$this->setStatus('finished');
Log::info(sprintf('Done with import job %s', $this->job->key));
@@ -125,7 +139,9 @@ class FileRoutine implements RoutineInterface
*/
public function setJob(ImportJob $job)
{
$this->job = $job;
$this->job = $job;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($job->user);
}
/**
@@ -134,18 +150,16 @@ class FileRoutine implements RoutineInterface
protected function getImportObjects(): Collection
{
$objects = new Collection;
$config = $this->job->configuration;
$fileType = $config['file-type'] ?? 'csv';
$fileType = $this->getConfig()['file-type'] ?? 'csv';
// will only respond to "file"
$class = config(sprintf('import.options.file.processors.%s', $fileType));
/** @var FileProcessorInterface $processor */
$processor = app($class);
$processor->setJob($this->job);
if ('configured' === $this->job->status) {
if ('configured' === $this->getStatus()) {
// set job as "running"...
$this->job->status = 'running';
$this->job->save();
$this->setStatus('running');
Log::debug('Job is configured, start with run()');
$processor->run();
@@ -155,6 +169,14 @@ class FileRoutine implements RoutineInterface
return $objects;
}
/**
* Shorthand method.
*/
private function addStep()
{
$this->repository->addStepsDone($this->job, 1);
}
/**
*
*/
@@ -167,11 +189,12 @@ class FileRoutine implements RoutineInterface
return new Tag;
}
$this->addTotalSteps($this->journals->count() + 2);
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$repository->setUser($this->job->user);
$data = [
$data = [
'tag' => trans('import.import_with_key', ['key' => $this->job->key]),
'date' => new Carbon,
'description' => null,
@@ -180,11 +203,11 @@ class FileRoutine implements RoutineInterface
'zoomLevel' => null,
'tagMode' => 'nothing',
];
$tag = $repository->store($data);
$extended = $this->job->extended_status;
$extended['tag'] = $tag->id;
$this->job->extended_status = $extended;
$this->job->save();
$tag = $repository->store($data);
$this->addStep();
$extended = $this->getExtendedStatus();
$extended['tag'] = $tag->id;
$this->setExtendedStatus($extended);
Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag));
Log::debug('Looping journals...');
@@ -193,12 +216,81 @@ class FileRoutine implements RoutineInterface
foreach ($journalIds as $journalId) {
Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId));
DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]);
$this->addStep();
}
Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $this->journals->count(), $tag->id, $tag->tag));
$this->addStep();
return $tag;
}
/**
* Shorthand method
*
* @return array
*/
private function getConfig(): array
{
return $this->repository->getConfiguration($this->job);
}
/**
* @return array
*/
private function getExtendedStatus(): array
{
return $this->repository->getExtendedStatus($this->job);
}
/**
* Shorthand method.
*
* @return string
*/
private function getStatus(): string
{
return $this->repository->getStatus($this->job);
}
/**
* @param array $extended
*/
private function setExtendedStatus(array $extended): void
{
$this->repository->setExtendedStatus($this->job, $extended);
return;
}
/**
* Shorthand
*
* @param string $status
*/
private function setStatus(string $status): void
{
$this->repository->setStatus($this->job, $status);
}
/**
* Shorthand
*
* @param int $steps
*/
private function setTotalSteps(int $steps)
{
$this->repository->setTotalSteps($this->job, $steps);
}
/**
* Shorthand
*
* @param int $steps
*/
private function addTotalSteps(int $steps)
{
$this->repository->addTotalSteps($this->job, $steps);
}
/**
* @param Collection $objects
*
@@ -206,9 +298,10 @@ class FileRoutine implements RoutineInterface
*/
private function storeObjects(Collection $objects): ImportStorage
{
$config = $this->getConfig();
$storage = new ImportStorage;
$storage->setJob($this->job);
$storage->setDateFormat($this->job->configuration['date-format']);
$storage->setDateFormat($config['date-format']);
$storage->setObjects($objects);
$storage->store();
Log::info('Back in storeObjects()');

View File

@@ -22,11 +22,26 @@ declare(strict_types=1);
namespace FireflyIII\Import\Routine;
use Carbon\Carbon;
use DB;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Import\Object\ImportJournal;
use FireflyIII\Import\Storage\ImportStorage;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\Services\Spectre\Exception\DuplicatedCustomerException;
use FireflyIII\Services\Spectre\Exception\SpectreException;
use FireflyIII\Services\Spectre\Object\Account;
use FireflyIII\Services\Spectre\Object\Customer;
use FireflyIII\Services\Spectre\Object\Login;
use FireflyIII\Services\Spectre\Object\Token;
use FireflyIII\Services\Spectre\Object\Transaction;
use FireflyIII\Services\Spectre\Request\CreateTokenRequest;
use FireflyIII\Services\Spectre\Request\ListAccountsRequest;
use FireflyIII\Services\Spectre\Request\ListCustomersRequest;
use FireflyIII\Services\Spectre\Request\ListLoginsRequest;
use FireflyIII\Services\Spectre\Request\ListTransactionsRequest;
use FireflyIII\Services\Spectre\Request\NewCustomerRequest;
use Illuminate\Support\Collection;
use Log;
@@ -46,6 +61,9 @@ class SpectreRoutine implements RoutineInterface
/** @var ImportJob */
private $job;
/** @var ImportJobRepositoryInterface */
private $repository;
/**
* ImportRoutine constructor.
*/
@@ -80,58 +98,58 @@ class SpectreRoutine implements RoutineInterface
}
/**
* A Spectre job that ends up here is either "configured" or "running", and will be set to "running"
* when it is "configured".
*
* Job has several stages, stored in extended status key 'stage'
*
* initial: just begun, nothing happened. action: get a customer and a token. Next status: has-token
* has-token: redirect user to sandstorm, make user login. set job to: user-logged-in
* user-logged-in: customer has an attempt. action: analyse/get attempt and go for next status.
* if attempt failed: job status is error, save a warning somewhere?
* if success, try to get accounts. Save in config key 'accounts'. set status: have-accounts and "configuring"
*
* have-accounts: make user link accounts and select accounts to import from.
*
* If job is "configuring" and stage "have-accounts" then present the accounts and make user link them to
* own asset accounts. Store this mapping, set config to "have-account-mapping" and job status configured".
*
* have-account-mapping: start downloading transactions?
*
*
* @throws \FireflyIII\Exceptions\FireflyException
* @throws \FireflyIII\Services\Spectre\Exception\SpectreException
*/
public function run(): bool
{
if ('configured' !== $this->job->status) {
Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->job->status));
return false;
if ('configured' === $this->getStatus()) {
$this->repository->updateStatus($this->job, 'running');
}
Log::info(sprintf('Start with import job %s using Spectre.', $this->job->key));
set_time_limit(0);
// check if job has token first!
$config = $this->job->configuration;
$hasToken = $config['has-token'] ?? false;
if ($hasToken === false) {
Log::debug('Job has no token');
// create customer if user does not have one:
$customer = $this->getCustomer();
Log::debug(sprintf('Customer ID is %s', $customer->getId()));
// use customer to request a token:
$uri = route('import.status', [$this->job->key]);
$token = $this->getToken($customer, $uri);
Log::debug(sprintf('Token is %s', $token->getToken()));
$stage = $this->getConfig()['stage'] ?? 'unknown';
// update job, give it the token:
$config = $this->job->configuration;
$config['has-token'] = true;
$config['token'] = $token->getToken();
$config['token-expires'] = $token->getExpiresAt()->format('U');
$config['token-url'] = $token->getConnectUrl();
$this->job->configuration = $config;
Log::debug('Job config is now', $config);
// update job, set status to "configuring".
$this->job->status = 'configuring';
$this->job->save();
Log::debug(sprintf('Job status is now %s', $this->job->status));
return true;
}
$isRedirected = $config['is-redirected'] ?? false;
if ($isRedirected === true) {
// assume user has "used" the token.
// ...
// now what?
throw new FireflyException('Application cannot handle this.');
switch ($stage) {
case 'initial':
// get customer and token:
$this->runStageInitial();
break;
case 'has-token':
// import routine does nothing at this point:
break;
case 'user-logged-in':
$this->runStageLoggedIn();
break;
case 'have-account-mapping':
$this->runStageHaveMapping();
break;
default:
throw new FireflyException(sprintf('Cannot handle stage %s', $stage));
}
throw new FireflyException('Application cannot handle this.');
return true;
}
/**
@@ -139,18 +157,40 @@ class SpectreRoutine implements RoutineInterface
*/
public function setJob(ImportJob $job)
{
$this->job = $job;
$this->job = $job;
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($job->user);
}
/**
* @return Customer
* @throws \FireflyIII\Exceptions\FireflyException
* @throws \FireflyIII\Services\Spectre\Exception\SpectreException
*/
protected function createCustomer(): Customer
{
$newCustomerRequest = new NewCustomerRequest($this->job->user);
$newCustomerRequest->call();
$customer = $newCustomerRequest->getCustomer();
$customer = null;
try {
$newCustomerRequest->call();
$customer = $newCustomerRequest->getCustomer();
} catch (DuplicatedCustomerException $e) {
// already exists, must fetch customer instead.
Log::warning('Customer exists already for user, fetch it.');
}
if (is_null($customer)) {
$getCustomerRequest = new ListCustomersRequest($this->job->user);
$getCustomerRequest->call();
$customers = $getCustomerRequest->getCustomers();
/** @var Customer $current */
foreach ($customers as $current) {
if ($current->getIdentifier() === 'default_ff3_customer') {
$customer = $current;
break;
}
}
}
Preferences::setForUser($this->job->user, 'spectre_customer', $customer->toArray());
@@ -160,15 +200,25 @@ class SpectreRoutine implements RoutineInterface
/**
* @return Customer
* @throws \FireflyIII\Exceptions\FireflyException
* @throws FireflyException
* @throws \FireflyIII\Services\Spectre\Exception\SpectreException
*/
protected function getCustomer(): Customer
{
$preference = Preferences::getForUser($this->job->user, 'spectre_customer', null);
if (is_null($preference)) {
return $this->createCustomer();
$config = $this->getConfig();
if (!is_null($config['customer'])) {
$customer = new Customer($config['customer']);
return $customer;
}
$customer = new Customer($preference->data);
$customer = $this->createCustomer();
$config['customer'] = [
'id' => $customer->getId(),
'identifier' => $customer->getIdentifier(),
'secret' => $customer->getSecret(),
];
$this->setConfig($config);
return $customer;
}
@@ -179,6 +229,7 @@ class SpectreRoutine implements RoutineInterface
*
* @return Token
* @throws \FireflyIII\Exceptions\FireflyException
* @throws \FireflyIII\Services\Spectre\Exception\SpectreException
*/
protected function getToken(Customer $customer, string $returnUri): Token
{
@@ -191,4 +242,351 @@ class SpectreRoutine implements RoutineInterface
return $request->getToken();
}
/**
* @throws FireflyException
* @throws SpectreException
*/
protected function runStageInitial(): void
{
Log::debug('In runStageInitial()');
// create customer if user does not have one:
$customer = $this->getCustomer();
Log::debug(sprintf('Customer ID is %s', $customer->getId()));
// use customer to request a token:
$uri = route('import.status', [$this->job->key]);
$token = $this->getToken($customer, $uri);
Log::debug(sprintf('Token is %s', $token->getToken()));
// update job, give it the token:
$config = $this->getConfig();
$config['has-token'] = true;
$config['token'] = $token->getToken();
$config['token-expires'] = $token->getExpiresAt()->format('U');
$config['token-url'] = $token->getConnectUrl();
$config['stage'] = 'has-token';
$this->setConfig($config);
Log::debug('Job config is now', $config);
// update job, set status to "configuring".
$this->setStatus('configuring');
Log::debug(sprintf('Job status is now %s', $this->job->status));
$this->addStep();
}
/**
* @throws FireflyException
* @throws SpectreException
*/
protected function runStageLoggedIn(): void
{
Log::debug('In runStageLoggedIn');
// list all logins:
$customer = $this->getCustomer();
$request = new ListLoginsRequest($this->job->user);
$request->setCustomer($customer);
$request->call();
$logins = $request->getLogins();
/** @var Login $final */
$final = null;
// loop logins, find the latest with no error in it:
$time = 0;
/** @var Login $login */
foreach ($logins as $login) {
$attempt = $login->getLastAttempt();
$attemptTime = intval($attempt->getCreatedAt()->format('U'));
if ($attemptTime > $time && is_null($attempt->getFailErrorClass())) {
$time = $attemptTime;
$final = $login;
}
}
if (is_null($final)) {
Log::error('Could not find a valid login for this user.');
$this->repository->addError($this->job, 0, 'Spectre connection failed. Did you use invalid credentials, press Cancel or failed the 2FA challenge?');
$this->repository->setStatus($this->job, 'error');
return;
}
$this->addStep();
// list the users accounts using this login.
$accountRequest = new ListAccountsRequest($this->job->user);
$accountRequest->setLogin($login);
$accountRequest->call();
$accounts = $accountRequest->getAccounts();
// store accounts in job:
$all = [];
/** @var Account $account */
foreach ($accounts as $account) {
$all[] = $account->toArray();
}
// update job:
$config = $this->getConfig();
$config['accounts'] = $all;
$config['login'] = $login->toArray();
$config['stage'] = 'have-accounts';
$this->setConfig($config);
$this->setStatus('configuring');
$this->addStep();
return;
}
/**
* Shorthand method.
*/
private function addStep()
{
$this->repository->addStepsDone($this->job, 1);
}
/**
* Shorthand
*
* @param int $steps
*/
private function addTotalSteps(int $steps)
{
$this->repository->addTotalSteps($this->job, $steps);
}
/**
* @return array
*/
private function getConfig(): array
{
return $this->repository->getConfiguration($this->job);
}
/**
* Shorthand method.
*
* @return array
*/
private function getExtendedStatus(): array
{
return $this->repository->getExtendedStatus($this->job);
}
/**
* Shorthand method.
*
* @return string
*/
private function getStatus(): string
{
return $this->repository->getStatus($this->job);
}
/**
* @param array $all
*
* @throws FireflyException
*/
private function importTransactions(array $all)
{
Log::debug('Going to import transactions');
$collection = new Collection;
// create import objects?
foreach ($all as $accountId => $data) {
Log::debug(sprintf('Now at account #%d', $accountId));
/** @var Transaction $transaction */
foreach ($data['transactions'] as $transaction) {
Log::debug(sprintf('Now at transaction #%d', $transaction->getId()));
/** @var Account $account */
$account = $data['account'];
$importJournal = new ImportJournal;
$importJournal->setUser($this->job->user);
$importJournal->asset->setDefaultAccountId($data['import_id']);
// call set value a bunch of times for various data entries:
$tags = [];
$tags[] = $transaction->getMode();
$tags[] = $transaction->getStatus();
if ($transaction->isDuplicated()) {
$tags[] = 'possibly-duplicated';
}
$extra = $transaction->getExtra()->toArray();
$notes = '';
$notes .= strval(trans('import.imported_from_account', ['account' => $account->getName()])) . ' '
. "\n"; // double space for newline in Markdown.
foreach ($extra as $key => $value) {
switch ($key) {
case 'account_number':
$importJournal->setValue(['role' => 'account-number', 'value' => $value]);
break;
case 'original_category':
case 'original_subcategory':
case 'customer_category_code':
case 'customer_category_name':
$tags[] = $value;
break;
case 'payee':
$importJournal->setValue(['role' => 'opposing-name', 'value' => $value]);
break;
case 'original_amount':
$importJournal->setValue(['role' => 'amount_foreign', 'value' => $value]);
break;
case 'original_currency_code':
$importJournal->setValue(['role' => 'foreign-currency-code', 'value' => $value]);
break;
default:
$notes .= $key . ': ' . $value . ' '; // for newline in Markdown.
}
}
// hash
$importJournal->setHash($transaction->getHash());
// account ID (Firefly III account):
$importJournal->setValue(['role' => 'account-id', 'value' => $data['import_id'], 'mapped' => $data['import_id']]);
// description:
$importJournal->setValue(['role' => 'description', 'value' => $transaction->getDescription()]);
// date:
$importJournal->setValue(['role' => 'date-transaction', 'value' => $transaction->getMadeOn()->toIso8601String()]);
// amount
$importJournal->setValue(['role' => 'amount', 'value' => $transaction->getAmount()]);
$importJournal->setValue(['role' => 'currency-code', 'value' => $transaction->getCurrencyCode()]);
// various meta fields:
$importJournal->setValue(['role' => 'category-name', 'value' => $transaction->getCategory()]);
$importJournal->setValue(['role' => 'note', 'value' => $notes]);
$importJournal->setValue(['role' => 'tags-comma', 'value' => join(',', $tags)]);
$collection->push($importJournal);
}
}
$this->addStep();
Log::debug(sprintf('Going to try and store all %d them.', $collection->count()));
$this->addTotalSteps(7 * $collection->count());
// try to store them (seven steps per transaction)
$storage = new ImportStorage;
$storage->setJob($this->job);
$storage->setDateFormat('Y-m-d\TH:i:sO');
$storage->setObjects($collection);
$storage->store();
Log::info('Back in importTransactions()');
// link to tag
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$repository->setUser($this->job->user);
$data = [
'tag' => trans('import.import_with_key', ['key' => $this->job->key]),
'date' => new Carbon,
'description' => null,
'latitude' => null,
'longitude' => null,
'zoomLevel' => null,
'tagMode' => 'nothing',
];
$tag = $repository->store($data);
$extended = $this->getExtendedStatus();
$extended['tag'] = $tag->id;
$this->setExtendedStatus($extended);
Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag));
Log::debug('Looping journals...');
$journalIds = $storage->journals->pluck('id')->toArray();
$tagId = $tag->id;
$this->addTotalSteps(count($journalIds));
foreach ($journalIds as $journalId) {
Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId));
DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]);
$this->addStep();
}
Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $storage->journals->count(), $tag->id, $tag->tag));
// set status to "finished"?
// update job:
$this->setStatus('finished');
$this->addStep();
return;
}
/**
* @throws FireflyException
* @throws SpectreException
*/
private function runStageHaveMapping()
{
$config = $this->getConfig();
$accounts = $config['accounts'] ?? [];
$all = [];
$count = 0;
/** @var array $accountArray */
foreach ($accounts as $accountArray) {
$account = new Account($accountArray);
$importId = intval($config['accounts-mapped'][$account->getid()] ?? 0);
$doImport = $importId !== 0 ? true : false;
if (!$doImport) {
Log::debug(sprintf('Will NOT import from Spectre account #%d ("%s")', $account->getId(), $account->getName()));
continue;
}
// grab all transactions
$listTransactionsRequest = new ListTransactionsRequest($this->job->user);
$listTransactionsRequest->setAccount($account);
$listTransactionsRequest->call();
$transactions = $listTransactionsRequest->getTransactions();
$all[$account->getId()] = [
'account' => $account,
'import_id' => $importId,
'transactions' => $transactions,
];
$count += count($transactions);
}
Log::debug(sprintf('Total number of transactions: %d', $count));
$this->addStep();
$this->importTransactions($all);
}
/**
* Shorthand.
*
* @param array $config
*/
private function setConfig(array $config): void
{
$this->repository->setConfiguration($this->job, $config);
return;
}
/**
* Shorthand method.
*
* @param array $extended
*/
private function setExtendedStatus(array $extended): void
{
$this->repository->setExtendedStatus($this->job, $extended);
return;
}
/**
* Shorthand.
*
* @param string $status
*/
private function setStatus(string $status): void
{
$this->repository->setStatus($this->job, $status);
}
}

View File

@@ -50,7 +50,10 @@ class SnsDescription implements SpecificInterface
*/
public function run(array $row): array
{
$row = array_values($row);
$row = array_values($row);
if (!isset($row[17])) {
return $row;
}
$row[17] = ltrim($row[17], "'");
$row[17] = rtrim($row[17], "'");

View File

@@ -30,11 +30,22 @@ use FireflyIII\Models\ImportJob;
use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use Illuminate\Support\Collection;
use Log;
use Preferences;
/**
* Is capable of storing individual ImportJournal objects.
* Adds 7 steps per object stored:
* 1. get all import data from import journal
* 2. is not a duplicate
* 3. create the journal
* 4. store journal
* 5. run rules
* 6. run bills
* 7. finished storing object
*
* Class ImportStorage.
*/
class ImportStorage
@@ -53,6 +64,8 @@ class ImportStorage
protected $defaultCurrencyId = 1;
/** @var ImportJob */
protected $job;
/** @var ImportJobRepositoryInterface */
protected $repository;
/** @var Collection */
protected $rules;
/** @var bool */
@@ -63,6 +76,8 @@ class ImportStorage
private $matchBills = false;
/** @var Collection */
private $objects;
/** @var int */
private $total = 0;
/** @var array */
private $transfers = [];
@@ -89,13 +104,17 @@ class ImportStorage
*/
public function setJob(ImportJob $job)
{
$this->job = $job;
$currency = app('amount')->getDefaultCurrencyByUser($this->job->user);
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($job->user);
$config = $this->repository->getConfiguration($job);
$currency = app('amount')->getDefaultCurrencyByUser($job->user);
$this->defaultCurrencyId = $currency->id;
$this->job = $job;
$this->transfers = $this->getTransfers();
$config = $job->configuration;
$this->applyRules = $config['apply_rules'] ?? false;
$this->matchBills = $config['match_bills'] ?? false;
$this->applyRules = $config['apply-rules'] ?? false;
$this->matchBills = $config['match-bills'] ?? false;
if (true === $this->applyRules) {
Log::debug('applyRules seems to be true, get the rules.');
$this->rules = $this->getRules();
@@ -108,6 +127,8 @@ class ImportStorage
}
Log::debug(sprintf('Value of apply rules is %s', var_export($this->applyRules, true)));
Log::debug(sprintf('Value of match bills is %s', var_export($this->matchBills, true)));
}
/**
@@ -116,6 +137,7 @@ class ImportStorage
public function setObjects(Collection $objects)
{
$this->objects = $objects;
$this->total = $objects->count();
}
/**
@@ -129,6 +151,7 @@ class ImportStorage
function (ImportJournal $importJournal, int $index) {
try {
$this->storeImportJournal($index, $importJournal);
$this->addStep();
} catch (FireflyException | ErrorException | Exception $e) {
$this->errors->push($e->getMessage());
Log::error(sprintf('Cannot import row #%d because: %s', $index, $e->getMessage()));
@@ -150,7 +173,7 @@ class ImportStorage
*/
protected function storeImportJournal(int $index, ImportJournal $importJournal): bool
{
Log::debug(sprintf('Going to store object #%d with description "%s"', $index, $importJournal->getDescription()));
Log::debug(sprintf('Going to store object #%d/%d with description "%s"', ($index + 1), $this->total, $importJournal->getDescription()));
$assetAccount = $importJournal->asset->getAccount();
$amount = $importJournal->getAmount();
$currencyId = $this->getCurrencyId($importJournal);
@@ -159,9 +182,7 @@ class ImportStorage
$opposingAccount = $this->getOpposingAccount($importJournal->opposing, $assetAccount->id, $amount);
$transactionType = $this->getTransactionType($amount, $opposingAccount);
$description = $importJournal->getDescription();
// First step done!
$this->job->addStepsDone(1);
$this->addStep();
/**
* Check for double transfer.
@@ -175,13 +196,17 @@ class ImportStorage
'opposing' => $opposingAccount->name,
];
if ($this->isDoubleTransfer($parameters) || $this->hashAlreadyImported($importJournal->hash)) {
$this->job->addStepsDone(3);
// throw error
$message = sprintf('Detected a possible duplicate, skip this one (hash: %s).', $importJournal->hash);
Log::error($message, $parameters);
// add five steps to keep the pace:
$this->addSteps(5);
throw new FireflyException($message);
}
unset($parameters);
$this->addStep();
// store journal and create transactions:
$parameters = [
@@ -197,9 +222,7 @@ class ImportStorage
];
$journal = $this->storeJournal($parameters);
unset($parameters);
// Another step done!
$this->job->addStepsDone(1);
$this->addStep();
// store meta object things:
$this->storeCategory($journal, $importJournal->category->getCategory());
@@ -221,31 +244,31 @@ class ImportStorage
// set journal completed:
$journal->completed = true;
$journal->save();
// Another step done!
$this->job->addStepsDone(1);
$this->addStep();
// run rules if config calls for it:
if (true === $this->applyRules) {
Log::info('Will apply rules to this journal.');
$this->applyRules($journal);
}
Preferences::setForUser($this->job->user, 'lastActivity', microtime());
if (!(true === $this->applyRules)) {
Log::info('Will NOT apply rules to this journal.');
}
$this->addStep();
// match bills if config calls for it.
if (true === $this->matchBills) {
Log::info('Cannot match bills (yet).');
Log::info('Will match bills.');
$this->matchBills($journal);
}
if (!(true === $this->matchBills)) {
Log::info('Cannot match bills (yet), but do not have to.');
}
$this->addStep();
// Another step done!
$this->job->addStepsDone(1);
$this->journals->push($journal);
Log::info(sprintf('Imported new journal #%d: "%s", amount %s %s.', $journal->id, $journal->description, $journal->transactionCurrency->code, $amount));
@@ -253,6 +276,24 @@ class ImportStorage
return true;
}
/**
* Shorthand method.
*/
private function addStep()
{
$this->repository->addStepsDone($this->job, 1);
}
/**
* Shorthand method
*
* @param int $steps
*/
private function addSteps(int $steps)
{
$this->repository->addStepsDone($this->job, $steps);
}
/**
* @param array $parameters
*
@@ -269,7 +310,6 @@ class ImportStorage
$amount = app('steam')->positive($parameters['amount']);
$names = [$parameters['asset'], $parameters['opposing']];
$transfer = [];
sort($names);

View File

@@ -121,11 +121,11 @@ trait ImportSupport
{
$transaction = new Transaction;
$transaction->account_id = $parameters['account'];
$transaction->transaction_journal_id = $parameters['id'];
$transaction->transaction_currency_id = $parameters['currency'];
$transaction->transaction_journal_id = intval($parameters['id']);
$transaction->transaction_currency_id = intval($parameters['currency']);
$transaction->amount = $parameters['amount'];
$transaction->foreign_currency_id = $parameters['foreign_currency'];
$transaction->foreign_amount = $parameters['foreign_amount'];
$transaction->foreign_currency_id = intval($parameters['foreign_currency']) === 0 ? null : intval($parameters['foreign_currency']);
$transaction->foreign_amount = null === $transaction->foreign_currency_id ? null : $parameters['foreign_amount'];
$transaction->save();
if (null === $transaction->id) {
$errorText = join(', ', $transaction->getErrors()->all());
@@ -155,6 +155,7 @@ trait ImportSupport
* @param ImportJournal $importJournal
*
* @return int
* @throws FireflyException
*/
private function getCurrencyId(ImportJournal $importJournal): int
{
@@ -192,7 +193,7 @@ trait ImportSupport
{
// use given currency by import journal.
$currency = $importJournal->currency->getTransactionCurrency();
if (null !== $currency->id && $currency->id !== $currencyId) {
if (null !== $currency->id && intval($currency->id) !== intval($currencyId)) {
return $currency->id;
}
@@ -216,6 +217,7 @@ trait ImportSupport
* @see ImportSupport::getTransactionType
*
* @return Account
* @throws FireflyException
*/
private function getOpposingAccount(ImportAccount $account, int $forbiddenAccount, string $amount): Account
{
@@ -435,8 +437,6 @@ trait ImportSupport
if (!$journal->save()) {
$errorText = join(', ', $journal->getErrors()->all());
// add three steps:
$this->job->addStepsDone(3);
// throw error
throw new FireflyException($errorText);
}

View File

@@ -212,7 +212,7 @@ class Account extends Model
*
* @return string
*/
public function getNameAttribute($value): string
public function getNameAttribute($value): ?string
{
if ($this->encrypted) {
return Crypt::decrypt($value);
@@ -345,6 +345,15 @@ class Account extends Model
$this->attributes['iban'] = Crypt::encrypt($value);
}
/**
* @codeCoverageIgnore
* Get all of the notes.
*/
public function notes()
{
return $this->morphMany(Note::class, 'noteable');
}
/**
* @codeCoverageIgnore
*

View File

@@ -132,7 +132,7 @@ class Bill extends Model
*/
public function notes()
{
return $this->morphMany('FireflyIII\Models\Note', 'noteable');
return $this->morphMany(Note::class, 'noteable');
}
/**

View File

@@ -30,6 +30,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* Class ExportJob.
*
* @property User $user
* @property string $key
*/
class ExportJob extends Model
{

View File

@@ -82,32 +82,6 @@ class ImportJob extends Model
throw new NotFoundHttpException;
}
/**
* @param int $index
* @param string $message
*
* @return bool
*/
public function addError(int $index, string $message): bool
{
$extended = $this->extended_status;
$extended['errors'][$index][] = $message;
$this->extended_status = $extended;
return true;
}
/**
* @param int $count
*/
public function addStepsDone(int $count)
{
$status = $this->extended_status;
$status['done'] += $count;
$this->extended_status = $status;
$this->save();
}
/**
* @param int $count
*/
@@ -117,6 +91,7 @@ class ImportJob extends Model
$status['steps'] += $count;
$this->extended_status = $status;
$this->save();
Log::debug(sprintf('Add %d to total steps for job "%s" making total steps %d', $count, $this->key, $status['steps']));
}
/**
@@ -161,7 +136,7 @@ class ImportJob extends Model
*/
public function getExtendedStatusAttribute($value)
{
if (0 === strlen($value)) {
if (0 === strlen(strval($value))) {
return [];
}
@@ -209,6 +184,7 @@ class ImportJob extends Model
$disk = Storage::disk('upload');
$encryptedContent = $disk->get($fileName);
$content = Crypt::decrypt($encryptedContent);
$content = trim($content);
Log::debug(sprintf('Content size is %d bytes.', strlen($content)));
return $content;

View File

@@ -57,8 +57,8 @@ class Note extends Model
/**
* @codeCoverageIgnore
* Get all of the owning noteable models. Currently piggy bank and
* transaction journal.
*
* Get all of the owning noteable models.
*/
public function noteable()
{

View File

@@ -158,7 +158,7 @@ class PiggyBank extends Model
public function leftOnAccount(Carbon $date): string
{
$balance = Steam::balanceIgnoreVirtual($this->account, $date);
// @var PiggyBank $p
/** @var PiggyBank $piggyBank */
foreach ($this->account->piggyBanks as $piggyBank) {
$currentAmount = $piggyBank->currentRelevantRep()->currentamount ?? '0';

View File

@@ -66,6 +66,7 @@ use Watson\Validating\ValidatingTrait;
* @property string $transaction_currency_symbol
* @property int $transaction_currency_dp
* @property string $transaction_currency_code
* @property string $description
*/
class Transaction extends Model
{

View File

@@ -28,6 +28,9 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class TransactionCurrency.
*
* @property string $code
*
*/
class TransactionCurrency extends Model
{

View File

@@ -92,8 +92,6 @@ class TransactionJournal extends Model
if (auth()->check()) {
$journalId = intval($value);
$journal = auth()->user()->transactionJournals()->where('transaction_journals.id', $journalId)
->with('transactionType')
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->first(['transaction_journals.*']);
if (!is_null($journal)) {
return $journal;

Some files were not shown because too many files have changed in this diff Show More