mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-11-03 20:55:05 +00:00 
			
		
		
		
	Compare commits
	
		
			825 Commits
		
	
	
		
			develop-20
			...
			develop-20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					582671ca84 | ||
| 
						 | 
					22498b5804 | ||
| 
						 | 
					87f277a482 | ||
| 
						 | 
					ae0d74f57a | ||
| 
						 | 
					0ae5593dde | ||
| 
						 | 
					0d11769590 | ||
| 
						 | 
					b7d8daf013 | ||
| 
						 | 
					a9c0126b05 | ||
| 
						 | 
					6bc5a57d10 | ||
| 
						 | 
					2714ee96f1 | ||
| 
						 | 
					524d382b7a | ||
| 
						 | 
					2723e05d2a | ||
| 
						 | 
					6dd9bda6b4 | ||
| 
						 | 
					44449bc716 | ||
| 
						 | 
					b17d8edb50 | ||
| 
						 | 
					578072238a | ||
| 
						 | 
					b4edd3dcc4 | ||
| 
						 | 
					068094caac | ||
| 
						 | 
					deb58e617d | ||
| 
						 | 
					baca0c1120 | ||
| 
						 | 
					02543438a4 | ||
| 
						 | 
					d507e59038 | ||
| 
						 | 
					9d0fd7ef1b | ||
| 
						 | 
					dbef5e2143 | ||
| 
						 | 
					04eca755d2 | ||
| 
						 | 
					7883692196 | ||
| 
						 | 
					8f64977cb9 | ||
| 
						 | 
					f94fdc4979 | ||
| 
						 | 
					a0a0e28447 | ||
| 
						 | 
					f6f7783b94 | ||
| 
						 | 
					d233cc1de8 | ||
| 
						 | 
					37671499c8 | ||
| 
						 | 
					c83b79998d | ||
| 
						 | 
					ed842c2b42 | ||
| 
						 | 
					8c5f114339 | ||
| 
						 | 
					8b2f1d0b4f | ||
| 
						 | 
					591a1b3050 | ||
| 
						 | 
					42ec3fe02b | ||
| 
						 | 
					370a398b5e | ||
| 
						 | 
					554d89b6e9 | ||
| 
						 | 
					cb049f5dda | ||
| 
						 | 
					0728668d41 | ||
| 
						 | 
					dfc187874e | ||
| 
						 | 
					225588f3e7 | ||
| 
						 | 
					06cc6c29aa | ||
| 
						 | 
					b2d4469908 | ||
| 
						 | 
					c398383905 | ||
| 
						 | 
					7af9dce33b | ||
| 
						 | 
					038790a5d6 | ||
| 
						 | 
					fb3295bde1 | ||
| 
						 | 
					43a4fd2ecb | ||
| 
						 | 
					899c72d068 | ||
| 
						 | 
					d118c0d886 | ||
| 
						 | 
					6d4004d1ed | ||
| 
						 | 
					ae60cd5b28 | ||
| 
						 | 
					ab31a72199 | ||
| 
						 | 
					2c1b9534f3 | ||
| 
						 | 
					7028cb1546 | ||
| 
						 | 
					dc1ecf6a42 | ||
| 
						 | 
					3a27f9d02c | ||
| 
						 | 
					4b27ab38f8 | ||
| 
						 | 
					40de147611 | ||
| 
						 | 
					df5756dc86 | ||
| 
						 | 
					bb4f90d730 | ||
| 
						 | 
					d89d46aaec | ||
| 
						 | 
					304d720c4c | ||
| 
						 | 
					7eff160190 | ||
| 
						 | 
					8b2e18ed9d | ||
| 
						 | 
					7001051833 | ||
| 
						 | 
					b4b9752c05 | ||
| 
						 | 
					acadc89eaa | ||
| 
						 | 
					6ff84b8e90 | ||
| 
						 | 
					7f3e3fc3bf | ||
| 
						 | 
					02233fd7a4 | ||
| 
						 | 
					50d3db0643 | ||
| 
						 | 
					3751831779 | ||
| 
						 | 
					14a24e47fb | ||
| 
						 | 
					b7e78cb0e6 | ||
| 
						 | 
					a8f65f42fc | ||
| 
						 | 
					d3385a116d | ||
| 
						 | 
					e0c446dd13 | ||
| 
						 | 
					33d11b4780 | ||
| 
						 | 
					07c49d1d04 | ||
| 
						 | 
					9463285ac9 | ||
| 
						 | 
					b41fc43e64 | ||
| 
						 | 
					562763c938 | ||
| 
						 | 
					ec60194110 | ||
| 
						 | 
					1e472ee095 | ||
| 
						 | 
					5597327448 | ||
| 
						 | 
					cdd5baf5be | ||
| 
						 | 
					7b5978059b | ||
| 
						 | 
					da0b41e45c | ||
| 
						 | 
					d0be2afba5 | ||
| 
						 | 
					d99851231a | ||
| 
						 | 
					7e02c141f9 | ||
| 
						 | 
					d03960e379 | ||
| 
						 | 
					16d3984ffc | ||
| 
						 | 
					856a194988 | ||
| 
						 | 
					1bff966bfe | ||
| 
						 | 
					1948b6118b | ||
| 
						 | 
					20c25d3ca2 | ||
| 
						 | 
					a153735ac3 | ||
| 
						 | 
					62509f7c18 | ||
| 
						 | 
					9b48b67158 | ||
| 
						 | 
					cbd50634a4 | ||
| 
						 | 
					f475393bc1 | ||
| 
						 | 
					abcddb09bf | ||
| 
						 | 
					cf71a0fc55 | ||
| 
						 | 
					78253f9e1e | ||
| 
						 | 
					ebd0848c7f | ||
| 
						 | 
					c8461eb0b5 | ||
| 
						 | 
					a4cbdeaeac | ||
| 
						 | 
					3e1ce69d52 | ||
| 
						 | 
					08a26b976e | ||
| 
						 | 
					5fc55381a2 | ||
| 
						 | 
					dbf3d24ae7 | ||
| 
						 | 
					cc7c6e02c5 | ||
| 
						 | 
					b45aa85853 | ||
| 
						 | 
					e7526ac5e3 | ||
| 
						 | 
					441ada70b8 | ||
| 
						 | 
					dedc06a46b | ||
| 
						 | 
					b0adf1b277 | ||
| 
						 | 
					28f65e9f44 | ||
| 
						 | 
					a013af5f0d | ||
| 
						 | 
					9552701662 | ||
| 
						 | 
					ef52f0aad1 | ||
| 
						 | 
					0b6f04905a | ||
| 
						 | 
					cdb36357d4 | ||
| 
						 | 
					8938622bd9 | ||
| 
						 | 
					b210294aa9 | ||
| 
						 | 
					5b02f20775 | ||
| 
						 | 
					fac382a5df | ||
| 
						 | 
					88d88bebc9 | ||
| 
						 | 
					755fb9c29b | ||
| 
						 | 
					51a835ab51 | ||
| 
						 | 
					c9895ab182 | ||
| 
						 | 
					e71d46a4e5 | ||
| 
						 | 
					8d1d5f37c1 | ||
| 
						 | 
					525a68682d | ||
| 
						 | 
					715648d0d8 | ||
| 
						 | 
					9452e93f22 | ||
| 
						 | 
					a6aa145471 | ||
| 
						 | 
					25aa6dcb59 | ||
| 
						 | 
					bb2270b274 | ||
| 
						 | 
					d7f6b4143e | ||
| 
						 | 
					0cf0e26fa8 | ||
| 
						 | 
					cc23197d60 | ||
| 
						 | 
					bc1721d95e | ||
| 
						 | 
					0d19173da6 | ||
| 
						 | 
					1983f07d3c | ||
| 
						 | 
					aad1b91cc2 | ||
| 
						 | 
					8cb1057a33 | ||
| 
						 | 
					b178032985 | ||
| 
						 | 
					561213e95d | ||
| 
						 | 
					44fa7c4306 | ||
| 
						 | 
					e2169563e2 | ||
| 
						 | 
					845344e003 | ||
| 
						 | 
					cdb48453e8 | ||
| 
						 | 
					9669cef518 | ||
| 
						 | 
					f962f71ed7 | ||
| 
						 | 
					94ed4021fb | ||
| 
						 | 
					1765855c57 | ||
| 
						 | 
					55cf3e7d44 | ||
| 
						 | 
					9f1840dc05 | ||
| 
						 | 
					78dab2e5f9 | ||
| 
						 | 
					103b9d5005 | ||
| 
						 | 
					1b75b778d8 | ||
| 
						 | 
					7e665dbdfc | ||
| 
						 | 
					b6897ec3a9 | ||
| 
						 | 
					660260174a | ||
| 
						 | 
					78d32865b5 | ||
| 
						 | 
					edfa92c1aa | ||
| 
						 | 
					63012f269c | ||
| 
						 | 
					7d0e7f779f | ||
| 
						 | 
					b8e18f80f4 | ||
| 
						 | 
					481b01e4f7 | ||
| 
						 | 
					edf2030251 | ||
| 
						 | 
					bd1cfffb61 | ||
| 
						 | 
					629f70d27d | ||
| 
						 | 
					57f5ebc0f9 | ||
| 
						 | 
					b4f51e7b47 | ||
| 
						 | 
					d78d254e86 | ||
| 
						 | 
					eb0c113699 | ||
| 
						 | 
					23045ebd59 | ||
| 
						 | 
					2e5931f304 | ||
| 
						 | 
					a620b07c00 | ||
| 
						 | 
					cb724145f2 | ||
| 
						 | 
					8ef17f6686 | ||
| 
						 | 
					debfd9160c | ||
| 
						 | 
					f2482e4ace | ||
| 
						 | 
					d98d757f8b | ||
| 
						 | 
					0c9a41a929 | ||
| 
						 | 
					e26f78bf50 | ||
| 
						 | 
					ed265f68ba | ||
| 
						 | 
					d2e9b64bf5 | ||
| 
						 | 
					3811aff206 | ||
| 
						 | 
					762d898fee | ||
| 
						 | 
					5e6034fc86 | ||
| 
						 | 
					9da10459d6 | ||
| 
						 | 
					ff80cedd6b | ||
| 
						 | 
					b213148ae8 | ||
| 
						 | 
					c8646e20cb | ||
| 
						 | 
					76a41fec50 | ||
| 
						 | 
					0e705bd038 | ||
| 
						 | 
					f33ffb98ff | ||
| 
						 | 
					faa0d59340 | ||
| 
						 | 
					5af0219884 | ||
| 
						 | 
					dafd99f155 | ||
| 
						 | 
					3560f0388c | ||
| 
						 | 
					b2954658d8 | ||
| 
						 | 
					44581d9983 | ||
| 
						 | 
					02dcfeb227 | ||
| 
						 | 
					d8bafb349d | ||
| 
						 | 
					889598a4c8 | ||
| 
						 | 
					7e37d10016 | ||
| 
						 | 
					ebaebb09d1 | ||
| 
						 | 
					531a3a4b6c | ||
| 
						 | 
					b3e313821b | ||
| 
						 | 
					51958af422 | ||
| 
						 | 
					e3b21ccdba | ||
| 
						 | 
					31bb208835 | ||
| 
						 | 
					8c97e805a2 | ||
| 
						 | 
					ac8a43bb37 | ||
| 
						 | 
					2df4b40a28 | ||
| 
						 | 
					e06736c254 | ||
| 
						 | 
					ec367e94ce | ||
| 
						 | 
					1515dea9fa | ||
| 
						 | 
					adedf9c17d | ||
| 
						 | 
					0b52fb84f1 | ||
| 
						 | 
					16e742ae73 | ||
| 
						 | 
					1b4471dfae | ||
| 
						 | 
					ae152ce0a4 | ||
| 
						 | 
					2aa023f140 | ||
| 
						 | 
					6e2e4c6f08 | ||
| 
						 | 
					1c8c038735 | ||
| 
						 | 
					4d339a6da8 | ||
| 
						 | 
					b7edd4407a | ||
| 
						 | 
					a679a1e94a | ||
| 
						 | 
					180451d32f | ||
| 
						 | 
					7396f22bca | ||
| 
						 | 
					058019aa84 | ||
| 
						 | 
					695f83d1d8 | ||
| 
						 | 
					ac4dfb3baf | ||
| 
						 | 
					427001b223 | ||
| 
						 | 
					3117d8b30d | ||
| 
						 | 
					d19dd2a8b2 | ||
| 
						 | 
					de3dcc3fc2 | ||
| 
						 | 
					077f3e095b | ||
| 
						 | 
					ad3b0bb320 | ||
| 
						 | 
					8538741341 | ||
| 
						 | 
					a0aef5d579 | ||
| 
						 | 
					fdd93427aa | ||
| 
						 | 
					ac3f6557de | ||
| 
						 | 
					b0a909150c | ||
| 
						 | 
					913f163fe4 | ||
| 
						 | 
					3126b07b33 | ||
| 
						 | 
					08ca90cf75 | ||
| 
						 | 
					540ac2a277 | ||
| 
						 | 
					ed80bed066 | ||
| 
						 | 
					41d2541c6a | ||
| 
						 | 
					5dedf63498 | ||
| 
						 | 
					09bc4f41d2 | ||
| 
						 | 
					cebf0b5c57 | ||
| 
						 | 
					1632a57e3e | ||
| 
						 | 
					744c4be7d1 | ||
| 
						 | 
					bd99ef3eff | ||
| 
						 | 
					8a86f13a5d | ||
| 
						 | 
					7418b2f0ee | ||
| 
						 | 
					a0e9de9312 | ||
| 
						 | 
					7e23a6f5e8 | ||
| 
						 | 
					44589f8744 | ||
| 
						 | 
					d24531030f | ||
| 
						 | 
					25bdab1346 | ||
| 
						 | 
					41af1c863a | ||
| 
						 | 
					76b3b18cfb | ||
| 
						 | 
					e6fb2958a9 | ||
| 
						 | 
					15b75b322f | ||
| 
						 | 
					86149d1032 | ||
| 
						 | 
					ded142cd9e | ||
| 
						 | 
					7923eb9ec9 | ||
| 
						 | 
					132553c108 | ||
| 
						 | 
					c2269fc9a4 | ||
| 
						 | 
					aed30d1499 | ||
| 
						 | 
					84a1a876e1 | ||
| 
						 | 
					dc675707f9 | ||
| 
						 | 
					d5667c7ef6 | ||
| 
						 | 
					cba1213dd1 | ||
| 
						 | 
					7219c90957 | ||
| 
						 | 
					af13bd991e | ||
| 
						 | 
					48e548eb52 | ||
| 
						 | 
					1a19e27f0e | ||
| 
						 | 
					0cbd22426d | ||
| 
						 | 
					d5e52e99e0 | ||
| 
						 | 
					f52978e71f | ||
| 
						 | 
					3a3358124d | ||
| 
						 | 
					929808c633 | ||
| 
						 | 
					a78df574f3 | ||
| 
						 | 
					875cad16b6 | ||
| 
						 | 
					7bc30192ca | ||
| 
						 | 
					a1a8968e98 | ||
| 
						 | 
					6abb74a038 | ||
| 
						 | 
					2d7d05e985 | ||
| 
						 | 
					d426e09474 | ||
| 
						 | 
					72d55cb953 | ||
| 
						 | 
					73ad865581 | ||
| 
						 | 
					fefb52beb7 | ||
| 
						 | 
					abd503543b | ||
| 
						 | 
					e3eb550581 | ||
| 
						 | 
					46b780758e | ||
| 
						 | 
					b2c3ee9779 | ||
| 
						 | 
					dca899bcee | ||
| 
						 | 
					9667b8a948 | ||
| 
						 | 
					661f225fe7 | ||
| 
						 | 
					4c6fe0c8de | ||
| 
						 | 
					78f457950e | ||
| 
						 | 
					d831cc8df2 | ||
| 
						 | 
					7056406afc | ||
| 
						 | 
					c85cfcf3e6 | ||
| 
						 | 
					db06d06789 | ||
| 
						 | 
					a28b990cd1 | ||
| 
						 | 
					dab4bfa7a6 | ||
| 
						 | 
					6575236f2b | ||
| 
						 | 
					ad582c8806 | ||
| 
						 | 
					452e9cb953 | ||
| 
						 | 
					a64f137b39 | ||
| 
						 | 
					c067d6aab0 | ||
| 
						 | 
					119b9920a6 | ||
| 
						 | 
					99ed54fce8 | ||
| 
						 | 
					2ea57cdd38 | ||
| 
						 | 
					bb94bdfdaf | ||
| 
						 | 
					4de8398cc2 | ||
| 
						 | 
					e6e8cd5d8a | ||
| 
						 | 
					0b200309ba | ||
| 
						 | 
					a184548912 | ||
| 
						 | 
					c987191212 | ||
| 
						 | 
					7009b444d9 | ||
| 
						 | 
					06551d5367 | ||
| 
						 | 
					a20622ac0c | ||
| 
						 | 
					ca38117fca | ||
| 
						 | 
					9478f78d4f | ||
| 
						 | 
					5c2397bbae | ||
| 
						 | 
					92fefef816 | ||
| 
						 | 
					d3ced65524 | ||
| 
						 | 
					29eb748831 | ||
| 
						 | 
					76df3d5f33 | ||
| 
						 | 
					252076ec1f | ||
| 
						 | 
					bbec28591f | ||
| 
						 | 
					075a360ba6 | ||
| 
						 | 
					477524a8ae | ||
| 
						 | 
					dfe055732d | ||
| 
						 | 
					78b611a18d | ||
| 
						 | 
					367bdf65e6 | ||
| 
						 | 
					3fc9caa31a | ||
| 
						 | 
					95a41fcab7 | ||
| 
						 | 
					58b409fc00 | ||
| 
						 | 
					3eaaac09ad | ||
| 
						 | 
					bcb672920c | ||
| 
						 | 
					79b91e25c2 | ||
| 
						 | 
					7170931464 | ||
| 
						 | 
					c1b5a1a13e | ||
| 
						 | 
					a6265ce8ab | ||
| 
						 | 
					90109917df | ||
| 
						 | 
					0acd54c2b7 | ||
| 
						 | 
					c96226b9b4 | ||
| 
						 | 
					6d143f1624 | ||
| 
						 | 
					93324d1154 | ||
| 
						 | 
					a39f0e1891 | ||
| 
						 | 
					822f609a22 | ||
| 
						 | 
					cd7ddd1c61 | ||
| 
						 | 
					0b63ba26bb | ||
| 
						 | 
					94d70cdb62 | ||
| 
						 | 
					acb3831c8b | ||
| 
						 | 
					c9d9ecede4 | ||
| 
						 | 
					4eb5873353 | ||
| 
						 | 
					7ca39fdb21 | ||
| 
						 | 
					b8d1d7a8c0 | ||
| 
						 | 
					1af79eab30 | ||
| 
						 | 
					03be2704ce | ||
| 
						 | 
					34baea66a7 | ||
| 
						 | 
					0638d109d0 | ||
| 
						 | 
					561e228a2d | ||
| 
						 | 
					cb5d856769 | ||
| 
						 | 
					04fe5d1fc4 | ||
| 
						 | 
					45e9d4f8de | ||
| 
						 | 
					73fdbb6202 | ||
| 
						 | 
					e49dbefddd | ||
| 
						 | 
					fc5143337a | ||
| 
						 | 
					4b3eb6dace | ||
| 
						 | 
					c741b2a819 | ||
| 
						 | 
					cebfaa32bf | ||
| 
						 | 
					d356d39d43 | ||
| 
						 | 
					7d9f22d3f4 | ||
| 
						 | 
					c6c8f282e2 | ||
| 
						 | 
					6a64420721 | ||
| 
						 | 
					fcde4e2488 | ||
| 
						 | 
					aa5c4c20e9 | ||
| 
						 | 
					794e31e487 | ||
| 
						 | 
					16bf186312 | ||
| 
						 | 
					45c722e786 | ||
| 
						 | 
					36d9e5c3fe | ||
| 
						 | 
					8d614de67f | ||
| 
						 | 
					d17da670ab | ||
| 
						 | 
					5bf4df9ad8 | ||
| 
						 | 
					07db6b59ce | ||
| 
						 | 
					e16f1cf4ee | ||
| 
						 | 
					4c80d929ca | ||
| 
						 | 
					16364d9859 | ||
| 
						 | 
					c24f6acb2c | ||
| 
						 | 
					4bd19e0627 | ||
| 
						 | 
					69d839997a | ||
| 
						 | 
					c02c027f4f | ||
| 
						 | 
					b14606625e | ||
| 
						 | 
					222d7b56c7 | ||
| 
						 | 
					b7f7bf42b2 | ||
| 
						 | 
					0cbd64d31a | ||
| 
						 | 
					60bdae47c4 | ||
| 
						 | 
					ca4b38d905 | ||
| 
						 | 
					3674465f53 | ||
| 
						 | 
					b951d4130c | ||
| 
						 | 
					7992b810fd | ||
| 
						 | 
					c1c0afa40b | ||
| 
						 | 
					56c9026299 | ||
| 
						 | 
					021ddfc36b | ||
| 
						 | 
					5bd72f6428 | ||
| 
						 | 
					feabfe54f0 | ||
| 
						 | 
					565409b486 | ||
| 
						 | 
					f57366da5f | ||
| 
						 | 
					064217ccb0 | ||
| 
						 | 
					fa3ccbda33 | ||
| 
						 | 
					f43aadf02d | ||
| 
						 | 
					3b8a4d3e9b | ||
| 
						 | 
					3d410556ef | ||
| 
						 | 
					f15ca1d0a1 | ||
| 
						 | 
					7002463c54 | ||
| 
						 | 
					649f876437 | ||
| 
						 | 
					3cfd178cbd | ||
| 
						 | 
					cefbaafa19 | ||
| 
						 | 
					a8c88800c4 | ||
| 
						 | 
					9d1a127200 | ||
| 
						 | 
					3fdde2d1c8 | ||
| 
						 | 
					cdc0b8dd2c | ||
| 
						 | 
					1a1e06e6e8 | ||
| 
						 | 
					6d39b8468c | ||
| 
						 | 
					bdee3947b2 | ||
| 
						 | 
					2317037655 | ||
| 
						 | 
					dcea6b757b | ||
| 
						 | 
					bd7fe92818 | ||
| 
						 | 
					850e47d8db | ||
| 
						 | 
					96fe62400f | ||
| 
						 | 
					5d07fcdcb6 | ||
| 
						 | 
					fd5d2d57a8 | ||
| 
						 | 
					8e7d42201f | ||
| 
						 | 
					f26bd3cb31 | ||
| 
						 | 
					36915cdace | ||
| 
						 | 
					8a5cecd2a0 | ||
| 
						 | 
					78da1b22bb | ||
| 
						 | 
					6d970a9794 | ||
| 
						 | 
					8bb7739f05 | ||
| 
						 | 
					7788bb4b33 | ||
| 
						 | 
					2ecb4bb3b7 | ||
| 
						 | 
					4a783d3c3c | ||
| 
						 | 
					e16645ae87 | ||
| 
						 | 
					9d3189be7e | ||
| 
						 | 
					07fca78293 | ||
| 
						 | 
					82080501c7 | ||
| 
						 | 
					d93d6bfc66 | ||
| 
						 | 
					a41326ef94 | ||
| 
						 | 
					90b77845c3 | ||
| 
						 | 
					57af80d820 | ||
| 
						 | 
					fc4d5a1dfd | ||
| 
						 | 
					8ab9ab8d21 | ||
| 
						 | 
					75674b5793 | ||
| 
						 | 
					a7d6f26051 | ||
| 
						 | 
					eb540ce148 | ||
| 
						 | 
					a3077fe43b | ||
| 
						 | 
					63bb84d375 | ||
| 
						 | 
					e5f5aa628e | ||
| 
						 | 
					c54f84dc8e | ||
| 
						 | 
					c2e562623c | ||
| 
						 | 
					c8d5e8a9dc | ||
| 
						 | 
					963f017be3 | ||
| 
						 | 
					0e0eeb736f | ||
| 
						 | 
					e8d9b8fa49 | ||
| 
						 | 
					c166b9242e | ||
| 
						 | 
					8ff8efced2 | ||
| 
						 | 
					0b4fb9a806 | ||
| 
						 | 
					ba9fef9410 | ||
| 
						 | 
					f7d94d17cd | ||
| 
						 | 
					1fea9c6817 | ||
| 
						 | 
					a88c8bedbe | ||
| 
						 | 
					fbf3468053 | ||
| 
						 | 
					2a3ba9799e | ||
| 
						 | 
					d121aad28f | ||
| 
						 | 
					dc808fa807 | ||
| 
						 | 
					a1be6ff62b | ||
| 
						 | 
					20dc5b0256 | ||
| 
						 | 
					edd54e23c5 | ||
| 
						 | 
					1238df8784 | ||
| 
						 | 
					b8c62652b0 | ||
| 
						 | 
					54b2d02f63 | ||
| 
						 | 
					47faf89a5c | ||
| 
						 | 
					b7fb5a3854 | ||
| 
						 | 
					a384b4202a | ||
| 
						 | 
					99ecac0ce4 | ||
| 
						 | 
					6102982456 | ||
| 
						 | 
					b88e981b4b | ||
| 
						 | 
					827263b03e | ||
| 
						 | 
					d44e74d334 | ||
| 
						 | 
					911f46c590 | ||
| 
						 | 
					7d42c4ee5d | ||
| 
						 | 
					ea89f6177f | ||
| 
						 | 
					74291b3870 | ||
| 
						 | 
					2c4f2082fe | ||
| 
						 | 
					d8d58cc29b | ||
| 
						 | 
					85b17e4035 | ||
| 
						 | 
					83de5667b3 | ||
| 
						 | 
					5fffe873c6 | ||
| 
						 | 
					78c09c82d6 | ||
| 
						 | 
					704abc315d | ||
| 
						 | 
					86b4965458 | ||
| 
						 | 
					d2dc0c2bf0 | ||
| 
						 | 
					9f4894bbb5 | ||
| 
						 | 
					76a8675a34 | ||
| 
						 | 
					6988301da1 | ||
| 
						 | 
					109cd37211 | ||
| 
						 | 
					284ff4d1b0 | ||
| 
						 | 
					bc0ab7af99 | ||
| 
						 | 
					a17bc7258f | ||
| 
						 | 
					87911c2438 | ||
| 
						 | 
					746f1fd300 | ||
| 
						 | 
					9e5faf919f | ||
| 
						 | 
					cc6cbe6605 | ||
| 
						 | 
					dc6d708897 | ||
| 
						 | 
					6189d24b98 | ||
| 
						 | 
					f6e28dc88f | ||
| 
						 | 
					75ea035630 | ||
| 
						 | 
					4cdb14301d | ||
| 
						 | 
					9f95221ba3 | ||
| 
						 | 
					e3a67be412 | ||
| 
						 | 
					5749b642ce | ||
| 
						 | 
					baff7c67f9 | ||
| 
						 | 
					ccc005942f | ||
| 
						 | 
					5b83c33039 | ||
| 
						 | 
					cc32578c5f | ||
| 
						 | 
					80f410835b | ||
| 
						 | 
					b537a3145d | ||
| 
						 | 
					bfa1fcbaf8 | ||
| 
						 | 
					56243907c4 | ||
| 
						 | 
					5928dd72e6 | ||
| 
						 | 
					c6bf0ff1cd | ||
| 
						 | 
					19d1cf192b | ||
| 
						 | 
					37d7dc7e3e | ||
| 
						 | 
					95a3a194b8 | ||
| 
						 | 
					3542387188 | ||
| 
						 | 
					da1b002a64 | ||
| 
						 | 
					46daee28e7 | ||
| 
						 | 
					9ade5635d4 | ||
| 
						 | 
					e14e80f33c | ||
| 
						 | 
					0c824e21c8 | ||
| 
						 | 
					fab1c68569 | ||
| 
						 | 
					c1534657f2 | ||
| 
						 | 
					39841de680 | ||
| 
						 | 
					43a720b62b | ||
| 
						 | 
					5ec54de29e | ||
| 
						 | 
					397e37f344 | ||
| 
						 | 
					b6f84c2b99 | ||
| 
						 | 
					843f86fc66 | ||
| 
						 | 
					0e8e364074 | ||
| 
						 | 
					bbccbef578 | ||
| 
						 | 
					ee11a8e3a0 | ||
| 
						 | 
					e8618047bd | ||
| 
						 | 
					f104b76f73 | ||
| 
						 | 
					cb701d8506 | ||
| 
						 | 
					70a334c56e | ||
| 
						 | 
					e6b2db1e29 | ||
| 
						 | 
					e8dffa0052 | ||
| 
						 | 
					c4f0512f39 | ||
| 
						 | 
					3268019d0c | ||
| 
						 | 
					a0ef6a1fc8 | ||
| 
						 | 
					99d0098b20 | ||
| 
						 | 
					a7a54c042c | ||
| 
						 | 
					c44e48a793 | ||
| 
						 | 
					53b501ca73 | ||
| 
						 | 
					322f70bcca | ||
| 
						 | 
					35559c077b | ||
| 
						 | 
					590ffe7c76 | ||
| 
						 | 
					8a2d8f148e | ||
| 
						 | 
					4f0e15e07d | ||
| 
						 | 
					7463861e0c | ||
| 
						 | 
					1e70fa28be | ||
| 
						 | 
					26c6ca470b | ||
| 
						 | 
					5e54034e0e | ||
| 
						 | 
					25873ef734 | ||
| 
						 | 
					1092b04b22 | ||
| 
						 | 
					01ce74dd72 | ||
| 
						 | 
					41430d8386 | ||
| 
						 | 
					01eb19169c | ||
| 
						 | 
					cfaa7d7c68 | ||
| 
						 | 
					14d3312a10 | ||
| 
						 | 
					87be478dd8 | ||
| 
						 | 
					0b6877a20e | ||
| 
						 | 
					7186f0ef60 | ||
| 
						 | 
					538933691e | ||
| 
						 | 
					46c49ddbd8 | ||
| 
						 | 
					bcfb134b6e | ||
| 
						 | 
					57981f1cf9 | ||
| 
						 | 
					0310186fb7 | ||
| 
						 | 
					4dcb38290e | ||
| 
						 | 
					2f5c37048b | ||
| 
						 | 
					370c8b16ae | ||
| 
						 | 
					af0555592a | ||
| 
						 | 
					9c07ddaed6 | ||
| 
						 | 
					bb7355a566 | ||
| 
						 | 
					1d48347f8c | ||
| 
						 | 
					060b76ca9c | ||
| 
						 | 
					2b2b9b6f7a | ||
| 
						 | 
					f3dd05a0c0 | ||
| 
						 | 
					47a91aa273 | ||
| 
						 | 
					41bc236603 | ||
| 
						 | 
					65349451ea | ||
| 
						 | 
					e77b6a55a4 | ||
| 
						 | 
					2379bcff11 | ||
| 
						 | 
					7133156fa1 | ||
| 
						 | 
					a59176689d | ||
| 
						 | 
					bc2d8f3dfb | ||
| 
						 | 
					ddf89a9d5a | ||
| 
						 | 
					7daaba17f6 | ||
| 
						 | 
					9cb5b1384f | ||
| 
						 | 
					7d13263482 | ||
| 
						 | 
					d9ff252915 | ||
| 
						 | 
					51ba550251 | ||
| 
						 | 
					fd21c467ad | ||
| 
						 | 
					9aa90650b4 | ||
| 
						 | 
					d892257e8b | ||
| 
						 | 
					db0dbcfcf1 | ||
| 
						 | 
					f591996f04 | ||
| 
						 | 
					b08d385586 | ||
| 
						 | 
					20ef22f67e | ||
| 
						 | 
					c888baf542 | ||
| 
						 | 
					8b0af3f666 | ||
| 
						 | 
					7043e1e7c0 | ||
| 
						 | 
					c5854eba23 | ||
| 
						 | 
					ddf1a8cebb | ||
| 
						 | 
					7dcaf167e9 | ||
| 
						 | 
					b359d51d3a | ||
| 
						 | 
					3913fa5086 | ||
| 
						 | 
					ab2772abe0 | ||
| 
						 | 
					bc7875b17b | ||
| 
						 | 
					4938fa9990 | ||
| 
						 | 
					84df2c80ee | ||
| 
						 | 
					dc17060754 | ||
| 
						 | 
					e2fa81dddc | ||
| 
						 | 
					182dfc95fe | ||
| 
						 | 
					c8979b6c33 | ||
| 
						 | 
					ab872e8912 | ||
| 
						 | 
					d36b94fabf | ||
| 
						 | 
					e3d4ceaecb | ||
| 
						 | 
					e3a6e5b788 | ||
| 
						 | 
					57235c0e00 | ||
| 
						 | 
					2298c3ddaf | ||
| 
						 | 
					7224f1be6f | ||
| 
						 | 
					1bd3019c16 | ||
| 
						 | 
					f0fa21dead | ||
| 
						 | 
					845eaed8d7 | ||
| 
						 | 
					b3649cd4d0 | ||
| 
						 | 
					55f14c587b | ||
| 
						 | 
					441a8a8408 | ||
| 
						 | 
					060c9648f1 | ||
| 
						 | 
					7680c8733f | ||
| 
						 | 
					5a0af5c93b | ||
| 
						 | 
					f4b066add1 | ||
| 
						 | 
					9ecb414b02 | ||
| 
						 | 
					ad4f908c24 | ||
| 
						 | 
					025f739442 | ||
| 
						 | 
					6df7354c48 | ||
| 
						 | 
					3f77c845ca | ||
| 
						 | 
					d4771f7a5c | ||
| 
						 | 
					ec4e2bfa4f | ||
| 
						 | 
					dfdbfae4b5 | ||
| 
						 | 
					349d38b956 | ||
| 
						 | 
					2267aa3ac4 | ||
| 
						 | 
					2323aa454e | ||
| 
						 | 
					8b3317b665 | ||
| 
						 | 
					15f893c343 | ||
| 
						 | 
					309b3e765e | ||
| 
						 | 
					d3fad06e00 | ||
| 
						 | 
					834f24c99c | ||
| 
						 | 
					35291e1298 | ||
| 
						 | 
					ac4e9dcbc5 | ||
| 
						 | 
					d57806f2ba | ||
| 
						 | 
					3b005c317d | ||
| 
						 | 
					e91903fed2 | ||
| 
						 | 
					fee2002b0f | ||
| 
						 | 
					f12e502eb8 | ||
| 
						 | 
					24e62b1cee | ||
| 
						 | 
					f559ec73e0 | ||
| 
						 | 
					530b501fcf | ||
| 
						 | 
					d5ea78025e | ||
| 
						 | 
					3413b9b5b5 | ||
| 
						 | 
					0b45c1aa76 | ||
| 
						 | 
					5718d1690a | ||
| 
						 | 
					67b16cc070 | ||
| 
						 | 
					5746ac3247 | ||
| 
						 | 
					8a2c520b11 | ||
| 
						 | 
					f46c14df8c | ||
| 
						 | 
					009fbba491 | ||
| 
						 | 
					53d84347c2 | ||
| 
						 | 
					1961487055 | ||
| 
						 | 
					c9ce5df74b | ||
| 
						 | 
					1371b6773e | ||
| 
						 | 
					b9f1baf150 | ||
| 
						 | 
					66b322e844 | ||
| 
						 | 
					487b65b669 | ||
| 
						 | 
					9078781d61 | ||
| 
						 | 
					1ec830521a | ||
| 
						 | 
					c4bf2aae7d | ||
| 
						 | 
					69ca88d9f8 | ||
| 
						 | 
					b38b7b2534 | ||
| 
						 | 
					f19bfc3b4b | ||
| 
						 | 
					d22f9c09d7 | ||
| 
						 | 
					fc2da9eb42 | ||
| 
						 | 
					f2c9e20aef | ||
| 
						 | 
					16b8ca2746 | ||
| 
						 | 
					46ea074821 | ||
| 
						 | 
					d2c89781e2 | ||
| 
						 | 
					e54d711891 | ||
| 
						 | 
					84d3ad4764 | ||
| 
						 | 
					b908951a2d | ||
| 
						 | 
					8b87deea58 | ||
| 
						 | 
					0d7325b3dc | ||
| 
						 | 
					a3fd99a498 | ||
| 
						 | 
					0ff405d1e0 | ||
| 
						 | 
					46a60af966 | ||
| 
						 | 
					591c9e3b39 | ||
| 
						 | 
					c30461b20b | ||
| 
						 | 
					2c3f86d9bc | ||
| 
						 | 
					34349e4475 | ||
| 
						 | 
					6acd5be5dc | ||
| 
						 | 
					55a2b4e789 | ||
| 
						 | 
					f41397eb43 | ||
| 
						 | 
					41fc1e8f82 | ||
| 
						 | 
					bee219ebf7 | ||
| 
						 | 
					438f602961 | ||
| 
						 | 
					429e72e681 | ||
| 
						 | 
					7a134781f2 | ||
| 
						 | 
					b572c1dcd3 | ||
| 
						 | 
					95593f847b | ||
| 
						 | 
					b82fcbd97b | ||
| 
						 | 
					daddee7806 | ||
| 
						 | 
					930a08ec90 | ||
| 
						 | 
					fd2edf3b23 | ||
| 
						 | 
					0597255c08 | ||
| 
						 | 
					955ab38a85 | ||
| 
						 | 
					1311a0db8b | ||
| 
						 | 
					0ce9ee6a6c | ||
| 
						 | 
					3a339382d4 | ||
| 
						 | 
					a5b15bbc16 | ||
| 
						 | 
					fbf89fd514 | ||
| 
						 | 
					b3223feba2 | ||
| 
						 | 
					88a9bc379e | ||
| 
						 | 
					b442b91b7c | ||
| 
						 | 
					9fadbbe087 | ||
| 
						 | 
					1ef7239276 | ||
| 
						 | 
					ea573e9434 | ||
| 
						 | 
					34fa24e4a8 | ||
| 
						 | 
					a1be4a4d8a | ||
| 
						 | 
					b8e8af1e2a | ||
| 
						 | 
					c13a3fb30c | ||
| 
						 | 
					cb8fa4e1f4 | ||
| 
						 | 
					bf7f4f9887 | ||
| 
						 | 
					af48548e81 | ||
| 
						 | 
					90d58ec8fa | ||
| 
						 | 
					e92dd7f464 | ||
| 
						 | 
					3bdf9eeed2 | ||
| 
						 | 
					558ac7b0da | ||
| 
						 | 
					9d0488ffbc | ||
| 
						 | 
					d7fa8b283e | ||
| 
						 | 
					a0097bd613 | ||
| 
						 | 
					ffc2156e5f | ||
| 
						 | 
					e0a89bb5fe | ||
| 
						 | 
					647179cd3c | ||
| 
						 | 
					5106ccdbd7 | ||
| 
						 | 
					7103098fe7 | ||
| 
						 | 
					f8072f0bfc | ||
| 
						 | 
					96ac3a95c8 | ||
| 
						 | 
					cd713dc40f | ||
| 
						 | 
					d9fba39d80 | ||
| 
						 | 
					2564470197 | ||
| 
						 | 
					9222c82af0 | ||
| 
						 | 
					243f283bfd | ||
| 
						 | 
					5b60aaecc0 | ||
| 
						 | 
					20a4caec60 | ||
| 
						 | 
					99cc096b71 | ||
| 
						 | 
					5626d1c56d | ||
| 
						 | 
					68c9c4ec3c | ||
| 
						 | 
					f9d4a43e05 | ||
| 
						 | 
					92e7f344e0 | ||
| 
						 | 
					89ce2838d5 | ||
| 
						 | 
					356b217692 | ||
| 
						 | 
					950e39b753 | ||
| 
						 | 
					8f14979717 | ||
| 
						 | 
					aa2afd162e | ||
| 
						 | 
					fe33352ec1 | ||
| 
						 | 
					65c5249815 | ||
| 
						 | 
					b1afaea1aa | ||
| 
						 | 
					997dc3814b | ||
| 
						 | 
					b37b5b86d4 | ||
| 
						 | 
					b13a4e1016 | ||
| 
						 | 
					7897ebc4d5 | ||
| 
						 | 
					ac17b82d85 | ||
| 
						 | 
					1b1712d998 | ||
| 
						 | 
					a2c0d9f7d0 | ||
| 
						 | 
					5b68b25c85 | ||
| 
						 | 
					d3a215b575 | ||
| 
						 | 
					5c352a0d3e | ||
| 
						 | 
					fded058ea6 | ||
| 
						 | 
					99f041b114 | ||
| 
						 | 
					283b594995 | ||
| 
						 | 
					723aa65e7a | ||
| 
						 | 
					64d315ad51 | ||
| 
						 | 
					d0844356cb | ||
| 
						 | 
					ba8d65835a | ||
| 
						 | 
					fa3343f437 | ||
| 
						 | 
					c5b8a951d2 | ||
| 
						 | 
					20b1fc05cb | 
@@ -22,6 +22,9 @@
 | 
			
		||||
 | 
			
		||||
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
 | 
			
		||||
 | 
			
		||||
echo "Running PHP CS Fixer"
 | 
			
		||||
$SCRIPT_DIR/phpcs.sh
 | 
			
		||||
echo "Running PHPStan"
 | 
			
		||||
$SCRIPT_DIR/phpstan.sh
 | 
			
		||||
echo "Running PHPMD"
 | 
			
		||||
$SCRIPT_DIR/phpmd.sh
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,8 @@
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;
 | 
			
		||||
 | 
			
		||||
$current = __DIR__;
 | 
			
		||||
 | 
			
		||||
$paths = [
 | 
			
		||||
@@ -35,36 +37,40 @@ $finder = PhpCsFixer\Finder::create()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
$config = new PhpCsFixer\Config();
 | 
			
		||||
return $config->setRules([
 | 
			
		||||
                             'no_unused_imports'             => true,
 | 
			
		||||
                             '@PhpCsFixer'                   => true,
 | 
			
		||||
                             '@PHP83Migration'               => true,
 | 
			
		||||
                             '@PhpCsFixer:risky'             => true,
 | 
			
		||||
                             '@PSR12:risky'                  => true,
 | 
			
		||||
                             'declare_strict_types'          => true,
 | 
			
		||||
                             'strict_param'                  => true,
 | 
			
		||||
                             'comment_to_phpdoc'             => false, // breaks phpstan lines in combination with PHPStorm.
 | 
			
		||||
                             'array_syntax'                  => ['syntax' => 'short'],
 | 
			
		||||
                             'native_function_invocation'    => false, // annoying
 | 
			
		||||
                             'php_unit_data_provider_name'   => false, // bloody annoying long test names
 | 
			
		||||
                             'static_lambda'                 => false, // breaks the Response macro for API's.
 | 
			
		||||
                             'phpdoc_summary'                => false, // annoying.
 | 
			
		||||
                             'single_space_around_construct' => [
 | 
			
		||||
                                 'constructs_followed_by_a_single_space' => [
 | 
			
		||||
                                     'protected',
 | 
			
		||||
                                 ],
 | 
			
		||||
                             ],
 | 
			
		||||
                             'statement_indentation'         => true,
 | 
			
		||||
                             'type_declaration_spaces'       => false,
 | 
			
		||||
                             'cast_spaces'                   => false,
 | 
			
		||||
                             'binary_operator_spaces'        => [
 | 
			
		||||
                                 'default' => 'at_least_single_space',
 | 
			
		||||
                                 'operators' => [
 | 
			
		||||
                                     '=>' => 'align_single_space_by_scope',
 | 
			
		||||
                                     '='  => 'align_single_space_minimal_by_scope',
 | 
			
		||||
                                     '??='  => 'align_single_space_minimal_by_scope',
 | 
			
		||||
                                 ],
 | 
			
		||||
                             ],
 | 
			
		||||
                             'void_return'                   => true,
 | 
			
		||||
                         ])
 | 
			
		||||
$config->setParallelConfig(ParallelConfigFactory::detect());
 | 
			
		||||
return $config->setRules(
 | 
			
		||||
    [
 | 
			
		||||
        // rule sets
 | 
			
		||||
        '@PHP83Migration'               => true,
 | 
			
		||||
        '@PhpCsFixer'                   => true,
 | 
			
		||||
        '@PhpCsFixer:risky'             => true,
 | 
			
		||||
        '@PSR12'                        => true,
 | 
			
		||||
        '@PSR12:risky'                  => true,
 | 
			
		||||
        'declare_strict_types'          => true,
 | 
			
		||||
        'strict_param'                  => true,
 | 
			
		||||
        'no_unused_imports'             => true,
 | 
			
		||||
        'single_space_around_construct' => true,
 | 
			
		||||
        'statement_indentation'         => true,
 | 
			
		||||
        'void_return'                   => true,
 | 
			
		||||
 | 
			
		||||
        // disabled rules
 | 
			
		||||
        'native_function_invocation'    => false, // annoying
 | 
			
		||||
        'php_unit_data_provider_name'   => false, // bloody annoying long test names
 | 
			
		||||
        'static_lambda'                 => false, // breaks the Response macro for API's.
 | 
			
		||||
        'phpdoc_summary'                => false, // annoying.
 | 
			
		||||
        'comment_to_phpdoc'             => false, // breaks phpstan lines in combination with PHPStorm.
 | 
			
		||||
        'type_declaration_spaces'       => false,
 | 
			
		||||
        'cast_spaces'                   => false,
 | 
			
		||||
 | 
			
		||||
        // complex rules
 | 
			
		||||
        'array_syntax'                  => ['syntax' => 'short'],
 | 
			
		||||
        'binary_operator_spaces'        => [
 | 
			
		||||
            'default'   => 'at_least_single_space',
 | 
			
		||||
            'operators' => [
 | 
			
		||||
                '=>'  => 'align_single_space_by_scope',
 | 
			
		||||
                '='   => 'align_single_space_minimal_by_scope',
 | 
			
		||||
                '??=' => 'align_single_space_minimal_by_scope',
 | 
			
		||||
            ],
 | 
			
		||||
        ],
 | 
			
		||||
    ])
 | 
			
		||||
              ->setFinder($finder);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "require": {
 | 
			
		||||
    "friendsofphp/php-cs-fixer": "^3.12"
 | 
			
		||||
  }
 | 
			
		||||
    "require": {
 | 
			
		||||
        "friendsofphp/php-cs-fixer": "^3.12"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1089
									
								
								.ci/php-cs-fixer/composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1089
									
								
								.ci/php-cs-fixer/composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										19
									
								
								.ci/phpcs.sh
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								.ci/phpcs.sh
									
									
									
									
									
								
							@@ -20,23 +20,8 @@
 | 
			
		||||
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# Install composer packages
 | 
			
		||||
#composer install --no-scripts --no-ansi
 | 
			
		||||
 | 
			
		||||
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
 | 
			
		||||
 | 
			
		||||
# enable test .env file.
 | 
			
		||||
# cp .ci/.env.ci .env
 | 
			
		||||
 | 
			
		||||
OUTPUT_FORMAT=txt
 | 
			
		||||
EXTRA_PARAMS=""
 | 
			
		||||
 | 
			
		||||
if [[ $GITHUB_ACTIONS = "true" ]]
 | 
			
		||||
then
 | 
			
		||||
    OUTPUT_FORMAT=txt
 | 
			
		||||
    EXTRA_PARAMS=""
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# clean up php code
 | 
			
		||||
cd $SCRIPT_DIR/php-cs-fixer
 | 
			
		||||
composer update --quiet
 | 
			
		||||
@@ -44,8 +29,8 @@ rm -f .php-cs-fixer.cache
 | 
			
		||||
PHP_CS_FIXER_IGNORE_ENV=true
 | 
			
		||||
./vendor/bin/php-cs-fixer fix \
 | 
			
		||||
    --config $SCRIPT_DIR/php-cs-fixer/.php-cs-fixer.php \
 | 
			
		||||
    --format=$OUTPUT_FORMAT \
 | 
			
		||||
    --allow-risky=yes $EXTRA_PARAMS
 | 
			
		||||
    --format=txt \
 | 
			
		||||
    --allow-risky=yes
 | 
			
		||||
 | 
			
		||||
EXIT_CODE=$?
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										94
									
								
								.ci/phpmd/composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										94
									
								
								.ci/phpmd/composer.lock
									
									
									
										generated
									
									
									
								
							@@ -9,16 +9,16 @@
 | 
			
		||||
    "packages-dev": [
 | 
			
		||||
        {
 | 
			
		||||
            "name": "composer/pcre",
 | 
			
		||||
            "version": "3.1.1",
 | 
			
		||||
            "version": "3.1.3",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/composer/pcre.git",
 | 
			
		||||
                "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9"
 | 
			
		||||
                "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9",
 | 
			
		||||
                "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9",
 | 
			
		||||
                "url": "https://api.github.com/repos/composer/pcre/zipball/5b16e25a5355f1f3afdfc2f954a0a80aec4826a8",
 | 
			
		||||
                "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -60,7 +60,7 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/composer/pcre/issues",
 | 
			
		||||
                "source": "https://github.com/composer/pcre/tree/3.1.1"
 | 
			
		||||
                "source": "https://github.com/composer/pcre/tree/3.1.3"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": [
 | 
			
		||||
                {
 | 
			
		||||
@@ -76,20 +76,20 @@
 | 
			
		||||
                    "type": "tidelift"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2023-10-11T07:11:09+00:00"
 | 
			
		||||
            "time": "2024-03-19T10:26:25+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "composer/xdebug-handler",
 | 
			
		||||
            "version": "3.0.3",
 | 
			
		||||
            "version": "3.0.4",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/composer/xdebug-handler.git",
 | 
			
		||||
                "reference": "ced299686f41dce890debac69273b47ffe98a40c"
 | 
			
		||||
                "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c",
 | 
			
		||||
                "reference": "ced299686f41dce890debac69273b47ffe98a40c",
 | 
			
		||||
                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/4f988f8fdf580d53bdb2d1278fe93d1ed5462255",
 | 
			
		||||
                "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -100,7 +100,7 @@
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "phpstan/phpstan": "^1.0",
 | 
			
		||||
                "phpstan/phpstan-strict-rules": "^1.1",
 | 
			
		||||
                "symfony/phpunit-bridge": "^6.0"
 | 
			
		||||
                "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "autoload": {
 | 
			
		||||
@@ -124,9 +124,9 @@
 | 
			
		||||
                "performance"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "irc": "irc://irc.freenode.org/composer",
 | 
			
		||||
                "irc": "ircs://irc.libera.chat:6697/composer",
 | 
			
		||||
                "issues": "https://github.com/composer/xdebug-handler/issues",
 | 
			
		||||
                "source": "https://github.com/composer/xdebug-handler/tree/3.0.3"
 | 
			
		||||
                "source": "https://github.com/composer/xdebug-handler/tree/3.0.4"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": [
 | 
			
		||||
                {
 | 
			
		||||
@@ -142,7 +142,7 @@
 | 
			
		||||
                    "type": "tidelift"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2022-02-25T21:32:43+00:00"
 | 
			
		||||
            "time": "2024-03-26T18:29:49+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "pdepend/pdepend",
 | 
			
		||||
@@ -395,16 +395,16 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "symfony/config",
 | 
			
		||||
            "version": "v7.0.3",
 | 
			
		||||
            "version": "v7.0.4",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/symfony/config.git",
 | 
			
		||||
                "reference": "86a5027869ca3d6bdecae6d5d6c2f77c8f2c1d16"
 | 
			
		||||
                "reference": "44deeba7233f08f383185ffa37dace3b3bc87364"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/symfony/config/zipball/86a5027869ca3d6bdecae6d5d6c2f77c8f2c1d16",
 | 
			
		||||
                "reference": "86a5027869ca3d6bdecae6d5d6c2f77c8f2c1d16",
 | 
			
		||||
                "url": "https://api.github.com/repos/symfony/config/zipball/44deeba7233f08f383185ffa37dace3b3bc87364",
 | 
			
		||||
                "reference": "44deeba7233f08f383185ffa37dace3b3bc87364",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -450,7 +450,7 @@
 | 
			
		||||
            "description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
 | 
			
		||||
            "homepage": "https://symfony.com",
 | 
			
		||||
            "support": {
 | 
			
		||||
                "source": "https://github.com/symfony/config/tree/v7.0.3"
 | 
			
		||||
                "source": "https://github.com/symfony/config/tree/v7.0.4"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": [
 | 
			
		||||
                {
 | 
			
		||||
@@ -466,20 +466,20 @@
 | 
			
		||||
                    "type": "tidelift"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2024-01-30T08:34:29+00:00"
 | 
			
		||||
            "time": "2024-02-26T07:52:39+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "symfony/dependency-injection",
 | 
			
		||||
            "version": "v7.0.3",
 | 
			
		||||
            "version": "v7.0.4",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/symfony/dependency-injection.git",
 | 
			
		||||
                "reference": "e915c6684b8e3ae90a4441f6823ebbb40edf0b92"
 | 
			
		||||
                "reference": "47f37af245df8457ea63409fc242b3cc825ce5eb"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e915c6684b8e3ae90a4441f6823ebbb40edf0b92",
 | 
			
		||||
                "reference": "e915c6684b8e3ae90a4441f6823ebbb40edf0b92",
 | 
			
		||||
                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/47f37af245df8457ea63409fc242b3cc825ce5eb",
 | 
			
		||||
                "reference": "47f37af245df8457ea63409fc242b3cc825ce5eb",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -530,7 +530,7 @@
 | 
			
		||||
            "description": "Allows you to standardize and centralize the way objects are constructed in your application",
 | 
			
		||||
            "homepage": "https://symfony.com",
 | 
			
		||||
            "support": {
 | 
			
		||||
                "source": "https://github.com/symfony/dependency-injection/tree/v7.0.3"
 | 
			
		||||
                "source": "https://github.com/symfony/dependency-injection/tree/v7.0.4"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": [
 | 
			
		||||
                {
 | 
			
		||||
@@ -546,7 +546,7 @@
 | 
			
		||||
                    "type": "tidelift"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2024-01-30T08:34:29+00:00"
 | 
			
		||||
            "time": "2024-02-22T20:27:20+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "symfony/deprecation-contracts",
 | 
			
		||||
@@ -680,16 +680,16 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "symfony/polyfill-ctype",
 | 
			
		||||
            "version": "v1.28.0",
 | 
			
		||||
            "version": "v1.29.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/symfony/polyfill-ctype.git",
 | 
			
		||||
                "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
 | 
			
		||||
                "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
 | 
			
		||||
                "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
 | 
			
		||||
                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
 | 
			
		||||
                "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -703,9 +703,6 @@
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "extra": {
 | 
			
		||||
                "branch-alias": {
 | 
			
		||||
                    "dev-main": "1.28-dev"
 | 
			
		||||
                },
 | 
			
		||||
                "thanks": {
 | 
			
		||||
                    "name": "symfony/polyfill",
 | 
			
		||||
                    "url": "https://github.com/symfony/polyfill"
 | 
			
		||||
@@ -742,7 +739,7 @@
 | 
			
		||||
                "portable"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
 | 
			
		||||
                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": [
 | 
			
		||||
                {
 | 
			
		||||
@@ -758,20 +755,20 @@
 | 
			
		||||
                    "type": "tidelift"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2023-01-26T09:26:14+00:00"
 | 
			
		||||
            "time": "2024-01-29T20:11:03+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "symfony/polyfill-mbstring",
 | 
			
		||||
            "version": "v1.28.0",
 | 
			
		||||
            "version": "v1.29.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/symfony/polyfill-mbstring.git",
 | 
			
		||||
                "reference": "42292d99c55abe617799667f454222c54c60e229"
 | 
			
		||||
                "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
 | 
			
		||||
                "reference": "42292d99c55abe617799667f454222c54c60e229",
 | 
			
		||||
                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
 | 
			
		||||
                "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -785,9 +782,6 @@
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "extra": {
 | 
			
		||||
                "branch-alias": {
 | 
			
		||||
                    "dev-main": "1.28-dev"
 | 
			
		||||
                },
 | 
			
		||||
                "thanks": {
 | 
			
		||||
                    "name": "symfony/polyfill",
 | 
			
		||||
                    "url": "https://github.com/symfony/polyfill"
 | 
			
		||||
@@ -825,7 +819,7 @@
 | 
			
		||||
                "shim"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
 | 
			
		||||
                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": [
 | 
			
		||||
                {
 | 
			
		||||
@@ -841,7 +835,7 @@
 | 
			
		||||
                    "type": "tidelift"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2023-07-28T09:04:16+00:00"
 | 
			
		||||
            "time": "2024-01-29T20:11:03+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "symfony/service-contracts",
 | 
			
		||||
@@ -927,16 +921,16 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "symfony/var-exporter",
 | 
			
		||||
            "version": "v7.0.3",
 | 
			
		||||
            "version": "v7.0.4",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/symfony/var-exporter.git",
 | 
			
		||||
                "reference": "1fb79308cb5fc2b44bff6e8af10a5af6812e05b8"
 | 
			
		||||
                "reference": "dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1fb79308cb5fc2b44bff6e8af10a5af6812e05b8",
 | 
			
		||||
                "reference": "1fb79308cb5fc2b44bff6e8af10a5af6812e05b8",
 | 
			
		||||
                "url": "https://api.github.com/repos/symfony/var-exporter/zipball/dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41",
 | 
			
		||||
                "reference": "dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -981,7 +975,7 @@
 | 
			
		||||
                "serialize"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "source": "https://github.com/symfony/var-exporter/tree/v7.0.3"
 | 
			
		||||
                "source": "https://github.com/symfony/var-exporter/tree/v7.0.4"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": [
 | 
			
		||||
                {
 | 
			
		||||
@@ -997,7 +991,7 @@
 | 
			
		||||
                    "type": "tidelift"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2024-01-23T15:02:46+00:00"
 | 
			
		||||
            "time": "2024-02-26T10:35:24+00:00"
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "aliases": [],
 | 
			
		||||
 
 | 
			
		||||
@@ -19,9 +19,9 @@
 | 
			
		||||
  ~ along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
  -->
 | 
			
		||||
 | 
			
		||||
<ruleset name="pcsg-generated-ruleset"
 | 
			
		||||
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         name="pcsg-generated-ruleset"
 | 
			
		||||
         xmlns="http://pmd.sf.net/ruleset/1.0.0"
 | 
			
		||||
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
 | 
			
		||||
         xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
 | 
			
		||||
    <description>Firefly III ruleset.</description>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
parameters:
 | 
			
		||||
  scanFiles:
 | 
			
		||||
    - ../_ide_helper_models.php
 | 
			
		||||
  universalObjectCratesClasses:
 | 
			
		||||
    - Illuminate\Database\Eloquent\Model
 | 
			
		||||
  # TODO: slowly remove these parameters and fix the issues found.
 | 
			
		||||
  reportUnmatchedIgnoredErrors: false
 | 
			
		||||
  checkGenericClassInNonGenericObjectType: false  # remove this rule when all other issues are solved.
 | 
			
		||||
  ignoreErrors:
 | 
			
		||||
  # TODO: slowly remove these exceptions and fix the issues found.
 | 
			
		||||
    - '#Dynamic call to static method#' # all the Laravel ORM things depend on this.
 | 
			
		||||
@@ -11,6 +12,7 @@ parameters:
 | 
			
		||||
    - '#with no value type specified in iterable type array#' # remove this rule when all other issues are solved.
 | 
			
		||||
    - '#has no value type specified in iterable type array#' # remove this rule when all other issues are solved.
 | 
			
		||||
    - '#is not allowed to extend#'
 | 
			
		||||
    - '#does not specify its types#'
 | 
			
		||||
    - '#switch is forbidden to use#'
 | 
			
		||||
    - '#is neither abstract nor final#'
 | 
			
		||||
    - '#on left side of \?\?\= always exists and is not nullable#'
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										52
									
								
								.env.example
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								.env.example
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
# 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
 | 
			
		||||
APP_ENV=production
 | 
			
		||||
 | 
			
		||||
# Set to true if you want to see debug information in error screens.
 | 
			
		||||
APP_DEBUG=false
 | 
			
		||||
@@ -111,7 +111,10 @@ PGSQL_SSL_CERT=null
 | 
			
		||||
PGSQL_SSL_KEY=null
 | 
			
		||||
PGSQL_SSL_CRL_FILE=null
 | 
			
		||||
 | 
			
		||||
# more PostgreSQL settings
 | 
			
		||||
# For postgresql 15 and up, setting this to public will no longer work as expected, becasuse the
 | 
			
		||||
# 'public' schema is without grants. This can be worked around by having a super user grant those
 | 
			
		||||
# necessary privileges, but in security conscious setups that's not viable.
 | 
			
		||||
# You will need to set this to the schema you want to use.
 | 
			
		||||
PGSQL_SCHEMA=public
 | 
			
		||||
 | 
			
		||||
# If you're looking for performance improvements, you could install memcached or redis
 | 
			
		||||
@@ -173,6 +176,7 @@ MAILGUN_ENDPOINT=api.mailgun.net
 | 
			
		||||
# If you use Docker or similar, you can set these variables from a file by appending them with _FILE
 | 
			
		||||
MANDRILL_SECRET=
 | 
			
		||||
SPARKPOST_SECRET=
 | 
			
		||||
MAILERSEND_API_KEY=
 | 
			
		||||
 | 
			
		||||
# Firefly III can send you the following messages.
 | 
			
		||||
SEND_ERROR_MESSAGE=true
 | 
			
		||||
@@ -184,6 +188,11 @@ SEND_REPORT_JOURNALS=true
 | 
			
		||||
# Since this involves an external service, it's optional and disabled by default.
 | 
			
		||||
ENABLE_EXTERNAL_MAP=false
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Enable or disable exchange rate conversion. This function isn't used yet by Firefly III
 | 
			
		||||
#
 | 
			
		||||
ENABLE_EXCHANGE_RATES=false
 | 
			
		||||
 | 
			
		||||
# Set this value to true if you want Firefly III to download currency exchange rates
 | 
			
		||||
# from the internet. These rates are hosted by the creator of Firefly III inside
 | 
			
		||||
# an Azure Storage Container.
 | 
			
		||||
@@ -291,27 +300,6 @@ DKR_BUILD_LOCALE=false
 | 
			
		||||
# Won't significantly speed up things.
 | 
			
		||||
DKR_CHECK_SQLITE=true
 | 
			
		||||
 | 
			
		||||
# Run database creation and migration commands. Disable this only if you're 100% sure the DB exists
 | 
			
		||||
# and is up to date.
 | 
			
		||||
DKR_RUN_MIGRATION=true
 | 
			
		||||
 | 
			
		||||
# Run database upgrade commands. Disable this only when you're 100% sure your DB is up-to-date
 | 
			
		||||
# with the latest fixes (outside of migrations!)
 | 
			
		||||
DKR_RUN_UPGRADE=true
 | 
			
		||||
 | 
			
		||||
# Verify database integrity. Includes all data checks and verifications.
 | 
			
		||||
# Disabling this makes Firefly III assume your DB is intact.
 | 
			
		||||
DKR_RUN_VERIFY=true
 | 
			
		||||
 | 
			
		||||
# Run database reporting commands. When disabled, Firefly III won't go over your data to report current state.
 | 
			
		||||
# Disabling this should have no impact on data integrity or safety but it won't warn you of possible issues.
 | 
			
		||||
DKR_RUN_REPORT=true
 | 
			
		||||
 | 
			
		||||
# Generate OAuth2 keys.
 | 
			
		||||
# When disabled, Firefly III won't attempt to generate OAuth2 Passport keys. This won't be an issue, IFF (if and only if)
 | 
			
		||||
# you had previously generated keys already and they're stored in your database for restoration.
 | 
			
		||||
DKR_RUN_PASSPORT_INSTALL=true
 | 
			
		||||
 | 
			
		||||
# Leave the following configuration vars as is.
 | 
			
		||||
# Unless you like to tinker and know what you're doing.
 | 
			
		||||
APP_NAME=FireflyIII
 | 
			
		||||
@@ -325,6 +313,12 @@ PUSHER_ID=
 | 
			
		||||
DEMO_USERNAME=
 | 
			
		||||
DEMO_PASSWORD=
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Disable or enable the running balance column data
 | 
			
		||||
# Please disable this. It's a very experimental feature.
 | 
			
		||||
#
 | 
			
		||||
USE_RUNNING_BALANCE=false
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# The v2 layout is very experimental. If it breaks you get to keep both parts.
 | 
			
		||||
# Be wary of data loss.
 | 
			
		||||
@@ -332,15 +326,7 @@ DEMO_PASSWORD=
 | 
			
		||||
FIREFLY_III_LAYOUT=v1
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# If you have trouble configuring your Firefly III installation, DON'T BOTHER setting this variable.
 | 
			
		||||
# It won't work. It doesn't do ANYTHING. Don't believe the lies you read online. I'm not joking.
 | 
			
		||||
# This configuration value WILL NOT HELP.
 | 
			
		||||
#
 | 
			
		||||
# Notable exception to this rule is Synology, which, according to some users, will use APP_URL to rewrite stuff.
 | 
			
		||||
#
 | 
			
		||||
# This variable is ONLY used in some of the emails Firefly III sends around. Nowhere else.
 | 
			
		||||
# So when configuring anything WEB related this variable doesn't do anything. Nothing
 | 
			
		||||
#
 | 
			
		||||
# If you're stuck I understand you get desperate but look SOMEWHERE ELSE.
 | 
			
		||||
# Please make sure this URL matches the external URL of your Firefly III installation.
 | 
			
		||||
# It is used to validate specific requests and to generate URLs in emails.
 | 
			
		||||
#
 | 
			
		||||
APP_URL=http://localhost
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
# code owners for this Firefly III related repository
 | 
			
		||||
* @JC5 @SDx3
 | 
			
		||||
							
								
								
									
										3
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							@@ -4,6 +4,7 @@ updates:
 | 
			
		||||
  # Check for updates to GitHub Actions every week
 | 
			
		||||
  - package-ecosystem: "github-actions"
 | 
			
		||||
    directory: "/"
 | 
			
		||||
    labels: []
 | 
			
		||||
    schedule:
 | 
			
		||||
      interval: "weekly"
 | 
			
		||||
 | 
			
		||||
@@ -11,6 +12,7 @@ updates:
 | 
			
		||||
  - package-ecosystem: "composer"
 | 
			
		||||
    directory: "/" # Location of package manifests
 | 
			
		||||
    target-branch: develop
 | 
			
		||||
    labels: []
 | 
			
		||||
    versioning-strategy: increase
 | 
			
		||||
    schedule:
 | 
			
		||||
      interval: "weekly"
 | 
			
		||||
@@ -18,6 +20,7 @@ updates:
 | 
			
		||||
  # yarn / JS updates
 | 
			
		||||
  - package-ecosystem: "npm"
 | 
			
		||||
    directory: "/"
 | 
			
		||||
    labels: []
 | 
			
		||||
    target-branch: develop
 | 
			
		||||
    versioning-strategy: increase
 | 
			
		||||
    schedule:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/funding.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/funding.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,4 +1,6 @@
 | 
			
		||||
# These are supported funding model platforms
 | 
			
		||||
# Firefly III sponsor options
 | 
			
		||||
 | 
			
		||||
github: jc5
 | 
			
		||||
patreon: JC5
 | 
			
		||||
ko_fi: jamesc5
 | 
			
		||||
liberapay: JC5
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/label-actions.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/label-actions.yml
									
									
									
									
										vendored
									
									
								
							@@ -4,6 +4,7 @@
 | 
			
		||||
feature:
 | 
			
		||||
  issues:
 | 
			
		||||
    # Post a comment, `{issue-author}` is an optional placeholder
 | 
			
		||||
    unlabel: feature
 | 
			
		||||
    comment: |
 | 
			
		||||
      Hi there! 
 | 
			
		||||
 | 
			
		||||
@@ -32,6 +33,7 @@ epic:
 | 
			
		||||
      Thank you for your contributions.
 | 
			
		||||
 | 
			
		||||
enhancement:
 | 
			
		||||
  unlabel: enhancement
 | 
			
		||||
  issues:
 | 
			
		||||
    # Post a comment, `{issue-author}` is an optional placeholder
 | 
			
		||||
    comment: |
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
								
							@@ -1,13 +1,20 @@
 | 
			
		||||
<!--
 | 
			
		||||
Before you create a new PR, please consider:
 | 
			
		||||
Thank you for submitting new code to Firefly III, or any of the related projects. Please read the following rules carefully.
 | 
			
		||||
 | 
			
		||||
1) Pull requests for the MAIN branch will be closed.
 | 
			
		||||
2) DO NOT include translations in your PR. Only English US sentences.
 | 
			
		||||
- Please do not submit solutions for problems that are not already reported in an issue.
 | 
			
		||||
- Unfortunately, Firefly III can't be your learning experience. If you're new to all of this, please open an issue first.
 | 
			
		||||
- Please do not open PRs to "discuss" possible solutions or to "get feedback" on your code. I simply don't have time for that.
 | 
			
		||||
- Pull requests for the MAIN branch will be closed.
 | 
			
		||||
- DO NOT include translated strings in your PR.
 | 
			
		||||
- PRs (or parts thereof) that only fix issues inside code comments will not be accepted.
 | 
			
		||||
 | 
			
		||||
If it feels necessary to open an issue first, please do so, before you open a PR.
 | 
			
		||||
 | 
			
		||||
See also: https://docs.firefly-iii.org/explanation/support/#contributing-code
 | 
			
		||||
 | 
			
		||||
Thanks.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
Fixes issue # (if relevant)
 | 
			
		||||
    
 | 
			
		||||
This PR fixes issue # (if relevant).
 | 
			
		||||
 | 
			
		||||
Changes in this pull request:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								.github/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										58
									
								
								.github/stale.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,58 +0,0 @@
 | 
			
		||||
# Configuration for probot-stale - https://github.com/probot/stale
 | 
			
		||||
 | 
			
		||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
 | 
			
		||||
daysUntilStale: 14
 | 
			
		||||
 | 
			
		||||
# Number of days of inactivity before a stale Issue or Pull Request is closed.
 | 
			
		||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
 | 
			
		||||
daysUntilClose: 14
 | 
			
		||||
 | 
			
		||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
 | 
			
		||||
# - "[Status] Maybe Later"
 | 
			
		||||
exemptLabels:
 | 
			
		||||
  - enhancement
 | 
			
		||||
  - feature
 | 
			
		||||
  - bug
 | 
			
		||||
  - announcement
 | 
			
		||||
  - "layout-v3"
 | 
			
		||||
 | 
			
		||||
# Set to true to ignore issues in a project (defaults to false)
 | 
			
		||||
exemptProjects: false
 | 
			
		||||
 | 
			
		||||
# Set to true to ignore issues in a milestone (defaults to false)
 | 
			
		||||
exemptMilestones: false
 | 
			
		||||
 | 
			
		||||
# Label to use when marking as stale
 | 
			
		||||
staleLabel: stale
 | 
			
		||||
 | 
			
		||||
# Comment to post when marking as stale. Set to `false` to disable
 | 
			
		||||
markComment: >
 | 
			
		||||
  This issue has been automatically marked as stale because it has not had
 | 
			
		||||
  recent activity. It will be closed if no further activity occurs. Thank you
 | 
			
		||||
  for your contributions.
 | 
			
		||||
 | 
			
		||||
# Comment to post when removing the stale label.
 | 
			
		||||
# unmarkComment: >
 | 
			
		||||
#   Your comment here.
 | 
			
		||||
 | 
			
		||||
# Comment to post when closing a stale Issue or Pull Request.
 | 
			
		||||
# closeComment: >
 | 
			
		||||
#   Your comment here.
 | 
			
		||||
 | 
			
		||||
# Limit the number of actions per hour, from 1-30. Default is 30
 | 
			
		||||
limitPerRun: 30
 | 
			
		||||
 | 
			
		||||
# Limit to only `issues` or `pulls`
 | 
			
		||||
# only: issues
 | 
			
		||||
 | 
			
		||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
 | 
			
		||||
# pulls:
 | 
			
		||||
#   daysUntilStale: 30
 | 
			
		||||
#   markComment: >
 | 
			
		||||
#     This pull request has been automatically marked as stale because it has not had
 | 
			
		||||
#     recent activity. It will be closed if no further activity occurs. Thank you
 | 
			
		||||
#     for your contributions.
 | 
			
		||||
 | 
			
		||||
# issues:
 | 
			
		||||
#   exemptLabels:
 | 
			
		||||
#     - confirmed
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							@@ -7,7 +7,7 @@ permissions:
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: '0 0 * * *'
 | 
			
		||||
    - cron: '0 1 * * *'
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
jobs:
 | 
			
		||||
  prune:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/close-duplicates.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/close-duplicates.yml
									
									
									
									
										vendored
									
									
								
							@@ -3,7 +3,7 @@ name: "Issues - Command to close duplicate issues"
 | 
			
		||||
# the workflow to execute on is comments that are newly created
 | 
			
		||||
on:
 | 
			
		||||
  issue_comment:
 | 
			
		||||
    types: [created]
 | 
			
		||||
    types: [ created ]
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  issues: write
 | 
			
		||||
@@ -13,7 +13,7 @@ jobs:
 | 
			
		||||
  close_duplicates:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: github/command@v1.1.0
 | 
			
		||||
      - uses: github/command@v1.2.2
 | 
			
		||||
        id: command
 | 
			
		||||
        with:
 | 
			
		||||
          allowed_contexts: "issue"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/debug-info-actions.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/debug-info-actions.yml
									
									
									
									
										vendored
									
									
								
							@@ -3,9 +3,9 @@ name: 'Issues - Respond to hidden commands'
 | 
			
		||||
# the workflow to execute on is comments that are newly created
 | 
			
		||||
on:
 | 
			
		||||
  issues:
 | 
			
		||||
    types: [opened, edited]
 | 
			
		||||
    types: [ opened, edited ]
 | 
			
		||||
  issue_comment:
 | 
			
		||||
    types: [created]
 | 
			
		||||
    types: [ created ]
 | 
			
		||||
 | 
			
		||||
# permissions needed for reacting to IssueOps commands on issues and PRs
 | 
			
		||||
permissions:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.github/workflows/label-actions.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/label-actions.yml
									
									
									
									
										vendored
									
									
								
							@@ -2,11 +2,11 @@ name: 'Issues - Reply to specific labels'
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  issues:
 | 
			
		||||
    types: [labeled, unlabeled]
 | 
			
		||||
    types: [ labeled, unlabeled ]
 | 
			
		||||
  pull_request_target:
 | 
			
		||||
    types: [labeled, unlabeled]
 | 
			
		||||
    types: [ labeled, unlabeled ]
 | 
			
		||||
  discussion:
 | 
			
		||||
    types: [labeled, unlabeled]
 | 
			
		||||
    types: [ labeled, unlabeled ]
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							@@ -3,17 +3,27 @@ name: 'Issues - Lock old issues'
 | 
			
		||||
on:
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: '0 0 * * *'
 | 
			
		||||
    - cron: '0 2 * * *'
 | 
			
		||||
 | 
			
		||||
concurrency:
 | 
			
		||||
  group: lock-threads
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  issues: write
 | 
			
		||||
  pull-requests: write
 | 
			
		||||
  discussions: write
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  lock:
 | 
			
		||||
    permissions:
 | 
			
		||||
      issues: write
 | 
			
		||||
      pull-requests: write
 | 
			
		||||
      discussions: write
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: JC5/lock-threads@main
 | 
			
		||||
      - uses: dessant/lock-threads@v5
 | 
			
		||||
        with:
 | 
			
		||||
          github-token: ${{ github.token }}
 | 
			
		||||
          issue-inactive-days: 7
 | 
			
		||||
          pr-inactive-days: 7
 | 
			
		||||
          issue-inactive-days: 21
 | 
			
		||||
          pr-inactive-days: 21
 | 
			
		||||
          discussion-inactive-days: 21
 | 
			
		||||
          log-output: true
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										191
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										191
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -4,11 +4,11 @@ on:
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
    inputs:
 | 
			
		||||
      version:
 | 
			
		||||
        description: 'Version to release'
 | 
			
		||||
        description: 'Release "v1.2.3" or "develop"'
 | 
			
		||||
        required: true
 | 
			
		||||
        default: 'develop'
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron:  '15 0 * * MON,THU'
 | 
			
		||||
    - cron: '0 3 * * MON'
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
@@ -39,7 +39,7 @@ jobs:
 | 
			
		||||
          php-version: '8.3'
 | 
			
		||||
          extensions: mbstring, intl, zip, bcmath
 | 
			
		||||
      - name: crowdin action
 | 
			
		||||
        uses: crowdin/github-action@v1
 | 
			
		||||
        uses: crowdin/github-action@v2
 | 
			
		||||
        with:
 | 
			
		||||
          upload_sources: true
 | 
			
		||||
          download_translations: true
 | 
			
		||||
@@ -51,7 +51,7 @@ jobs:
 | 
			
		||||
          CROWDIN_TOKEN: ${{ secrets.CROWDIN_TOKEN }}
 | 
			
		||||
      - name: Cleanup translations
 | 
			
		||||
        id: cleanup-transactions
 | 
			
		||||
        uses: JC5/firefly-iii-dev@v32
 | 
			
		||||
        uses: JC5/firefly-iii-dev@main
 | 
			
		||||
        with:
 | 
			
		||||
          action: 'ff3:crowdin-warning'
 | 
			
		||||
          output: ''
 | 
			
		||||
@@ -60,16 +60,25 @@ jobs:
 | 
			
		||||
          GH_TOKEN: ''
 | 
			
		||||
      - name: Cleanup changelog
 | 
			
		||||
        id: cleanup-changelog
 | 
			
		||||
        uses: JC5/firefly-iii-dev@v32
 | 
			
		||||
        uses: JC5/firefly-iii-dev@main
 | 
			
		||||
        with:
 | 
			
		||||
          action: 'ff3:changelog'
 | 
			
		||||
          output: ''
 | 
			
		||||
        env:
 | 
			
		||||
          FIREFLY_III_ROOT: /github/workspace
 | 
			
		||||
          GH_TOKEN: ${{ secrets.CHANGELOG_TOKEN }}
 | 
			
		||||
      - name: "Create THANKS.md"
 | 
			
		||||
        id: thank-you
 | 
			
		||||
        uses: JC5/firefly-iii-dev@main
 | 
			
		||||
        with:
 | 
			
		||||
          action: 'ff3:thank-you'
 | 
			
		||||
          output: ''
 | 
			
		||||
        env:
 | 
			
		||||
          FIREFLY_III_ROOT: /github/workspace
 | 
			
		||||
          GH_TOKEN: ''
 | 
			
		||||
      - name: Extract changelog
 | 
			
		||||
        id: extract-changelog
 | 
			
		||||
        uses: JC5/firefly-iii-dev@v32
 | 
			
		||||
        uses: JC5/firefly-iii-dev@main
 | 
			
		||||
        with:
 | 
			
		||||
          action: 'ff3:extract-changelog'
 | 
			
		||||
          output: 'output'
 | 
			
		||||
@@ -78,7 +87,7 @@ jobs:
 | 
			
		||||
          GH_TOKEN: ""
 | 
			
		||||
      - name: Replace version
 | 
			
		||||
        id: replace-version
 | 
			
		||||
        uses: JC5/firefly-iii-dev@v32
 | 
			
		||||
        uses: JC5/firefly-iii-dev@main
 | 
			
		||||
        with:
 | 
			
		||||
          action: 'ff3:version'
 | 
			
		||||
          output: ''
 | 
			
		||||
@@ -88,7 +97,7 @@ jobs:
 | 
			
		||||
          FF_III_VERSION: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
 | 
			
		||||
      - name: Generate JSON v1
 | 
			
		||||
        id: json-v1
 | 
			
		||||
        uses: JC5/firefly-iii-dev@v32
 | 
			
		||||
        uses: JC5/firefly-iii-dev@main
 | 
			
		||||
        with:
 | 
			
		||||
          action: 'ff3:json-translations v1'
 | 
			
		||||
          output: ''
 | 
			
		||||
@@ -97,7 +106,7 @@ jobs:
 | 
			
		||||
          GH_TOKEN: ''
 | 
			
		||||
      - name: Generate JSON v2
 | 
			
		||||
        id: json-v2
 | 
			
		||||
        uses: JC5/firefly-iii-dev@v32
 | 
			
		||||
        uses: JC5/firefly-iii-dev@main
 | 
			
		||||
        with:
 | 
			
		||||
          action: 'ff3:json-translations v2'
 | 
			
		||||
          output: ''
 | 
			
		||||
@@ -106,44 +115,76 @@ jobs:
 | 
			
		||||
          GH_TOKEN: ''
 | 
			
		||||
      - name: Code cleanup
 | 
			
		||||
        id: code-cleanup
 | 
			
		||||
        uses: JC5/firefly-iii-dev@v32
 | 
			
		||||
        uses: JC5/firefly-iii-dev@main
 | 
			
		||||
        with:
 | 
			
		||||
          action: 'ff3:code'
 | 
			
		||||
          output: ''
 | 
			
		||||
        env:
 | 
			
		||||
          FIREFLY_III_ROOT: /github/workspace
 | 
			
		||||
          GH_TOKEN: ''
 | 
			
		||||
      - name: Build new JS
 | 
			
		||||
      - name: Build JS
 | 
			
		||||
        run: |
 | 
			
		||||
          npm upgrade
 | 
			
		||||
          npm run build
 | 
			
		||||
      - name: Build old JS
 | 
			
		||||
        id: old-js
 | 
			
		||||
        uses: JC5/firefly-iii-dev@v32
 | 
			
		||||
        with:
 | 
			
		||||
          action: 'ff3:old-js'
 | 
			
		||||
          output: ''
 | 
			
		||||
        env:
 | 
			
		||||
          FIREFLY_III_ROOT: /github/workspace
 | 
			
		||||
          GH_TOKEN: ''
 | 
			
		||||
          npm install
 | 
			
		||||
          npm run prod  --workspace=v1
 | 
			
		||||
          npm run build --workspace=v2
 | 
			
		||||
          npm update
 | 
			
		||||
      - name: Run CI
 | 
			
		||||
        run: |
 | 
			
		||||
          rm -rf vendor composer.lock
 | 
			
		||||
          composer validate --strict
 | 
			
		||||
          composer update --no-dev --no-scripts --no-plugins -q
 | 
			
		||||
          sudo chown -R runner:docker resources/lang
 | 
			
		||||
          .ci/phpcs.sh
 | 
			
		||||
      - name: Import GPG key
 | 
			
		||||
        uses: crazy-max/ghaction-import-gpg@v6
 | 
			
		||||
        with:
 | 
			
		||||
          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
 | 
			
		||||
          passphrase: ${{ secrets.PASSPHRASE }}
 | 
			
		||||
      - name: Release
 | 
			
		||||
        run: |
 | 
			
		||||
          # do some configuration
 | 
			
		||||
          sudo timedatectl set-timezone Europe/Amsterdam
 | 
			
		||||
          git config user.name github-actions
 | 
			
		||||
          git config user.email 41898282+github-actions[bot]@users.noreply.github.com
 | 
			
		||||
          git config advice.addIgnoredFile false
 | 
			
		||||
 | 
			
		||||
          # set some variables
 | 
			
		||||
          releaseName=$version
 | 
			
		||||
          originalName=$version
 | 
			
		||||
          zipName=FireflyIII-$version.zip
 | 
			
		||||
          tarName=FireflyIII-$version.tar.gz
 | 
			
		||||
 | 
			
		||||
          # update composer (again)
 | 
			
		||||
          composer update --no-dev --no-scripts --no-plugins
 | 
			
		||||
          composer dump-autoload
 | 
			
		||||
 | 
			
		||||
          # if this is a develop build, slightly different variable names.
 | 
			
		||||
          if [[ "develop" == "$version" ]]; then
 | 
			
		||||
            [[ -z $(git status --untracked-files=normal --porcelain) ]] && echo "this branch is clean, no need to push..." && exit 0;
 | 
			
		||||
            releaseName=$version-$(date +'%Y%m%d')
 | 
			
		||||
            originalName=$releaseName
 | 
			
		||||
            zipName=FireflyIII-develop.zip
 | 
			
		||||
            tarName=FireflyIII-develop.tar.gz
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
          # in both cases, if the release or tag already exists, add ".1" until it no longer exists.
 | 
			
		||||
          tagFound=true
 | 
			
		||||
          tagCount=1
 | 
			
		||||
          while [ "$tagFound" = true ]
 | 
			
		||||
          do
 | 
			
		||||
            if [ $(git tag -l "$releaseName") ]; then
 | 
			
		||||
              echo "Tag $releaseName exists already."
 | 
			
		||||
              releaseName="$originalName"."$tagCount"
 | 
			
		||||
              echo "Tag for release is now $releaseName"
 | 
			
		||||
              tagCount=$((tagCount+1))
 | 
			
		||||
            else
 | 
			
		||||
             echo "Tag $releaseName does not exist, can continue"
 | 
			
		||||
             tagFound=false
 | 
			
		||||
            fi
 | 
			
		||||
          done
 | 
			
		||||
          echo "Will use tag and release name $releaseName."
 | 
			
		||||
 | 
			
		||||
          # add all content, except output.txt (this contains the changelog and/or the download instructions)
 | 
			
		||||
          echo 'Add all and reset output.txt'
 | 
			
		||||
          git add -A
 | 
			
		||||
          if test -f "output.txt"; then
 | 
			
		||||
            git reset output.txt
 | 
			
		||||
@@ -151,19 +192,101 @@ jobs:
 | 
			
		||||
          git commit -m "Auto commit for release '$version' on $(date +'%Y-%m-%d')" || true
 | 
			
		||||
          git push
 | 
			
		||||
 | 
			
		||||
          # zip and tar everything
 | 
			
		||||
          echo 'Zip and tar...'
 | 
			
		||||
          zip -rq $zipName . -x "*.git*" "*.ci*" "*.github*" "*node_modules*" "*output.txt*"
 | 
			
		||||
          touch $tarName
 | 
			
		||||
          tar --exclude=$tarName --exclude=$zipName --exclude='./.git' --exclude='./.ci' --exclude='./.github' --exclude='./node_modules' --exclude='./output.txt' -czf $tarName .
 | 
			
		||||
 | 
			
		||||
          # add sha256 sum
 | 
			
		||||
          echo 'Sha sum ...'
 | 
			
		||||
          sha256sum -b $zipName > $zipName.sha256
 | 
			
		||||
          sha256sum -b $tarName > $tarName.sha256
 | 
			
		||||
 | 
			
		||||
          # add signatures:
 | 
			
		||||
          gpg --armor --detach-sign $zipName
 | 
			
		||||
          gpg --armor --detach-sign $tarName
 | 
			
		||||
 | 
			
		||||
          # create a development (nightly) release:
 | 
			
		||||
          if [[ "develop" == "$version" ]]; then
 | 
			
		||||
            echo "Create nightly release."
 | 
			
		||||
            git tag -a $version-$(date +'%Y%m%d') -m "Nightly development release '$version' on $(date +'%Y-%m-%d')"
 | 
			
		||||
            git push origin $version-$(date +'%Y%m%d')
 | 
			
		||||
            gh release create $version-$(date +'%Y%m%d') -p --verify-tag \
 | 
			
		||||
              -t "Development release for $(date +'%Y-%m-%d')" \
 | 
			
		||||
              -n "Bi-weekly development release of Firefly III with the latest fixes, translations and features. This release was created on **$(date +'%Y-%m-%d')** and may contain bugs. Use at your own risk. Docker users can find this release under the \`develop\` tag."
 | 
			
		||||
          else
 | 
			
		||||
            echo "Create default release."
 | 
			
		||||
            git tag -a $version -m "Here be changelog"
 | 
			
		||||
            git push origin $version
 | 
			
		||||
            gh release create $version -F output.txt -t "$version" --verify-tag
 | 
			
		||||
            echo 'Develop release.'
 | 
			
		||||
            # add text to output.txt (instructions)
 | 
			
		||||
            rm output.txt
 | 
			
		||||
            echo "Bi-weekly development release of Firefly III with the latest fixes, translations and features. Docker users can find this release under the \`develop\` tag." >> output.txt
 | 
			
		||||
            echo "" >> output.txt
 | 
			
		||||
            echo "This release was created on **$(date +'%Y-%m-%d')** and may contain unexpected bugs. Data loss is rare but is not impossible. The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
 | 
			
		||||
            echo "" >> output.txt
 | 
			
		||||
            echo "* Please read the installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt
 | 
			
		||||
            echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
 | 
			
		||||
            echo "" >> output.txt
 | 
			
		||||
            echo ":warning: Please be careful with this pre-release, as it may not work as expected." >> output.txt
 | 
			
		||||
 | 
			
		||||
            # create the release:
 | 
			
		||||
            echo "Create nightly release."
 | 
			
		||||
            git tag -a $releaseName -m "Nightly development release '$version' on $(date +'%Y-%m-%d')"
 | 
			
		||||
            git push origin $releaseName
 | 
			
		||||
            gh release create $releaseName -p --verify-tag \
 | 
			
		||||
              -t "Development release for $(date +'%Y-%m-%d')" \
 | 
			
		||||
              -F output.txt
 | 
			
		||||
 | 
			
		||||
            # add zip file to release.
 | 
			
		||||
            gh release upload $releaseName $zipName
 | 
			
		||||
            gh release upload $releaseName $tarName
 | 
			
		||||
 | 
			
		||||
            # add sha256 sum to release
 | 
			
		||||
            gh release upload $releaseName $zipName.sha256
 | 
			
		||||
            gh release upload $releaseName $tarName.sha256
 | 
			
		||||
 | 
			
		||||
            # add signatures to release
 | 
			
		||||
            gh release upload $releaseName $zipName.asc
 | 
			
		||||
            gh release upload $releaseName $tarName.asc
 | 
			
		||||
 | 
			
		||||
            # get current HEAD and add as file to the release
 | 
			
		||||
            HEAD=$(git rev-parse HEAD)
 | 
			
		||||
            echo $HEAD > HEAD.txt
 | 
			
		||||
            gh release upload $releaseName HEAD.txt
 | 
			
		||||
          else
 | 
			
		||||
            echo 'MAIN (real) release'
 | 
			
		||||
            sudo chown -R runner:docker output.txt
 | 
			
		||||
            # add text to output.txt (more instructions)
 | 
			
		||||
            echo '' >> output.txt
 | 
			
		||||
            echo '### Instructions' >> output.txt
 | 
			
		||||
            echo '' >> output.txt
 | 
			
		||||
            echo "* Installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt
 | 
			
		||||
            echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
 | 
			
		||||
            echo "* The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
 | 
			
		||||
 | 
			
		||||
            echo "Create default release."
 | 
			
		||||
            git tag -a $releaseName -m "Here be changelog"
 | 
			
		||||
            git push origin $releaseName
 | 
			
		||||
            gh release create $releaseName -F output.txt -t "$releaseName" --verify-tag
 | 
			
		||||
 | 
			
		||||
            # add archive files to release
 | 
			
		||||
            gh release upload $releaseName $zipName
 | 
			
		||||
            gh release upload $releaseName $tarName
 | 
			
		||||
 | 
			
		||||
            # add sha256 sums to release
 | 
			
		||||
            gh release upload $releaseName $zipName.sha256
 | 
			
		||||
            gh release upload $releaseName $tarName.sha256
 | 
			
		||||
 | 
			
		||||
            # add signatures to release
 | 
			
		||||
            gh release upload $releaseName $zipName.asc
 | 
			
		||||
            gh release upload $releaseName $tarName.asc
 | 
			
		||||
 | 
			
		||||
            # get current HEAD and add as file to the release
 | 
			
		||||
            HEAD=$(git rev-parse HEAD)
 | 
			
		||||
            echo $HEAD > HEAD.txt
 | 
			
		||||
            gh release upload $releaseName HEAD.txt
 | 
			
		||||
 | 
			
		||||
            # remove all temporary files
 | 
			
		||||
            rm output.txt
 | 
			
		||||
            rm HEAD.txt
 | 
			
		||||
            rm $zipName
 | 
			
		||||
            rm $zipName.sha256
 | 
			
		||||
            rm $tarName
 | 
			
		||||
            rm $tarName.sha256
 | 
			
		||||
 | 
			
		||||
            # merge main back into develop
 | 
			
		||||
            git checkout develop
 | 
			
		||||
            git merge main
 | 
			
		||||
            git push
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								.github/workflows/sonarcloud.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/sonarcloud.yml
									
									
									
									
										vendored
									
									
								
							@@ -45,15 +45,6 @@ jobs:
 | 
			
		||||
      - name: Install Composer dependencies
 | 
			
		||||
        run: composer install --prefer-dist --no-interaction --no-progress --no-scripts
 | 
			
		||||
 | 
			
		||||
      - name: PHPStan
 | 
			
		||||
        run: .ci/phpstan.sh
 | 
			
		||||
 | 
			
		||||
      - name: PHPMD
 | 
			
		||||
        run: .ci/phpmd.sh
 | 
			
		||||
 | 
			
		||||
      - name: PHP CS Fixer
 | 
			
		||||
        run: .ci/phpcs.sh
 | 
			
		||||
 | 
			
		||||
      - name: "Create database file"
 | 
			
		||||
        run: touch storage/database/database.sqlite
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +1,7 @@
 | 
			
		||||
name: "Issues - Mark and close stale issues"
 | 
			
		||||
on:
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: "30 1 * * *"
 | 
			
		||||
    - cron: "0 4 * * *"
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
@@ -12,22 +12,23 @@ jobs:
 | 
			
		||||
    permissions:
 | 
			
		||||
      issues: write  # for actions/stale to close stale issues
 | 
			
		||||
      pull-requests: write  # for actions/stale to close stale PRs
 | 
			
		||||
      actions: write
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/stale@v9
 | 
			
		||||
        with:
 | 
			
		||||
          repo-token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
          stale-issue-message: >
 | 
			
		||||
            Hi there! 
 | 
			
		||||
            
 | 
			
		||||
          stale-issue-message: |
 | 
			
		||||
            Hi there!
 | 
			
		||||
 | 
			
		||||
            This is an automatic reply. `Share and enjoy`
 | 
			
		||||
 | 
			
		||||
            This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
 | 
			
		||||
 | 
			
		||||
            Thank you for your contributions.
 | 
			
		||||
          stale-pr-message: >
 | 
			
		||||
            Hi there! 
 | 
			
		||||
            
 | 
			
		||||
          stale-pr-message: |
 | 
			
		||||
            Hi there!
 | 
			
		||||
 | 
			
		||||
            This is an automatic reply. `Share and enjoy`
 | 
			
		||||
 | 
			
		||||
            This PR has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
 | 
			
		||||
@@ -35,4 +36,5 @@ jobs:
 | 
			
		||||
            Thank you for your contributions.
 | 
			
		||||
          days-before-stale: 14
 | 
			
		||||
          days-before-close: 7
 | 
			
		||||
          exempt-issue-labels: 'enhancement,feature,bug,announcement,epic,triage'
 | 
			
		||||
          exempt-all-milestones: true
 | 
			
		||||
          exempt-issue-labels: 'triage'
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -7,3 +7,14 @@ yarn-error.log
 | 
			
		||||
.env
 | 
			
		||||
/.ci/php-cs-fixer/vendor
 | 
			
		||||
coverage.xml
 | 
			
		||||
 | 
			
		||||
# ignore generated files.
 | 
			
		||||
public/build
 | 
			
		||||
 | 
			
		||||
# ignore v1 build files
 | 
			
		||||
resources/assets/v1/node_modules
 | 
			
		||||
resources/assets/v1/build
 | 
			
		||||
 | 
			
		||||
# ignore v2 build files
 | 
			
		||||
resources/assets/v2/node_modules
 | 
			
		||||
resources/assets/v2/build
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										198
									
								
								THANKS.md
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										198
									
								
								THANKS.md
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,198 @@
 | 
			
		||||
# Thank you! :tada: :heart: :tada:
 | 
			
		||||
 | 
			
		||||
Over time, many people have contributed to Firefly III. Their efforts are not always visible, but always remembered and appreciated.
 | 
			
		||||
Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution.
 | 
			
		||||
 | 
			
		||||
## 2024
 | 
			
		||||
- Jhon Pedroza
 | 
			
		||||
- mzhubail
 | 
			
		||||
- tasnim
 | 
			
		||||
- withbest
 | 
			
		||||
- Steve Wasiura
 | 
			
		||||
- imlonghao
 | 
			
		||||
- Rahman Yusuf
 | 
			
		||||
- Michael Thomas
 | 
			
		||||
- WardenJakx
 | 
			
		||||
- kuilin
 | 
			
		||||
- Stevie Robinson
 | 
			
		||||
- luzpaz
 | 
			
		||||
- Lemuel Roberto Bonifácio
 | 
			
		||||
- maureenferreira
 | 
			
		||||
 | 
			
		||||
## 2023
 | 
			
		||||
- tieu1991
 | 
			
		||||
- Maxco10
 | 
			
		||||
- zqye
 | 
			
		||||
- Mateus Pereira
 | 
			
		||||
- josephbadow
 | 
			
		||||
- Christian Desktop
 | 
			
		||||
- Edgars
 | 
			
		||||
- Hannah K
 | 
			
		||||
- noxonad
 | 
			
		||||
- Kaijia Feng
 | 
			
		||||
- Marc Ordinas i Llopis
 | 
			
		||||
- Kuba Turek
 | 
			
		||||
- Julien Stébenne
 | 
			
		||||
 | 
			
		||||
## 2022
 | 
			
		||||
- Johannes Zellner
 | 
			
		||||
- Janne Heß
 | 
			
		||||
- charlesteets
 | 
			
		||||
- Nathan PERIER
 | 
			
		||||
- Jan Willhaus
 | 
			
		||||
- canoine
 | 
			
		||||
- Rick Cuddy
 | 
			
		||||
- James
 | 
			
		||||
- Hugo Meyronneinc
 | 
			
		||||
- naveen
 | 
			
		||||
- neilnaveen
 | 
			
		||||
- naveensrinivasan
 | 
			
		||||
- Federico Micelli
 | 
			
		||||
- George Hahn
 | 
			
		||||
 | 
			
		||||
## 2021
 | 
			
		||||
- StillLoading
 | 
			
		||||
- Igor Rzegocki
 | 
			
		||||
- Lorenzo Breda
 | 
			
		||||
- Hosh
 | 
			
		||||
- Flightkick
 | 
			
		||||
- alex6480
 | 
			
		||||
- VREEdom
 | 
			
		||||
- Hamza FADIL
 | 
			
		||||
- Kasper Læssø Sørensen
 | 
			
		||||
- Alex
 | 
			
		||||
- Jeroen De Meerleer
 | 
			
		||||
- Ruben van Erk
 | 
			
		||||
- Fabian Zimmermann
 | 
			
		||||
- Mirko Berger
 | 
			
		||||
- KaihatsuOnline
 | 
			
		||||
- MihataBG
 | 
			
		||||
 | 
			
		||||
## 2020
 | 
			
		||||
- Hannes Körber
 | 
			
		||||
- Julien Cassagne
 | 
			
		||||
- bu4ak
 | 
			
		||||
- Viktor Yakovlev
 | 
			
		||||
- Oliver Kaufmann
 | 
			
		||||
- Arvind Chembarpu
 | 
			
		||||
- GrayStrider
 | 
			
		||||
- psychowood
 | 
			
		||||
- Hosh Sadiq
 | 
			
		||||
- emansih
 | 
			
		||||
- Aniruddha Maru
 | 
			
		||||
- johnny
 | 
			
		||||
- sephrat
 | 
			
		||||
- bpatath
 | 
			
		||||
- Florian Dupret
 | 
			
		||||
- Maxim Kurbatov
 | 
			
		||||
- Lucas Guima
 | 
			
		||||
- Sandro
 | 
			
		||||
- Ruben Verhoef
 | 
			
		||||
- Daniel Idzerda
 | 
			
		||||
- Calum Smith
 | 
			
		||||
- Agraphie
 | 
			
		||||
- Tomer Shvueli
 | 
			
		||||
- Tomer S
 | 
			
		||||
 | 
			
		||||
## 2019
 | 
			
		||||
- Pascal Jungblut
 | 
			
		||||
- Justyn Shull
 | 
			
		||||
- Timendum
 | 
			
		||||
- Nicolas Lœuillet
 | 
			
		||||
- Dominic Guhl
 | 
			
		||||
- Melroy van den Berg
 | 
			
		||||
- Henning Stein
 | 
			
		||||
- Jan Klepek
 | 
			
		||||
- Jonathan
 | 
			
		||||
- Geoffrey “Frogeye” Preud'homme
 | 
			
		||||
- Michael Fix
 | 
			
		||||
- Juraj Mlich
 | 
			
		||||
- Eddybrando Vásquez
 | 
			
		||||
- hulloanson
 | 
			
		||||
- Will Rouesnel
 | 
			
		||||
- lastlink
 | 
			
		||||
- Mr. Funk
 | 
			
		||||
- Simon Taddiken
 | 
			
		||||
- Joris
 | 
			
		||||
- Bastiaan Nijkamp
 | 
			
		||||
 | 
			
		||||
## 2018
 | 
			
		||||
- a1ex4
 | 
			
		||||
- Daniel Quah
 | 
			
		||||
- Marco Lourenço
 | 
			
		||||
- Dennis Enderink
 | 
			
		||||
- Luca Bognolo
 | 
			
		||||
- Mike Conway
 | 
			
		||||
- Ben
 | 
			
		||||
- Mathieu Post
 | 
			
		||||
- George Hertz
 | 
			
		||||
- HamuZ HamuZ
 | 
			
		||||
- David Meiseles
 | 
			
		||||
- Erik Gelderblom
 | 
			
		||||
- Luca Vallerini
 | 
			
		||||
- Clemens Wijnekus
 | 
			
		||||
- Jacob Weisz
 | 
			
		||||
- Mateusz Gozdek
 | 
			
		||||
- anmol26s
 | 
			
		||||
- Kevin Hellemun
 | 
			
		||||
- Shashank M Chakravarthy
 | 
			
		||||
- Nico Schreiner
 | 
			
		||||
- Paul Sohier
 | 
			
		||||
- Brenden Conte
 | 
			
		||||
- Ben Yanke
 | 
			
		||||
- Andrew Prokhorenkov
 | 
			
		||||
- devlearner
 | 
			
		||||
- Kelvin
 | 
			
		||||
- J'informatique
 | 
			
		||||
 | 
			
		||||
## 2017
 | 
			
		||||
- Victor Mosin
 | 
			
		||||
- Justin
 | 
			
		||||
- Hugo van Duijn
 | 
			
		||||
- Lukas Winkler
 | 
			
		||||
- Marcin Szymanski
 | 
			
		||||
- Jens Kat
 | 
			
		||||
- koziolek
 | 
			
		||||
- jleeong
 | 
			
		||||
- Simon Hanna
 | 
			
		||||
- richard & xeli.eu
 | 
			
		||||
- Sergey Besedin
 | 
			
		||||
- Welbert Serra
 | 
			
		||||
- Joris de Vries
 | 
			
		||||
- Patrick Kostjens
 | 
			
		||||
- Enrico Lamperti
 | 
			
		||||
- Christian Musa
 | 
			
		||||
- Enno Lohmeier
 | 
			
		||||
 | 
			
		||||
## 2016
 | 
			
		||||
- Sander
 | 
			
		||||
- Toon Schoenmakers
 | 
			
		||||
- Telyn
 | 
			
		||||
- Sander Kleykens
 | 
			
		||||
- Tom van der Werf
 | 
			
		||||
- Matthew Peck
 | 
			
		||||
- Sander Mulders
 | 
			
		||||
- Bonno Nachtegaal-Karels
 | 
			
		||||
- Niek Haarman
 | 
			
		||||
- Edwin
 | 
			
		||||
- Thijs Alkemade
 | 
			
		||||
- zjean
 | 
			
		||||
- Graham Miller
 | 
			
		||||
- Robert Horlings
 | 
			
		||||
- leander091
 | 
			
		||||
 | 
			
		||||
## 2015
 | 
			
		||||
- Antonio Spinelli
 | 
			
		||||
- Colin O'Dell
 | 
			
		||||
- RonaldvanMeer
 | 
			
		||||
- Richard Ebbers
 | 
			
		||||
- Balazs Varkonyi
 | 
			
		||||
- Niek van der Kooy
 | 
			
		||||
- Ilya Kil
 | 
			
		||||
 | 
			
		||||
## 2014
 | 
			
		||||
- Stewart Malik
 | 
			
		||||
- Graham Campbell
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Thank you for all your support!
 | 
			
		||||
@@ -77,7 +77,7 @@ class AccountController extends Controller
 | 
			
		||||
        $query           = $data['query'];
 | 
			
		||||
        $date            = $data['date'] ?? today(config('app.timezone'));
 | 
			
		||||
        $return          = [];
 | 
			
		||||
        $result          = $this->repository->searchAccount((string) $query, $types, $this->parameters->get('limit'));
 | 
			
		||||
        $result          = $this->repository->searchAccount((string)$query, $types, $this->parameters->get('limit'));
 | 
			
		||||
 | 
			
		||||
        // TODO this code is duplicated in the V2 Autocomplete controller, which means this code is due to be deprecated.
 | 
			
		||||
        $defaultCurrency = app('amount')->getDefaultCurrency();
 | 
			
		||||
@@ -97,11 +97,11 @@ class AccountController extends Controller
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $return[]        = [
 | 
			
		||||
                'id'                      => (string) $account->id,
 | 
			
		||||
                'id'                      => (string)$account->id,
 | 
			
		||||
                'name'                    => $account->name,
 | 
			
		||||
                'name_with_balance'       => $nameWithBalance,
 | 
			
		||||
                'type'                    => $account->accountType->type,
 | 
			
		||||
                'currency_id'             => (string) $currency->id,
 | 
			
		||||
                'currency_id'             => (string)$currency->id,
 | 
			
		||||
                'currency_name'           => $currency->name,
 | 
			
		||||
                'currency_code'           => $currency->code,
 | 
			
		||||
                'currency_symbol'         => $currency->symbol,
 | 
			
		||||
@@ -114,8 +114,8 @@ class AccountController extends Controller
 | 
			
		||||
            $return,
 | 
			
		||||
            static function (array $left, array $right) {
 | 
			
		||||
                $order = [AccountType::ASSET, AccountType::REVENUE, AccountType::EXPENSE];
 | 
			
		||||
                $posA  = (int) array_search($left['type'], $order, true);
 | 
			
		||||
                $posB  = (int) array_search($right['type'], $order, true);
 | 
			
		||||
                $posA  = (int)array_search($left['type'], $order, true);
 | 
			
		||||
                $posB  = (int)array_search($right['type'], $order, true);
 | 
			
		||||
 | 
			
		||||
                return $posA - $posB;
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ class BillController extends Controller
 | 
			
		||||
        $filtered = $result->map(
 | 
			
		||||
            static function (Bill $item) {
 | 
			
		||||
                return [
 | 
			
		||||
                    'id'     => (string) $item->id,
 | 
			
		||||
                    'id'     => (string)$item->id,
 | 
			
		||||
                    'name'   => $item->name,
 | 
			
		||||
                    'active' => $item->active,
 | 
			
		||||
                ];
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ class BudgetController extends Controller
 | 
			
		||||
        $filtered = $result->map(
 | 
			
		||||
            static function (Budget $item) {
 | 
			
		||||
                return [
 | 
			
		||||
                    'id'   => (string) $item->id,
 | 
			
		||||
                    'id'   => (string)$item->id,
 | 
			
		||||
                    'name' => $item->name,
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ class CategoryController extends Controller
 | 
			
		||||
        $filtered = $result->map(
 | 
			
		||||
            static function (Category $item) {
 | 
			
		||||
                return [
 | 
			
		||||
                    'id'   => (string) $item->id,
 | 
			
		||||
                    'id'   => (string)$item->id,
 | 
			
		||||
                    'name' => $item->name,
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ class CurrencyController extends Controller
 | 
			
		||||
        /** @var TransactionCurrency $currency */
 | 
			
		||||
        foreach ($collection as $currency) {
 | 
			
		||||
            $result[] = [
 | 
			
		||||
                'id'             => (string) $currency->id,
 | 
			
		||||
                'id'             => (string)$currency->id,
 | 
			
		||||
                'name'           => $currency->name,
 | 
			
		||||
                'code'           => $currency->code,
 | 
			
		||||
                'symbol'         => $currency->symbol,
 | 
			
		||||
@@ -94,7 +94,7 @@ class CurrencyController extends Controller
 | 
			
		||||
        /** @var TransactionCurrency $currency */
 | 
			
		||||
        foreach ($collection as $currency) {
 | 
			
		||||
            $result[] = [
 | 
			
		||||
                'id'             => (string) $currency->id,
 | 
			
		||||
                'id'             => (string)$currency->id,
 | 
			
		||||
                'name'           => sprintf('%s (%s)', $currency->name, $currency->code),
 | 
			
		||||
                'code'           => $currency->code,
 | 
			
		||||
                'symbol'         => $currency->symbol,
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ class ObjectGroupController extends Controller
 | 
			
		||||
        /** @var ObjectGroup $objectGroup */
 | 
			
		||||
        foreach ($result as $objectGroup) {
 | 
			
		||||
            $return[] = [
 | 
			
		||||
                'id'    => (string) $objectGroup->id,
 | 
			
		||||
                'id'    => (string)$objectGroup->id,
 | 
			
		||||
                'name'  => $objectGroup->title,
 | 
			
		||||
                'title' => $objectGroup->title,
 | 
			
		||||
            ];
 | 
			
		||||
 
 | 
			
		||||
@@ -75,14 +75,14 @@ class PiggyBankController extends Controller
 | 
			
		||||
            $currency    = $this->accountRepository->getAccountCurrency($piggy->account) ?? $defaultCurrency;
 | 
			
		||||
            $objectGroup = $piggy->objectGroups()->first();
 | 
			
		||||
            $response[]  = [
 | 
			
		||||
                'id'                      => (string) $piggy->id,
 | 
			
		||||
                'id'                      => (string)$piggy->id,
 | 
			
		||||
                'name'                    => $piggy->name,
 | 
			
		||||
                'currency_id'             => (string) $currency->id,
 | 
			
		||||
                'currency_id'             => (string)$currency->id,
 | 
			
		||||
                'currency_name'           => $currency->name,
 | 
			
		||||
                'currency_code'           => $currency->code,
 | 
			
		||||
                'currency_symbol'         => $currency->symbol,
 | 
			
		||||
                'currency_decimal_places' => $currency->decimal_places,
 | 
			
		||||
                'object_group_id'         => null === $objectGroup ? null : (string) $objectGroup->id,
 | 
			
		||||
                'object_group_id'         => null === $objectGroup ? null : (string)$objectGroup->id,
 | 
			
		||||
                'object_group_title'      => $objectGroup?->title,
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
@@ -107,7 +107,7 @@ class PiggyBankController extends Controller
 | 
			
		||||
            $currentAmount = $this->piggyRepository->getRepetition($piggy)->currentamount ?? '0';
 | 
			
		||||
            $objectGroup   = $piggy->objectGroups()->first();
 | 
			
		||||
            $response[]    = [
 | 
			
		||||
                'id'                      => (string) $piggy->id,
 | 
			
		||||
                'id'                      => (string)$piggy->id,
 | 
			
		||||
                'name'                    => $piggy->name,
 | 
			
		||||
                'name_with_balance'       => sprintf(
 | 
			
		||||
                    '%s (%s / %s)',
 | 
			
		||||
@@ -115,12 +115,12 @@ class PiggyBankController extends Controller
 | 
			
		||||
                    app('amount')->formatAnything($currency, $currentAmount, false),
 | 
			
		||||
                    app('amount')->formatAnything($currency, $piggy->targetamount, false),
 | 
			
		||||
                ),
 | 
			
		||||
                'currency_id'             => (string) $currency->id,
 | 
			
		||||
                'currency_id'             => (string)$currency->id,
 | 
			
		||||
                'currency_name'           => $currency->name,
 | 
			
		||||
                'currency_code'           => $currency->code,
 | 
			
		||||
                'currency_symbol'         => $currency->symbol,
 | 
			
		||||
                'currency_decimal_places' => $currency->decimal_places,
 | 
			
		||||
                'object_group_id'         => null === $objectGroup ? null : (string) $objectGroup->id,
 | 
			
		||||
                'object_group_id'         => null === $objectGroup ? null : (string)$objectGroup->id,
 | 
			
		||||
                'object_group_title'      => $objectGroup?->title,
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ class RecurrenceController extends Controller
 | 
			
		||||
        /** @var Recurrence $recurrence */
 | 
			
		||||
        foreach ($recurrences as $recurrence) {
 | 
			
		||||
            $response[] = [
 | 
			
		||||
                'id'          => (string) $recurrence->id,
 | 
			
		||||
                'id'          => (string)$recurrence->id,
 | 
			
		||||
                'name'        => $recurrence->title,
 | 
			
		||||
                'description' => $recurrence->description,
 | 
			
		||||
            ];
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,7 @@ class RuleController extends Controller
 | 
			
		||||
        /** @var Rule $rule */
 | 
			
		||||
        foreach ($rules as $rule) {
 | 
			
		||||
            $response[] = [
 | 
			
		||||
                'id'          => (string) $rule->id,
 | 
			
		||||
                'id'          => (string)$rule->id,
 | 
			
		||||
                'name'        => $rule->title,
 | 
			
		||||
                'description' => $rule->description,
 | 
			
		||||
            ];
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,7 @@ class RuleGroupController extends Controller
 | 
			
		||||
        /** @var RuleGroup $group */
 | 
			
		||||
        foreach ($groups as $group) {
 | 
			
		||||
            $response[] = [
 | 
			
		||||
                'id'          => (string) $group->id,
 | 
			
		||||
                'id'          => (string)$group->id,
 | 
			
		||||
                'name'        => $group->title,
 | 
			
		||||
                'description' => $group->description,
 | 
			
		||||
            ];
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ class TagController extends Controller
 | 
			
		||||
        /** @var Tag $tag */
 | 
			
		||||
        foreach ($result as $tag) {
 | 
			
		||||
            $array[] = [
 | 
			
		||||
                'id'   => (string) $tag->id,
 | 
			
		||||
                'id'   => (string)$tag->id,
 | 
			
		||||
                'name' => $tag->tag,
 | 
			
		||||
                'tag'  => $tag->tag,
 | 
			
		||||
            ];
 | 
			
		||||
 
 | 
			
		||||
@@ -76,8 +76,8 @@ class TransactionController extends Controller
 | 
			
		||||
        /** @var TransactionJournal $journal */
 | 
			
		||||
        foreach ($filtered as $journal) {
 | 
			
		||||
            $array[] = [
 | 
			
		||||
                'id'                   => (string) $journal->id,
 | 
			
		||||
                'transaction_group_id' => (string) $journal->transaction_group_id,
 | 
			
		||||
                'id'                   => (string)$journal->id,
 | 
			
		||||
                'transaction_group_id' => (string)$journal->transaction_group_id,
 | 
			
		||||
                'name'                 => $journal->description,
 | 
			
		||||
                'description'          => $journal->description,
 | 
			
		||||
            ];
 | 
			
		||||
@@ -96,7 +96,7 @@ class TransactionController extends Controller
 | 
			
		||||
        $result = new Collection();
 | 
			
		||||
        if (is_numeric($data['query'])) {
 | 
			
		||||
            // search for group, not journal.
 | 
			
		||||
            $firstResult = $this->groupRepository->find((int) $data['query']);
 | 
			
		||||
            $firstResult = $this->groupRepository->find((int)$data['query']);
 | 
			
		||||
            if (null !== $firstResult) {
 | 
			
		||||
                // group may contain multiple journals, each a result:
 | 
			
		||||
                foreach ($firstResult->transactionJournals as $journal) {
 | 
			
		||||
@@ -114,8 +114,8 @@ class TransactionController extends Controller
 | 
			
		||||
        /** @var TransactionJournal $journal */
 | 
			
		||||
        foreach ($result as $journal) {
 | 
			
		||||
            $array[] = [
 | 
			
		||||
                'id'                   => (string) $journal->id,
 | 
			
		||||
                'transaction_group_id' => (string) $journal->transaction_group_id,
 | 
			
		||||
                'id'                   => (string)$journal->id,
 | 
			
		||||
                'transaction_group_id' => (string)$journal->transaction_group_id,
 | 
			
		||||
                'name'                 => sprintf('#%d: %s', $journal->transaction_group_id, $journal->description),
 | 
			
		||||
                'description'          => sprintf('#%d: %s', $journal->transaction_group_id, $journal->description),
 | 
			
		||||
            ];
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,7 @@ class TransactionTypeController extends Controller
 | 
			
		||||
        foreach ($types as $type) {
 | 
			
		||||
            // different key for consistency.
 | 
			
		||||
            $array[] = [
 | 
			
		||||
                'id'   => (string) $type->id,
 | 
			
		||||
                'id'   => (string)$type->id,
 | 
			
		||||
                'name' => $type->type,
 | 
			
		||||
                'type' => $type->type,
 | 
			
		||||
            ];
 | 
			
		||||
 
 | 
			
		||||
@@ -83,17 +83,17 @@ class AccountController extends Controller
 | 
			
		||||
        // user's preferences
 | 
			
		||||
        $defaultSet = $this->repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray();
 | 
			
		||||
 | 
			
		||||
        /** @var Preference $frontPage */
 | 
			
		||||
        $frontPage  = app('preferences')->get('frontPageAccounts', $defaultSet);
 | 
			
		||||
        /** @var Preference $frontpage */
 | 
			
		||||
        $frontpage  = app('preferences')->get('frontpageAccounts', $defaultSet);
 | 
			
		||||
        $default    = app('amount')->getDefaultCurrency();
 | 
			
		||||
 | 
			
		||||
        if (!(is_array($frontPage->data) && count($frontPage->data) > 0)) {
 | 
			
		||||
            $frontPage->data = $defaultSet;
 | 
			
		||||
            $frontPage->save();
 | 
			
		||||
        if (!(is_array($frontpage->data) && count($frontpage->data) > 0)) {
 | 
			
		||||
            $frontpage->data = $defaultSet;
 | 
			
		||||
            $frontpage->save();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // get accounts:
 | 
			
		||||
        $accounts   = $this->repository->getAccountsById($frontPage->data);
 | 
			
		||||
        $accounts   = $this->repository->getAccountsById($frontpage->data);
 | 
			
		||||
        $chartData  = [];
 | 
			
		||||
 | 
			
		||||
        /** @var Account $account */
 | 
			
		||||
@@ -104,7 +104,7 @@ class AccountController extends Controller
 | 
			
		||||
            }
 | 
			
		||||
            $currentSet   = [
 | 
			
		||||
                'label'                   => $account->name,
 | 
			
		||||
                'currency_id'             => (string) $currency->id,
 | 
			
		||||
                'currency_id'             => (string)$currency->id,
 | 
			
		||||
                'currency_code'           => $currency->code,
 | 
			
		||||
                'currency_symbol'         => $currency->symbol,
 | 
			
		||||
                'currency_decimal_places' => $currency->decimal_places,
 | 
			
		||||
 
 | 
			
		||||
@@ -76,38 +76,6 @@ abstract class Controller extends BaseController
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to help build URL's.
 | 
			
		||||
     */
 | 
			
		||||
    final protected function buildParams(): string
 | 
			
		||||
    {
 | 
			
		||||
        $return = '?';
 | 
			
		||||
        $params = [];
 | 
			
		||||
        foreach ($this->parameters as $key => $value) {
 | 
			
		||||
            if ('page' === $key) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if ($value instanceof Carbon) {
 | 
			
		||||
                $params[$key] = $value->format('Y-m-d');
 | 
			
		||||
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            $params[$key] = $value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return.http_build_query($params);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final protected function getManager(): Manager
 | 
			
		||||
    {
 | 
			
		||||
        // create some objects:
 | 
			
		||||
        $manager = new Manager();
 | 
			
		||||
        $baseUrl = request()->getSchemeAndHttpHost().'/api/v1';
 | 
			
		||||
        $manager->setSerializer(new JsonApiSerializer($baseUrl));
 | 
			
		||||
 | 
			
		||||
        return $manager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to grab all parameters from the URL.
 | 
			
		||||
     */
 | 
			
		||||
@@ -216,4 +184,36 @@ abstract class Controller extends BaseController
 | 
			
		||||
 | 
			
		||||
        return $bag;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to help build URL's.
 | 
			
		||||
     */
 | 
			
		||||
    final protected function buildParams(): string
 | 
			
		||||
    {
 | 
			
		||||
        $return = '?';
 | 
			
		||||
        $params = [];
 | 
			
		||||
        foreach ($this->parameters as $key => $value) {
 | 
			
		||||
            if ('page' === $key) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if ($value instanceof Carbon) {
 | 
			
		||||
                $params[$key] = $value->format('Y-m-d');
 | 
			
		||||
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            $params[$key] = $value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return.http_build_query($params);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final protected function getManager(): Manager
 | 
			
		||||
    {
 | 
			
		||||
        // create some objects:
 | 
			
		||||
        $manager = new Manager();
 | 
			
		||||
        $baseUrl = request()->getSchemeAndHttpHost().'/api/v1';
 | 
			
		||||
        $manager->setSerializer(new JsonApiSerializer($baseUrl));
 | 
			
		||||
 | 
			
		||||
        return $manager;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -70,8 +70,8 @@ class TransactionController extends Controller
 | 
			
		||||
        // to respond to what is in the $query.
 | 
			
		||||
        // this is OK because only one thing can be in the query at the moment.
 | 
			
		||||
        if ($this->isUpdateTransactionAccount($params)) {
 | 
			
		||||
            $original    = $this->repository->find((int) $params['where']['account_id']);
 | 
			
		||||
            $destination = $this->repository->find((int) $params['update']['account_id']);
 | 
			
		||||
            $original    = $this->repository->find((int)$params['where']['account_id']);
 | 
			
		||||
            $destination = $this->repository->find((int)$params['update']['account_id']);
 | 
			
		||||
 | 
			
		||||
            /** @var AccountDestroyService $service */
 | 
			
		||||
            $service     = app(AccountDestroyService::class);
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ class DestroyController extends Controller
 | 
			
		||||
        $allExceptAssets = [AccountType::BENEFICIARY, AccountType::CASH, AccountType::CREDITCARD, AccountType::DEFAULT, AccountType::EXPENSE, AccountType::IMPORT, AccountType::INITIAL_BALANCE, AccountType::LIABILITY_CREDIT, AccountType::RECONCILIATION, AccountType::REVENUE];
 | 
			
		||||
        $all             = [AccountType::ASSET, AccountType::BENEFICIARY, AccountType::CASH, AccountType::CREDITCARD, AccountType::DEBT, AccountType::DEFAULT, AccountType::EXPENSE, AccountType::IMPORT, AccountType::INITIAL_BALANCE, AccountType::LIABILITY_CREDIT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::RECONCILIATION];
 | 
			
		||||
        $liabilities     = [AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD];
 | 
			
		||||
        $transactions    = [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER, TransactionType::RECONCILIATION, TransactionType::OPENING_BALANCE];
 | 
			
		||||
        $transactions    = [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER, TransactionType::RECONCILIATION];
 | 
			
		||||
 | 
			
		||||
        match ($objects) {
 | 
			
		||||
            'budgets'                => $this->destroyBudgets(),
 | 
			
		||||
 
 | 
			
		||||
@@ -68,6 +68,32 @@ class ExportController extends Controller
 | 
			
		||||
        return $this->returnExport('accounts');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws FireflyException
 | 
			
		||||
     */
 | 
			
		||||
    private function returnExport(string $key): LaravelResponse
 | 
			
		||||
    {
 | 
			
		||||
        $date     = date('Y-m-d-H-i-s');
 | 
			
		||||
        $fileName = sprintf('%s-export-%s.csv', $date, $key);
 | 
			
		||||
        $data     = $this->exporter->export();
 | 
			
		||||
 | 
			
		||||
        /** @var LaravelResponse $response */
 | 
			
		||||
        $response = response($data[$key]);
 | 
			
		||||
        $response
 | 
			
		||||
            ->header('Content-Description', 'File Transfer')
 | 
			
		||||
            ->header('Content-Type', 'application/octet-stream')
 | 
			
		||||
            ->header('Content-Disposition', 'attachment; filename='.$fileName)
 | 
			
		||||
            ->header('Content-Transfer-Encoding', 'binary')
 | 
			
		||||
            ->header('Connection', 'Keep-Alive')
 | 
			
		||||
            ->header('Expires', '0')
 | 
			
		||||
            ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
 | 
			
		||||
            ->header('Pragma', 'public')
 | 
			
		||||
            ->header('Content-Length', (string)strlen($data[$key]))
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
        return $response;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This endpoint is documented at:
 | 
			
		||||
     * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/data/exportBills
 | 
			
		||||
@@ -189,30 +215,4 @@ class ExportController extends Controller
 | 
			
		||||
 | 
			
		||||
        return $this->returnExport('transactions');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws FireflyException
 | 
			
		||||
     */
 | 
			
		||||
    private function returnExport(string $key): LaravelResponse
 | 
			
		||||
    {
 | 
			
		||||
        $date     = date('Y-m-d-H-i-s');
 | 
			
		||||
        $fileName = sprintf('%s-export-%s.csv', $date, $key);
 | 
			
		||||
        $data     = $this->exporter->export();
 | 
			
		||||
 | 
			
		||||
        /** @var LaravelResponse $response */
 | 
			
		||||
        $response = response($data[$key]);
 | 
			
		||||
        $response
 | 
			
		||||
            ->header('Content-Description', 'File Transfer')
 | 
			
		||||
            ->header('Content-Type', 'application/octet-stream')
 | 
			
		||||
            ->header('Content-Disposition', 'attachment; filename='.$fileName)
 | 
			
		||||
            ->header('Content-Transfer-Encoding', 'binary')
 | 
			
		||||
            ->header('Connection', 'Keep-Alive')
 | 
			
		||||
            ->header('Expires', '0')
 | 
			
		||||
            ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
 | 
			
		||||
            ->header('Pragma', 'public')
 | 
			
		||||
            ->header('Content-Length', (string) strlen($data[$key]))
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
        return $response;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -75,28 +75,28 @@ class TagController extends Controller
 | 
			
		||||
        $genericSet = $collector->getExtractedJournals();
 | 
			
		||||
 | 
			
		||||
        foreach ($genericSet as $journal) {
 | 
			
		||||
            $currencyId        = (int) $journal['currency_id'];
 | 
			
		||||
            $foreignCurrencyId = (int) $journal['foreign_currency_id'];
 | 
			
		||||
            $currencyId        = (int)$journal['currency_id'];
 | 
			
		||||
            $foreignCurrencyId = (int)$journal['foreign_currency_id'];
 | 
			
		||||
 | 
			
		||||
            if (0 !== $currencyId) {
 | 
			
		||||
                $response[$currencyId] ??= [
 | 
			
		||||
                    'difference'       => '0',
 | 
			
		||||
                    'difference_float' => 0,
 | 
			
		||||
                    'currency_id'      => (string) $currencyId,
 | 
			
		||||
                    'currency_id'      => (string)$currencyId,
 | 
			
		||||
                    'currency_code'    => $journal['currency_code'],
 | 
			
		||||
                ];
 | 
			
		||||
                $response[$currencyId]['difference']       = bcadd($response[$currencyId]['difference'], $journal['amount']);
 | 
			
		||||
                $response[$currencyId]['difference_float'] = (float) $response[$currencyId]['difference']; // float but on purpose.
 | 
			
		||||
                $response[$currencyId]['difference_float'] = (float)$response[$currencyId]['difference']; // float but on purpose.
 | 
			
		||||
            }
 | 
			
		||||
            if (0 !== $foreignCurrencyId) {
 | 
			
		||||
                $response[$foreignCurrencyId] ??= [
 | 
			
		||||
                    'difference'       => '0',
 | 
			
		||||
                    'difference_float' => 0,
 | 
			
		||||
                    'currency_id'      => (string) $foreignCurrencyId,
 | 
			
		||||
                    'currency_id'      => (string)$foreignCurrencyId,
 | 
			
		||||
                    'currency_code'    => $journal['foreign_currency_code'],
 | 
			
		||||
                ];
 | 
			
		||||
                $response[$foreignCurrencyId]['difference']       = bcadd($response[$foreignCurrencyId]['difference'], $journal['foreign_amount']);
 | 
			
		||||
                $response[$foreignCurrencyId]['difference_float'] = (float) $response[$foreignCurrencyId]['difference']; // float but on purpose.
 | 
			
		||||
                $response[$foreignCurrencyId]['difference_float'] = (float)$response[$foreignCurrencyId]['difference']; // float but on purpose.
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -130,8 +130,8 @@ class TagController extends Controller
 | 
			
		||||
 | 
			
		||||
        /** @var array $journal */
 | 
			
		||||
        foreach ($genericSet as $journal) {
 | 
			
		||||
            $currencyId        = (int) $journal['currency_id'];
 | 
			
		||||
            $foreignCurrencyId = (int) $journal['foreign_currency_id'];
 | 
			
		||||
            $currencyId        = (int)$journal['currency_id'];
 | 
			
		||||
            $foreignCurrencyId = (int)$journal['foreign_currency_id'];
 | 
			
		||||
 | 
			
		||||
            /** @var array $tag */
 | 
			
		||||
            foreach ($journal['tags'] as $tag) {
 | 
			
		||||
@@ -142,15 +142,15 @@ class TagController extends Controller
 | 
			
		||||
                // on currency ID
 | 
			
		||||
                if (0 !== $currencyId) {
 | 
			
		||||
                    $response[$key] ??= [
 | 
			
		||||
                        'id'               => (string) $tagId,
 | 
			
		||||
                        'id'               => (string)$tagId,
 | 
			
		||||
                        'name'             => $tag['name'],
 | 
			
		||||
                        'difference'       => '0',
 | 
			
		||||
                        'difference_float' => 0,
 | 
			
		||||
                        'currency_id'      => (string) $currencyId,
 | 
			
		||||
                        'currency_id'      => (string)$currencyId,
 | 
			
		||||
                        'currency_code'    => $journal['currency_code'],
 | 
			
		||||
                    ];
 | 
			
		||||
                    $response[$key]['difference']       = bcadd($response[$key]['difference'], $journal['amount']);
 | 
			
		||||
                    $response[$key]['difference_float'] = (float) $response[$key]['difference']; // float but on purpose.
 | 
			
		||||
                    $response[$key]['difference_float'] = (float)$response[$key]['difference']; // float but on purpose.
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // on foreign ID
 | 
			
		||||
@@ -158,11 +158,11 @@ class TagController extends Controller
 | 
			
		||||
                    $response[$foreignKey]                     = $journal[$foreignKey] ?? [
 | 
			
		||||
                        'difference'       => '0',
 | 
			
		||||
                        'difference_float' => 0,
 | 
			
		||||
                        'currency_id'      => (string) $foreignCurrencyId,
 | 
			
		||||
                        'currency_id'      => (string)$foreignCurrencyId,
 | 
			
		||||
                        'currency_code'    => $journal['foreign_currency_code'],
 | 
			
		||||
                    ];
 | 
			
		||||
                    $response[$foreignKey]['difference']       = bcadd($response[$foreignKey]['difference'], $journal['foreign_amount']);
 | 
			
		||||
                    $response[$foreignKey]['difference_float'] = (float) $response[$foreignKey]['difference']; // float but on purpose.
 | 
			
		||||
                    $response[$foreignKey]['difference_float'] = (float)$response[$foreignKey]['difference']; // float but on purpose.
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ class DestroyController extends Controller
 | 
			
		||||
     */
 | 
			
		||||
    public function destroy(Attachment $attachment): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        if(true === auth()->user()->hasRole('demo')) {
 | 
			
		||||
        if (true === auth()->user()->hasRole('demo')) {
 | 
			
		||||
            Log::channel('audit')->warning(sprintf('Demo user tries to access attachment API in %s', __METHOD__));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException();
 | 
			
		||||
 
 | 
			
		||||
@@ -75,7 +75,7 @@ class ShowController extends Controller
 | 
			
		||||
     */
 | 
			
		||||
    public function download(Attachment $attachment): LaravelResponse
 | 
			
		||||
    {
 | 
			
		||||
        if(true === auth()->user()->hasRole('demo')) {
 | 
			
		||||
        if (true === auth()->user()->hasRole('demo')) {
 | 
			
		||||
            Log::channel('audit')->warning(sprintf('Demo user tries to access attachment API in %s', __METHOD__));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException();
 | 
			
		||||
@@ -123,7 +123,7 @@ class ShowController extends Controller
 | 
			
		||||
     */
 | 
			
		||||
    public function index(): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        if(true === auth()->user()->hasRole('demo')) {
 | 
			
		||||
        if (true === auth()->user()->hasRole('demo')) {
 | 
			
		||||
            Log::channel('audit')->warning(sprintf('Demo user tries to access attachment API in %s', __METHOD__));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException();
 | 
			
		||||
@@ -161,7 +161,7 @@ class ShowController extends Controller
 | 
			
		||||
     */
 | 
			
		||||
    public function show(Attachment $attachment): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        if(true === auth()->user()->hasRole('demo')) {
 | 
			
		||||
        if (true === auth()->user()->hasRole('demo')) {
 | 
			
		||||
            Log::channel('audit')->warning(sprintf('Demo user tries to access attachment API in %s', __METHOD__));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException();
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ class StoreController extends Controller
 | 
			
		||||
     */
 | 
			
		||||
    public function store(StoreRequest $request): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        if(true === auth()->user()->hasRole('demo')) {
 | 
			
		||||
        if (true === auth()->user()->hasRole('demo')) {
 | 
			
		||||
            Log::channel('audit')->warning(sprintf('Demo user tries to access attachment API in %s', __METHOD__));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException();
 | 
			
		||||
@@ -98,7 +98,7 @@ class StoreController extends Controller
 | 
			
		||||
     */
 | 
			
		||||
    public function upload(Request $request, Attachment $attachment): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        if(true === auth()->user()->hasRole('demo')) {
 | 
			
		||||
        if (true === auth()->user()->hasRole('demo')) {
 | 
			
		||||
            Log::channel('audit')->warning(sprintf('Demo user tries to access attachment API in %s', __METHOD__));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException();
 | 
			
		||||
@@ -112,7 +112,12 @@ class StoreController extends Controller
 | 
			
		||||
 | 
			
		||||
            return response()->json([], 422);
 | 
			
		||||
        }
 | 
			
		||||
        $helper->saveAttachmentFromApi($attachment, $body);
 | 
			
		||||
        $result = $helper->saveAttachmentFromApi($attachment, $body);
 | 
			
		||||
        if (false === $result) {
 | 
			
		||||
            app('log')->error('Could not save attachment from API.');
 | 
			
		||||
 | 
			
		||||
            return response()->json([], 422);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return response()->json([], 204);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,7 @@ class UpdateController extends Controller
 | 
			
		||||
     */
 | 
			
		||||
    public function update(UpdateRequest $request, Attachment $attachment): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        if(true === auth()->user()->hasRole('demo')) {
 | 
			
		||||
        if (true === auth()->user()->hasRole('demo')) {
 | 
			
		||||
            Log::channel('audit')->warning(sprintf('Demo user tries to access attachment API in %s', __METHOD__));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,7 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * auth.php
 | 
			
		||||
 * Copyright (c) 2019 james@firefly-iii.org
 | 
			
		||||
/*
 | 
			
		||||
 * ExpressionController.php
 | 
			
		||||
 * Copyright (c) 2024 Michael Thomas
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of Firefly III (https://github.com/firefly-iii).
 | 
			
		||||
 *
 | 
			
		||||
@@ -20,20 +19,29 @@
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * PLEASE DO NOT EDIT THIS FILE DIRECTLY.
 | 
			
		||||
 * YOUR CHANGES WILL BE OVERWRITTEN!
 | 
			
		||||
 * YOUR PR WITH CHANGES TO THIS FILE WILL BE REJECTED!
 | 
			
		||||
 *
 | 
			
		||||
 * GO TO CROWDIN TO FIX OR CHANGE TRANSLATIONS!
 | 
			
		||||
 *
 | 
			
		||||
 * https://crowdin.com/project/firefly-iii
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
    'failed'   => 'Въведените удостоверителни данни не съвпадат с нашите записи.',
 | 
			
		||||
    'throttle' => 'Твърде много опити за влизане. Опитайте се пак след :seconds секунди.',
 | 
			
		||||
];
 | 
			
		||||
namespace FireflyIII\Api\V1\Controllers\Models\Rule;
 | 
			
		||||
 | 
			
		||||
use FireflyIII\Api\V1\Controllers\Controller;
 | 
			
		||||
use FireflyIII\Api\V1\Requests\Models\Rule\ValidateExpressionRequest;
 | 
			
		||||
use Illuminate\Http\JsonResponse;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class ExpressionController
 | 
			
		||||
 */
 | 
			
		||||
class ExpressionController extends Controller
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * This endpoint is documented at:
 | 
			
		||||
     * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/rules/validateExpression
 | 
			
		||||
     *
 | 
			
		||||
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
 | 
			
		||||
     */
 | 
			
		||||
    public function validateExpression(ValidateExpressionRequest $request): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        return response()->json([
 | 
			
		||||
            'valid' => true,
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -69,9 +69,15 @@ class UpdateController extends Controller
 | 
			
		||||
     */
 | 
			
		||||
    public function update(UpdateRequest $request, TransactionGroup $transactionGroup): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        app('log')->debug('Now in update routine for transaction group!');
 | 
			
		||||
        app('log')->debug('Now in update routine for transaction group');
 | 
			
		||||
        $data             = $request->getAll();
 | 
			
		||||
 | 
			
		||||
        // Fixes 8750.
 | 
			
		||||
        $transactions     = $data['transactions'] ?? [];
 | 
			
		||||
        foreach ($transactions as $index => $info) {
 | 
			
		||||
            unset($data['transactions'][$index]['type']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $transactionGroup = $this->groupRepository->update($transactionGroup, $data);
 | 
			
		||||
        $manager          = $this->getManager();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,7 @@ use FireflyIII\Support\Http\Api\TransactionFilter;
 | 
			
		||||
use FireflyIII\Transformers\CurrencyTransformer;
 | 
			
		||||
use FireflyIII\User;
 | 
			
		||||
use Illuminate\Http\JsonResponse;
 | 
			
		||||
use Illuminate\Support\Facades\Log;
 | 
			
		||||
use League\Fractal\Resource\Item;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -164,6 +165,7 @@ class UpdateController extends Controller
 | 
			
		||||
    public function update(UpdateRequest $request, TransactionCurrency $currency): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        $data        = $request->getAll();
 | 
			
		||||
        Log::debug(__METHOD__, $data);
 | 
			
		||||
 | 
			
		||||
        /** @var User $user */
 | 
			
		||||
        $user        = auth()->user();
 | 
			
		||||
@@ -173,6 +175,11 @@ class UpdateController extends Controller
 | 
			
		||||
        if (array_key_exists('enabled', $data) && false === $data['enabled'] && 1 === count($set) && $set->first()->id === $currency->id) {
 | 
			
		||||
            return response()->json([], 409);
 | 
			
		||||
        }
 | 
			
		||||
        // second safety catch on currency disable.
 | 
			
		||||
        if (array_key_exists('enabled', $data) && false === $data['enabled'] && $this->repository->currencyInUse($currency)) {
 | 
			
		||||
            return response()->json([], 409);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $currency    = $this->repository->update($currency, $data);
 | 
			
		||||
 | 
			
		||||
        app('preferences')->mark();
 | 
			
		||||
 
 | 
			
		||||
@@ -118,23 +118,6 @@ class BasicController extends Controller
 | 
			
		||||
        return response()->json($return);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if date is outside session range.
 | 
			
		||||
     */
 | 
			
		||||
    protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference
 | 
			
		||||
    {
 | 
			
		||||
        $result = false;
 | 
			
		||||
        if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) {
 | 
			
		||||
            $result = true;
 | 
			
		||||
        }
 | 
			
		||||
        // start and end in the past? use $end
 | 
			
		||||
        if ($start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date)) {
 | 
			
		||||
            $result = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getBalanceInformation(Carbon $start, Carbon $end): array
 | 
			
		||||
    {
 | 
			
		||||
        // prep some arrays:
 | 
			
		||||
@@ -152,7 +135,7 @@ class BasicController extends Controller
 | 
			
		||||
 | 
			
		||||
        /** @var array $transactionJournal */
 | 
			
		||||
        foreach ($set as $transactionJournal) {
 | 
			
		||||
            $currencyId           = (int) $transactionJournal['currency_id'];
 | 
			
		||||
            $currencyId           = (int)$transactionJournal['currency_id'];
 | 
			
		||||
            $incomes[$currencyId] ??= '0';
 | 
			
		||||
            $incomes[$currencyId] = bcadd(
 | 
			
		||||
                $incomes[$currencyId],
 | 
			
		||||
@@ -170,7 +153,7 @@ class BasicController extends Controller
 | 
			
		||||
 | 
			
		||||
        /** @var array $transactionJournal */
 | 
			
		||||
        foreach ($set as $transactionJournal) {
 | 
			
		||||
            $currencyId            = (int) $transactionJournal['currency_id'];
 | 
			
		||||
            $currencyId            = (int)$transactionJournal['currency_id'];
 | 
			
		||||
            $expenses[$currencyId] ??= '0';
 | 
			
		||||
            $expenses[$currencyId] = bcadd($expenses[$currencyId], $transactionJournal['amount']);
 | 
			
		||||
            $sums[$currencyId]     ??= '0';
 | 
			
		||||
@@ -189,7 +172,7 @@ class BasicController extends Controller
 | 
			
		||||
                'key'                     => sprintf('balance-in-%s', $currency->code),
 | 
			
		||||
                'title'                   => trans('firefly.box_balance_in_currency', ['currency' => $currency->symbol]),
 | 
			
		||||
                'monetary_value'          => $sums[$currencyId] ?? '0',
 | 
			
		||||
                'currency_id'             => (string) $currency->id,
 | 
			
		||||
                'currency_id'             => (string)$currency->id,
 | 
			
		||||
                'currency_code'           => $currency->code,
 | 
			
		||||
                'currency_symbol'         => $currency->symbol,
 | 
			
		||||
                'currency_decimal_places' => $currency->decimal_places,
 | 
			
		||||
@@ -202,7 +185,7 @@ class BasicController extends Controller
 | 
			
		||||
                'key'                     => sprintf('spent-in-%s', $currency->code),
 | 
			
		||||
                'title'                   => trans('firefly.box_spent_in_currency', ['currency' => $currency->symbol]),
 | 
			
		||||
                'monetary_value'          => $expenses[$currencyId] ?? '0',
 | 
			
		||||
                'currency_id'             => (string) $currency->id,
 | 
			
		||||
                'currency_id'             => (string)$currency->id,
 | 
			
		||||
                'currency_code'           => $currency->code,
 | 
			
		||||
                'currency_symbol'         => $currency->symbol,
 | 
			
		||||
                'currency_decimal_places' => $currency->decimal_places,
 | 
			
		||||
@@ -214,7 +197,7 @@ class BasicController extends Controller
 | 
			
		||||
                'key'                     => sprintf('earned-in-%s', $currency->code),
 | 
			
		||||
                'title'                   => trans('firefly.box_earned_in_currency', ['currency' => $currency->symbol]),
 | 
			
		||||
                'monetary_value'          => $incomes[$currencyId] ?? '0',
 | 
			
		||||
                'currency_id'             => (string) $currency->id,
 | 
			
		||||
                'currency_id'             => (string)$currency->id,
 | 
			
		||||
                'currency_code'           => $currency->code,
 | 
			
		||||
                'currency_symbol'         => $currency->symbol,
 | 
			
		||||
                'currency_decimal_places' => $currency->decimal_places,
 | 
			
		||||
@@ -248,7 +231,7 @@ class BasicController extends Controller
 | 
			
		||||
                'key'                     => sprintf('bills-paid-in-%s', $info['code']),
 | 
			
		||||
                'title'                   => trans('firefly.box_bill_paid_in_currency', ['currency' => $info['symbol']]),
 | 
			
		||||
                'monetary_value'          => $amount,
 | 
			
		||||
                'currency_id'             => (string) $info['id'],
 | 
			
		||||
                'currency_id'             => (string)$info['id'],
 | 
			
		||||
                'currency_code'           => $info['code'],
 | 
			
		||||
                'currency_symbol'         => $info['symbol'],
 | 
			
		||||
                'currency_decimal_places' => $info['decimal_places'],
 | 
			
		||||
@@ -267,7 +250,7 @@ class BasicController extends Controller
 | 
			
		||||
                'key'                     => sprintf('bills-unpaid-in-%s', $info['code']),
 | 
			
		||||
                'title'                   => trans('firefly.box_bill_unpaid_in_currency', ['currency' => $info['symbol']]),
 | 
			
		||||
                'monetary_value'          => $amount,
 | 
			
		||||
                'currency_id'             => (string) $info['id'],
 | 
			
		||||
                'currency_id'             => (string)$info['id'],
 | 
			
		||||
                'currency_code'           => $info['code'],
 | 
			
		||||
                'currency_symbol'         => $info['symbol'],
 | 
			
		||||
                'currency_decimal_places' => $info['decimal_places'],
 | 
			
		||||
@@ -294,21 +277,21 @@ class BasicController extends Controller
 | 
			
		||||
 | 
			
		||||
        foreach ($spent as $row) {
 | 
			
		||||
            // either an amount was budgeted or 0 is available.
 | 
			
		||||
            $amount          = (string) ($available[$row['currency_id']] ?? '0');
 | 
			
		||||
            $amount          = (string)($available[$row['currency_id']] ?? '0');
 | 
			
		||||
            $spentInCurrency = $row['sum'];
 | 
			
		||||
            $leftToSpend     = bcadd($amount, $spentInCurrency);
 | 
			
		||||
 | 
			
		||||
            $days            = $today->diffInDays($end) + 1;
 | 
			
		||||
            $days            = (int)$today->diffInDays($end, true) + 1;
 | 
			
		||||
            $perDay          = '0';
 | 
			
		||||
            if (0 !== $days && bccomp($leftToSpend, '0') > -1) {
 | 
			
		||||
                $perDay = bcdiv($leftToSpend, (string) $days);
 | 
			
		||||
                $perDay = bcdiv($leftToSpend, (string)$days);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $return[]        = [
 | 
			
		||||
                'key'                     => sprintf('left-to-spend-in-%s', $row['currency_code']),
 | 
			
		||||
                'title'                   => trans('firefly.box_left_to_spend_in_currency', ['currency' => $row['currency_symbol']]),
 | 
			
		||||
                'monetary_value'          => $leftToSpend,
 | 
			
		||||
                'currency_id'             => (string) $row['currency_id'],
 | 
			
		||||
                'currency_id'             => (string)$row['currency_id'],
 | 
			
		||||
                'currency_code'           => $row['currency_code'],
 | 
			
		||||
                'currency_symbol'         => $row['currency_symbol'],
 | 
			
		||||
                'currency_decimal_places' => $row['currency_decimal_places'],
 | 
			
		||||
@@ -368,7 +351,7 @@ class BasicController extends Controller
 | 
			
		||||
                'key'                     => sprintf('net-worth-in-%s', $data['currency_code']),
 | 
			
		||||
                'title'                   => trans('firefly.box_net_worth_in_currency', ['currency' => $data['currency_symbol']]),
 | 
			
		||||
                'monetary_value'          => $amount,
 | 
			
		||||
                'currency_id'             => (string) $data['currency_id'],
 | 
			
		||||
                'currency_id'             => (string)$data['currency_id'],
 | 
			
		||||
                'currency_code'           => $data['currency_code'],
 | 
			
		||||
                'currency_symbol'         => $data['currency_symbol'],
 | 
			
		||||
                'currency_decimal_places' => $data['currency_decimal_places'],
 | 
			
		||||
@@ -380,4 +363,21 @@ class BasicController extends Controller
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if date is outside session range.
 | 
			
		||||
     */
 | 
			
		||||
    protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference
 | 
			
		||||
    {
 | 
			
		||||
        $result = false;
 | 
			
		||||
        if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) {
 | 
			
		||||
            $result = true;
 | 
			
		||||
        }
 | 
			
		||||
        // start and end in the past? use $end
 | 
			
		||||
        if ($start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date)) {
 | 
			
		||||
            $result = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $result;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,7 @@ class AboutController extends Controller
 | 
			
		||||
        $data
 | 
			
		||||
                       = [
 | 
			
		||||
                           'version'     => config('firefly.version'),
 | 
			
		||||
                           'api_version' => config('firefly.api_version'),
 | 
			
		||||
                           'api_version' => config('firefly.version'),
 | 
			
		||||
                           'php_version' => $phpVersion,
 | 
			
		||||
                           'os'          => $phpOs,
 | 
			
		||||
                           'driver'      => $currentDriver,
 | 
			
		||||
 
 | 
			
		||||
@@ -88,6 +88,35 @@ class ConfigurationController extends Controller
 | 
			
		||||
        return response()->json($return);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all config values.
 | 
			
		||||
     */
 | 
			
		||||
    private function getDynamicConfiguration(): array
 | 
			
		||||
    {
 | 
			
		||||
        $isDemoSite  = app('fireflyconfig')->get('is_demo_site');
 | 
			
		||||
        $updateCheck = app('fireflyconfig')->get('permission_update_check');
 | 
			
		||||
        $lastCheck   = app('fireflyconfig')->get('last_update_check');
 | 
			
		||||
        $singleUser  = app('fireflyconfig')->get('single_user_mode');
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            'is_demo_site'            => $isDemoSite?->data,
 | 
			
		||||
            'permission_update_check' => null === $updateCheck ? null : (int)$updateCheck->data,
 | 
			
		||||
            'last_update_check'       => null === $lastCheck ? null : (int)$lastCheck->data,
 | 
			
		||||
            'single_user_mode'        => $singleUser?->data,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getStaticConfiguration(): array
 | 
			
		||||
    {
 | 
			
		||||
        $list   = EitherConfigKey::$static;
 | 
			
		||||
        $return = [];
 | 
			
		||||
        foreach ($list as $key) {
 | 
			
		||||
            $return[$key] = config($key);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This endpoint is documented at:
 | 
			
		||||
     * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/configuration/getSingleConfiguration
 | 
			
		||||
@@ -145,33 +174,4 @@ class ConfigurationController extends Controller
 | 
			
		||||
 | 
			
		||||
        return response()->json(['data' => $data])->header('Content-Type', self::CONTENT_TYPE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all config values.
 | 
			
		||||
     */
 | 
			
		||||
    private function getDynamicConfiguration(): array
 | 
			
		||||
    {
 | 
			
		||||
        $isDemoSite  = app('fireflyconfig')->get('is_demo_site');
 | 
			
		||||
        $updateCheck = app('fireflyconfig')->get('permission_update_check');
 | 
			
		||||
        $lastCheck   = app('fireflyconfig')->get('last_update_check');
 | 
			
		||||
        $singleUser  = app('fireflyconfig')->get('single_user_mode');
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            'is_demo_site'            => $isDemoSite?->data,
 | 
			
		||||
            'permission_update_check' => null === $updateCheck ? null : (int)$updateCheck->data,
 | 
			
		||||
            'last_update_check'       => null === $lastCheck ? null : (int)$lastCheck->data,
 | 
			
		||||
            'single_user_mode'        => $singleUser?->data,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getStaticConfiguration(): array
 | 
			
		||||
    {
 | 
			
		||||
        $list   = EitherConfigKey::$static;
 | 
			
		||||
        $return = [];
 | 
			
		||||
        foreach ($list as $key) {
 | 
			
		||||
            $return[$key] = config($key);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ use FireflyIII\Models\Preference;
 | 
			
		||||
use FireflyIII\Transformers\PreferenceTransformer;
 | 
			
		||||
use Illuminate\Http\JsonResponse;
 | 
			
		||||
use Illuminate\Pagination\LengthAwarePaginator;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
 | 
			
		||||
use League\Fractal\Resource\Collection as FractalCollection;
 | 
			
		||||
use League\Fractal\Resource\Item;
 | 
			
		||||
@@ -84,6 +85,10 @@ class PreferencesController extends Controller
 | 
			
		||||
    {
 | 
			
		||||
        $manager     = $this->getManager();
 | 
			
		||||
 | 
			
		||||
        if ('currencyPreference' === $preference->name) {
 | 
			
		||||
            throw new FireflyException('Please use api/v1/currencies/default instead.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /** @var PreferenceTransformer $transformer */
 | 
			
		||||
        $transformer = app(PreferenceTransformer::class);
 | 
			
		||||
        $transformer->setParameters($this->parameters);
 | 
			
		||||
@@ -93,6 +98,34 @@ class PreferencesController extends Controller
 | 
			
		||||
        return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * TODO This endpoint is not documented.
 | 
			
		||||
     *
 | 
			
		||||
     * Return a single preference by name.
 | 
			
		||||
     *
 | 
			
		||||
     * @param Collection<int, Preference> $collection
 | 
			
		||||
     */
 | 
			
		||||
    public function showList(Collection $collection): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        $manager     = $this->getManager();
 | 
			
		||||
        $count       = $collection->count();
 | 
			
		||||
        $pageSize    = $this->parameters->get('limit');
 | 
			
		||||
        $preferences = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
 | 
			
		||||
 | 
			
		||||
        // make paginator:
 | 
			
		||||
        $paginator   = new LengthAwarePaginator($preferences, $count, $pageSize, $this->parameters->get('page'));
 | 
			
		||||
        $paginator->setPath(route('api.v1.preferences.show-list').$this->buildParams());
 | 
			
		||||
 | 
			
		||||
        /** @var PreferenceTransformer $transformer */
 | 
			
		||||
        $transformer = app(PreferenceTransformer::class);
 | 
			
		||||
        $transformer->setParameters($this->parameters);
 | 
			
		||||
 | 
			
		||||
        $resource    = new FractalCollection($preferences, $transformer, self::RESOURCE_KEY);
 | 
			
		||||
        $resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
 | 
			
		||||
 | 
			
		||||
        return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This endpoint is documented at:
 | 
			
		||||
     * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/preferences/storePreference
 | 
			
		||||
@@ -103,6 +136,11 @@ class PreferencesController extends Controller
 | 
			
		||||
    {
 | 
			
		||||
        $manager     = $this->getManager();
 | 
			
		||||
        $data        = $request->getAll();
 | 
			
		||||
 | 
			
		||||
        if ('currencyPreference' === $data['name']) {
 | 
			
		||||
            throw new FireflyException('Please use api/v1/currencies/default instead.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $pref        = app('preferences')->set($data['name'], $data['data']);
 | 
			
		||||
 | 
			
		||||
        /** @var PreferenceTransformer $transformer */
 | 
			
		||||
@@ -122,6 +160,10 @@ class PreferencesController extends Controller
 | 
			
		||||
     */
 | 
			
		||||
    public function update(PreferenceUpdateRequest $request, Preference $preference): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        if ('currencyPreference' === $preference->name) {
 | 
			
		||||
            throw new FireflyException('Please use api/v1/currencies/default instead.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $manager     = $this->getManager();
 | 
			
		||||
        $data        = $request->getAll();
 | 
			
		||||
        $pref        = app('preferences')->set($preference->name, $data['data']);
 | 
			
		||||
 
 | 
			
		||||
@@ -70,7 +70,7 @@ class AttemptController extends Controller
 | 
			
		||||
        if ($message->webhook_id !== $webhook->id) {
 | 
			
		||||
            throw new FireflyException('200040: Webhook and webhook message are no match');
 | 
			
		||||
        }
 | 
			
		||||
        if(false === config('firefly.allow_webhooks')) {
 | 
			
		||||
        if (false === config('firefly.allow_webhooks')) {
 | 
			
		||||
            Log::channel('audit')->warning(sprintf('User lists webhook attempts of webhook #%d and message #%d, but webhooks are DISABLED.', $webhook->id, $message->id));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException('Webhooks are not enabled.');
 | 
			
		||||
@@ -114,7 +114,7 @@ class AttemptController extends Controller
 | 
			
		||||
            throw new FireflyException('200041: Webhook message and webhook attempt are no match');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(false === config('firefly.allow_webhooks')) {
 | 
			
		||||
        if (false === config('firefly.allow_webhooks')) {
 | 
			
		||||
            Log::channel('audit')->warning(sprintf('User views single webhook attempt #%d of webhook #%d and message #%d, but webhooks are DISABLED', $attempt->id, $webhook->id, $message->id));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException('Webhooks are not enabled.');
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ class DestroyController extends Controller
 | 
			
		||||
     */
 | 
			
		||||
    public function destroy(Webhook $webhook): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        if(false === config('firefly.allow_webhooks')) {
 | 
			
		||||
        if (false === config('firefly.allow_webhooks')) {
 | 
			
		||||
            Log::channel('audit')->warning(sprintf('User tries to destroy webhook #%d. but webhooks are DISABLED.', $webhook->id));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException('Webhooks are not enabled.');
 | 
			
		||||
@@ -120,7 +120,7 @@ class DestroyController extends Controller
 | 
			
		||||
            throw new FireflyException('200040: Webhook and webhook message are no match');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(false === config('firefly.allow_webhooks')) {
 | 
			
		||||
        if (false === config('firefly.allow_webhooks')) {
 | 
			
		||||
            Log::channel('audit')->warning(sprintf('User tries to destroy webhook #%d, message #%d, but webhooks are DISABLED.', $webhook->id, $message->id));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException('Webhooks are not enabled.');
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ class MessageController extends Controller
 | 
			
		||||
     */
 | 
			
		||||
    public function index(Webhook $webhook): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        if(false === config('firefly.allow_webhooks')) {
 | 
			
		||||
        if (false === config('firefly.allow_webhooks')) {
 | 
			
		||||
            Log::channel('audit')->warning(sprintf('User tries to view messages of webhook #%d, but webhooks are DISABLED.', $webhook->id));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException('Webhooks are not enabled.');
 | 
			
		||||
@@ -106,7 +106,7 @@ class MessageController extends Controller
 | 
			
		||||
        if ($message->webhook_id !== $webhook->id) {
 | 
			
		||||
            throw new FireflyException('200040: Webhook and webhook message are no match');
 | 
			
		||||
        }
 | 
			
		||||
        if(false === config('firefly.allow_webhooks')) {
 | 
			
		||||
        if (false === config('firefly.allow_webhooks')) {
 | 
			
		||||
            Log::channel('audit')->warning(sprintf('User tries to view message #%d of webhook #%d, but webhooks are DISABLED.', $message->id, $webhook->id));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException('Webhooks are not enabled.');
 | 
			
		||||
 
 | 
			
		||||
@@ -71,7 +71,7 @@ class ShowController extends Controller
 | 
			
		||||
     */
 | 
			
		||||
    public function index(): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        if(false === config('firefly.allow_webhooks')) {
 | 
			
		||||
        if (false === config('firefly.allow_webhooks')) {
 | 
			
		||||
            Log::channel('audit')->info('User tries to view all webhooks, but webhooks are DISABLED.');
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException('Webhooks are not enabled.');
 | 
			
		||||
@@ -106,7 +106,7 @@ class ShowController extends Controller
 | 
			
		||||
     */
 | 
			
		||||
    public function show(Webhook $webhook): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        if(false === config('firefly.allow_webhooks')) {
 | 
			
		||||
        if (false === config('firefly.allow_webhooks')) {
 | 
			
		||||
            Log::channel('audit')->info(sprintf('User tries to view webhook #%d, but webhooks are DISABLED.', $webhook->id));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException('Webhooks are not enabled.');
 | 
			
		||||
@@ -131,7 +131,7 @@ class ShowController extends Controller
 | 
			
		||||
     */
 | 
			
		||||
    public function triggerTransaction(Webhook $webhook, TransactionGroup $group): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        if(false === config('firefly.allow_webhooks')) {
 | 
			
		||||
        if (false === config('firefly.allow_webhooks')) {
 | 
			
		||||
            Log::channel('audit')->info(sprintf('User tries to trigger webhook #%d on transaction group #%d, but webhooks are DISABLED.', $webhook->id, $group->id));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException('Webhooks are not enabled.');
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ class StoreController extends Controller
 | 
			
		||||
    public function store(CreateRequest $request): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        $data        = $request->getData();
 | 
			
		||||
        if(false === config('firefly.allow_webhooks')) {
 | 
			
		||||
        if (false === config('firefly.allow_webhooks')) {
 | 
			
		||||
            Log::channel('audit')->info('User tries to store new webhook, but webhooks are DISABLED.', $data);
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException('Webhooks are not enabled.');
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,7 @@ class SubmitController extends Controller
 | 
			
		||||
     */
 | 
			
		||||
    public function submit(Webhook $webhook): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        if(false === config('firefly.allow_webhooks')) {
 | 
			
		||||
        if (false === config('firefly.allow_webhooks')) {
 | 
			
		||||
            Log::channel('audit')->info(sprintf('User tries to submit webhook #%d, but webhooks are DISABLED.', $webhook->id));
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException('Webhooks are not enabled.');
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ class UpdateController extends Controller
 | 
			
		||||
    public function update(Webhook $webhook, UpdateRequest $request): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        $data        = $request->getData();
 | 
			
		||||
        if(false === config('firefly.allow_webhooks')) {
 | 
			
		||||
        if (false === config('firefly.allow_webhooks')) {
 | 
			
		||||
            Log::channel('audit')->info(sprintf('User tries to update webhook #%d, but webhooks are DISABLED.', $webhook->id), $data);
 | 
			
		||||
 | 
			
		||||
            throw new NotFoundHttpException('Webhooks are not enabled.');
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@ class MoveTransactionsRequest extends FormRequest
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -83,12 +83,12 @@ class MoveTransactionsRequest extends FormRequest
 | 
			
		||||
        $data                = $validator->getData();
 | 
			
		||||
        $repository          = app(AccountRepositoryInterface::class);
 | 
			
		||||
        $repository->setUser(auth()->user());
 | 
			
		||||
        $original            = $repository->find((int) $data['original_account']);
 | 
			
		||||
        $destination         = $repository->find((int) $data['destination_account']);
 | 
			
		||||
        $original            = $repository->find((int)$data['original_account']);
 | 
			
		||||
        $destination         = $repository->find((int)$data['destination_account']);
 | 
			
		||||
 | 
			
		||||
        // not the same type:
 | 
			
		||||
        if ($original->accountType->type !== $destination->accountType->type) {
 | 
			
		||||
            $validator->errors()->add('title', (string) trans('validation.same_account_type'));
 | 
			
		||||
            $validator->errors()->add('title', (string)trans('validation.same_account_type'));
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
@@ -98,7 +98,7 @@ class MoveTransactionsRequest extends FormRequest
 | 
			
		||||
 | 
			
		||||
        // check different scenario's.
 | 
			
		||||
        if (null === $originalCurrency xor null === $destinationCurrency) {
 | 
			
		||||
            $validator->errors()->add('title', (string) trans('validation.same_account_currency'));
 | 
			
		||||
            $validator->errors()->add('title', (string)trans('validation.same_account_currency'));
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
@@ -107,7 +107,7 @@ class MoveTransactionsRequest extends FormRequest
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if ($originalCurrency->code !== $destinationCurrency->code) {
 | 
			
		||||
            $validator->errors()->add('title', (string) trans('validation.same_account_currency'));
 | 
			
		||||
            $validator->errors()->add('title', (string)trans('validation.same_account_currency'));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@ class TransactionRequest extends FormRequest
 | 
			
		||||
                $this->validateTransactionQuery($validator);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,9 @@ class DateRequest extends FormRequest
 | 
			
		||||
    {
 | 
			
		||||
        $start = $this->getCarbonDate('start');
 | 
			
		||||
        $end   = $this->getCarbonDate('end');
 | 
			
		||||
        if($start->diffInYears($end) > 5) {
 | 
			
		||||
        $start->startOfDay();
 | 
			
		||||
        $end->endOfDay();
 | 
			
		||||
        if ($start->diffInYears($end, true) > 5) {
 | 
			
		||||
            throw new FireflyException('Date range out of range.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -78,6 +78,25 @@ class GenericRequest extends FormRequest
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function parseAccounts(): void
 | 
			
		||||
    {
 | 
			
		||||
        if (0 !== $this->accounts->count()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $repository = app(AccountRepositoryInterface::class);
 | 
			
		||||
        $repository->setUser(auth()->user());
 | 
			
		||||
        $array      = $this->get('accounts');
 | 
			
		||||
        if (is_array($array)) {
 | 
			
		||||
            foreach ($array as $accountId) {
 | 
			
		||||
                $accountId = (int)$accountId;
 | 
			
		||||
                $account   = $repository->find($accountId);
 | 
			
		||||
                if (null !== $account) {
 | 
			
		||||
                    $this->accounts->push($account);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getBills(): Collection
 | 
			
		||||
    {
 | 
			
		||||
        $this->parseBills();
 | 
			
		||||
@@ -85,6 +104,25 @@ class GenericRequest extends FormRequest
 | 
			
		||||
        return $this->bills;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function parseBills(): void
 | 
			
		||||
    {
 | 
			
		||||
        if (0 !== $this->bills->count()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $repository = app(BillRepositoryInterface::class);
 | 
			
		||||
        $repository->setUser(auth()->user());
 | 
			
		||||
        $array      = $this->get('bills');
 | 
			
		||||
        if (is_array($array)) {
 | 
			
		||||
            foreach ($array as $billId) {
 | 
			
		||||
                $billId = (int)$billId;
 | 
			
		||||
                $bill   = $repository->find($billId);
 | 
			
		||||
                if (null !== $bill) {
 | 
			
		||||
                    $this->bills->push($bill);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getBudgets(): Collection
 | 
			
		||||
    {
 | 
			
		||||
        $this->parseBudgets();
 | 
			
		||||
@@ -92,6 +130,25 @@ class GenericRequest extends FormRequest
 | 
			
		||||
        return $this->budgets;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function parseBudgets(): void
 | 
			
		||||
    {
 | 
			
		||||
        if (0 !== $this->budgets->count()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $repository = app(BudgetRepositoryInterface::class);
 | 
			
		||||
        $repository->setUser(auth()->user());
 | 
			
		||||
        $array      = $this->get('budgets');
 | 
			
		||||
        if (is_array($array)) {
 | 
			
		||||
            foreach ($array as $budgetId) {
 | 
			
		||||
                $budgetId = (int)$budgetId;
 | 
			
		||||
                $budget   = $repository->find($budgetId);
 | 
			
		||||
                if (null !== $budget) {
 | 
			
		||||
                    $this->budgets->push($budget);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getCategories(): Collection
 | 
			
		||||
    {
 | 
			
		||||
        $this->parseCategories();
 | 
			
		||||
@@ -99,6 +156,25 @@ class GenericRequest extends FormRequest
 | 
			
		||||
        return $this->categories;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function parseCategories(): void
 | 
			
		||||
    {
 | 
			
		||||
        if (0 !== $this->categories->count()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $repository = app(CategoryRepositoryInterface::class);
 | 
			
		||||
        $repository->setUser(auth()->user());
 | 
			
		||||
        $array      = $this->get('categories');
 | 
			
		||||
        if (is_array($array)) {
 | 
			
		||||
            foreach ($array as $categoryId) {
 | 
			
		||||
                $categoryId = (int)$categoryId;
 | 
			
		||||
                $category   = $repository->find($categoryId);
 | 
			
		||||
                if (null !== $category) {
 | 
			
		||||
                    $this->categories->push($category);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getEnd(): Carbon
 | 
			
		||||
    {
 | 
			
		||||
        $date = $this->getCarbonDate('end');
 | 
			
		||||
@@ -154,100 +230,6 @@ class GenericRequest extends FormRequest
 | 
			
		||||
        return $this->tags;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The rules that the incoming request must be matched against.
 | 
			
		||||
     */
 | 
			
		||||
    public function rules(): array
 | 
			
		||||
    {
 | 
			
		||||
        // this is cheating, but it works to initialize the collections.
 | 
			
		||||
        $this->accounts   = new Collection();
 | 
			
		||||
        $this->budgets    = new Collection();
 | 
			
		||||
        $this->categories = new Collection();
 | 
			
		||||
        $this->bills      = new Collection();
 | 
			
		||||
        $this->tags       = new Collection();
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            'start' => 'required|date',
 | 
			
		||||
            'end'   => 'required|date|after_or_equal:start',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function parseAccounts(): void
 | 
			
		||||
    {
 | 
			
		||||
        if (0 !== $this->accounts->count()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $repository = app(AccountRepositoryInterface::class);
 | 
			
		||||
        $repository->setUser(auth()->user());
 | 
			
		||||
        $array      = $this->get('accounts');
 | 
			
		||||
        if (is_array($array)) {
 | 
			
		||||
            foreach ($array as $accountId) {
 | 
			
		||||
                $accountId = (int)$accountId;
 | 
			
		||||
                $account   = $repository->find($accountId);
 | 
			
		||||
                if (null !== $account) {
 | 
			
		||||
                    $this->accounts->push($account);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function parseBills(): void
 | 
			
		||||
    {
 | 
			
		||||
        if (0 !== $this->bills->count()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $repository = app(BillRepositoryInterface::class);
 | 
			
		||||
        $repository->setUser(auth()->user());
 | 
			
		||||
        $array      = $this->get('bills');
 | 
			
		||||
        if (is_array($array)) {
 | 
			
		||||
            foreach ($array as $billId) {
 | 
			
		||||
                $billId = (int)$billId;
 | 
			
		||||
                $bill   = $repository->find($billId);
 | 
			
		||||
                if (null !== $bill) {
 | 
			
		||||
                    $this->bills->push($bill);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function parseBudgets(): void
 | 
			
		||||
    {
 | 
			
		||||
        if (0 !== $this->budgets->count()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $repository = app(BudgetRepositoryInterface::class);
 | 
			
		||||
        $repository->setUser(auth()->user());
 | 
			
		||||
        $array      = $this->get('budgets');
 | 
			
		||||
        if (is_array($array)) {
 | 
			
		||||
            foreach ($array as $budgetId) {
 | 
			
		||||
                $budgetId = (int)$budgetId;
 | 
			
		||||
                $budget   = $repository->find($budgetId);
 | 
			
		||||
                if (null !== $budget) {
 | 
			
		||||
                    $this->budgets->push($budget);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function parseCategories(): void
 | 
			
		||||
    {
 | 
			
		||||
        if (0 !== $this->categories->count()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $repository = app(CategoryRepositoryInterface::class);
 | 
			
		||||
        $repository->setUser(auth()->user());
 | 
			
		||||
        $array      = $this->get('categories');
 | 
			
		||||
        if (is_array($array)) {
 | 
			
		||||
            foreach ($array as $categoryId) {
 | 
			
		||||
                $categoryId = (int)$categoryId;
 | 
			
		||||
                $category   = $repository->find($categoryId);
 | 
			
		||||
                if (null !== $category) {
 | 
			
		||||
                    $this->categories->push($category);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function parseTags(): void
 | 
			
		||||
    {
 | 
			
		||||
        if (0 !== $this->tags->count()) {
 | 
			
		||||
@@ -266,4 +248,22 @@ class GenericRequest extends FormRequest
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The rules that the incoming request must be matched against.
 | 
			
		||||
     */
 | 
			
		||||
    public function rules(): array
 | 
			
		||||
    {
 | 
			
		||||
        // this is cheating, but it works to initialize the collections.
 | 
			
		||||
        $this->accounts   = new Collection();
 | 
			
		||||
        $this->budgets    = new Collection();
 | 
			
		||||
        $this->categories = new Collection();
 | 
			
		||||
        $this->bills      = new Collection();
 | 
			
		||||
        $this->tags       = new Collection();
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            'start' => 'required|date',
 | 
			
		||||
            'end'   => 'required|date|after_or_equal:start',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,7 @@ class StoreRequest extends FormRequest
 | 
			
		||||
            'order'                   => $this->convertInteger('order'),
 | 
			
		||||
            'currency_code'           => $this->convertString('currency_code'),
 | 
			
		||||
            'virtual_balance'         => $this->convertString('virtual_balance'),
 | 
			
		||||
            'iban'                    => $this->convertString('iban'),
 | 
			
		||||
            'iban'                    => $this->convertIban('iban'),
 | 
			
		||||
            'BIC'                     => $this->convertString('bic'),
 | 
			
		||||
            'account_number'          => $this->convertString('account_number'),
 | 
			
		||||
            'account_role'            => $this->convertString('account_role'),
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,7 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
            'include_net_worth'       => ['include_net_worth', 'boolean'],
 | 
			
		||||
            'account_type_name'       => ['type', 'convertString'],
 | 
			
		||||
            'virtual_balance'         => ['virtual_balance', 'convertString'],
 | 
			
		||||
            'iban'                    => ['iban', 'convertString'],
 | 
			
		||||
            'iban'                    => ['iban', 'convertIban'],
 | 
			
		||||
            'BIC'                     => ['bic', 'convertString'],
 | 
			
		||||
            'account_number'          => ['account_number', 'convertString'],
 | 
			
		||||
            'account_role'            => ['account_role', 'convertString'],
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ class StoreRequest extends FormRequest
 | 
			
		||||
        $models = config('firefly.valid_attachment_models');
 | 
			
		||||
        $models = array_map(
 | 
			
		||||
            static function (string $className) {
 | 
			
		||||
                return str_replace('FireflyIII\\Models\\', '', $className);
 | 
			
		||||
                return str_replace('FireflyIII\Models\\', '', $className);
 | 
			
		||||
            },
 | 
			
		||||
            $models
 | 
			
		||||
        );
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
        $models = config('firefly.valid_attachment_models');
 | 
			
		||||
        $models = array_map(
 | 
			
		||||
            static function (string $className) {
 | 
			
		||||
                return str_replace('FireflyIII\\Models\\', '', $className);
 | 
			
		||||
                return str_replace('FireflyIII\Models\\', '', $className);
 | 
			
		||||
            },
 | 
			
		||||
            $models
 | 
			
		||||
        );
 | 
			
		||||
 
 | 
			
		||||
@@ -88,7 +88,7 @@ class Request extends FormRequest
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -79,12 +79,12 @@ class StoreRequest extends FormRequest
 | 
			
		||||
            'currency_id'    => 'numeric|exists:transaction_currencies,id',
 | 
			
		||||
            'currency_code'  => 'min:3|max:51|exists:transaction_currencies,code',
 | 
			
		||||
            'date'           => 'date|required',
 | 
			
		||||
            'end_date'       => 'date|after:date',
 | 
			
		||||
            'extension_date' => 'date|after:date',
 | 
			
		||||
            'end_date'       => 'nullable|date|after:date',
 | 
			
		||||
            'extension_date' => 'nullable|date|after:date',
 | 
			
		||||
            'repeat_freq'    => 'in:weekly,monthly,quarterly,half-year,yearly|required',
 | 
			
		||||
            'skip'           => 'min:0|max:31|numeric',
 | 
			
		||||
            'active'         => [new IsBoolean()],
 | 
			
		||||
            'notes'          => 'min:1|max:32768',
 | 
			
		||||
            'notes'          => 'nullable|min:1|max:32768',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -104,7 +104,7 @@ class StoreRequest extends FormRequest
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -109,7 +109,7 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -92,7 +92,7 @@ class StoreRequest extends FormRequest
 | 
			
		||||
                $this->validateAutoBudgetAmount($validator);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -104,7 +104,7 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
                $this->validateAutoBudgetAmount($validator);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -83,12 +83,12 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
                    $start = new Carbon($data['start']);
 | 
			
		||||
                    $end   = new Carbon($data['end']);
 | 
			
		||||
                    if ($end->isBefore($start)) {
 | 
			
		||||
                        $validator->errors()->add('end', (string) trans('validation.date_after'));
 | 
			
		||||
                        $validator->errors()->add('end', (string)trans('validation.date_after'));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -73,6 +73,65 @@ class StoreRequest extends FormRequest
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the transaction data as it is found in the submitted data. It's a complex method according to code
 | 
			
		||||
     * standards but it just has a lot of ??-statements because of the fields that may or may not exist.
 | 
			
		||||
     */
 | 
			
		||||
    private function getTransactionData(): array
 | 
			
		||||
    {
 | 
			
		||||
        $return       = [];
 | 
			
		||||
 | 
			
		||||
        // transaction data:
 | 
			
		||||
        /** @var null|array $transactions */
 | 
			
		||||
        $transactions = $this->get('transactions');
 | 
			
		||||
        if (null === $transactions) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /** @var array $transaction */
 | 
			
		||||
        foreach ($transactions as $transaction) {
 | 
			
		||||
            $return[] = $this->getSingleTransactionData($transaction);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the repetition data as it is found in the submitted data.
 | 
			
		||||
     */
 | 
			
		||||
    private function getRepetitionData(): array
 | 
			
		||||
    {
 | 
			
		||||
        $return      = [];
 | 
			
		||||
 | 
			
		||||
        // repetition data:
 | 
			
		||||
        /** @var null|array $repetitions */
 | 
			
		||||
        $repetitions = $this->get('repetitions');
 | 
			
		||||
        if (null === $repetitions) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /** @var array $repetition */
 | 
			
		||||
        foreach ($repetitions as $repetition) {
 | 
			
		||||
            $current  = [];
 | 
			
		||||
            if (array_key_exists('type', $repetition)) {
 | 
			
		||||
                $current['type'] = $repetition['type'];
 | 
			
		||||
            }
 | 
			
		||||
            if (array_key_exists('moment', $repetition)) {
 | 
			
		||||
                $current['moment'] = $repetition['moment'];
 | 
			
		||||
            }
 | 
			
		||||
            if (array_key_exists('skip', $repetition)) {
 | 
			
		||||
                $current['skip'] = (int)$repetition['skip'];
 | 
			
		||||
            }
 | 
			
		||||
            if (array_key_exists('weekend', $repetition)) {
 | 
			
		||||
                $current['weekend'] = (int)$repetition['weekend'];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $return[] = $current;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The rules that the incoming request must be matched against.
 | 
			
		||||
     */
 | 
			
		||||
@@ -132,67 +191,8 @@ class StoreRequest extends FormRequest
 | 
			
		||||
                $this->validateAccountInformation($validator);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the transaction data as it is found in the submitted data. It's a complex method according to code
 | 
			
		||||
     * standards but it just has a lot of ??-statements because of the fields that may or may not exist.
 | 
			
		||||
     */
 | 
			
		||||
    private function getTransactionData(): array
 | 
			
		||||
    {
 | 
			
		||||
        $return       = [];
 | 
			
		||||
 | 
			
		||||
        // transaction data:
 | 
			
		||||
        /** @var null|array $transactions */
 | 
			
		||||
        $transactions = $this->get('transactions');
 | 
			
		||||
        if (null === $transactions) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /** @var array $transaction */
 | 
			
		||||
        foreach ($transactions as $transaction) {
 | 
			
		||||
            $return[] = $this->getSingleTransactionData($transaction);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the repetition data as it is found in the submitted data.
 | 
			
		||||
     */
 | 
			
		||||
    private function getRepetitionData(): array
 | 
			
		||||
    {
 | 
			
		||||
        $return      = [];
 | 
			
		||||
 | 
			
		||||
        // repetition data:
 | 
			
		||||
        /** @var null|array $repetitions */
 | 
			
		||||
        $repetitions = $this->get('repetitions');
 | 
			
		||||
        if (null === $repetitions) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /** @var array $repetition */
 | 
			
		||||
        foreach ($repetitions as $repetition) {
 | 
			
		||||
            $current  = [];
 | 
			
		||||
            if (array_key_exists('type', $repetition)) {
 | 
			
		||||
                $current['type'] = $repetition['type'];
 | 
			
		||||
            }
 | 
			
		||||
            if (array_key_exists('moment', $repetition)) {
 | 
			
		||||
                $current['moment'] = $repetition['moment'];
 | 
			
		||||
            }
 | 
			
		||||
            if (array_key_exists('skip', $repetition)) {
 | 
			
		||||
                $current['skip'] = (int)$repetition['skip'];
 | 
			
		||||
            }
 | 
			
		||||
            if (array_key_exists('weekend', $repetition)) {
 | 
			
		||||
                $current['weekend'] = (int)$repetition['weekend'];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $return[] = $current;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -78,6 +78,70 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the repetition data as it is found in the submitted data.
 | 
			
		||||
     */
 | 
			
		||||
    private function getRepetitionData(): ?array
 | 
			
		||||
    {
 | 
			
		||||
        $return      = [];
 | 
			
		||||
 | 
			
		||||
        // repetition data:
 | 
			
		||||
        /** @var null|array $repetitions */
 | 
			
		||||
        $repetitions = $this->get('repetitions');
 | 
			
		||||
        if (null === $repetitions) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /** @var array $repetition */
 | 
			
		||||
        foreach ($repetitions as $repetition) {
 | 
			
		||||
            $current  = [];
 | 
			
		||||
            if (array_key_exists('type', $repetition)) {
 | 
			
		||||
                $current['type'] = $repetition['type'];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (array_key_exists('moment', $repetition)) {
 | 
			
		||||
                $current['moment'] = (string)$repetition['moment'];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (array_key_exists('skip', $repetition)) {
 | 
			
		||||
                $current['skip'] = (int)$repetition['skip'];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (array_key_exists('weekend', $repetition)) {
 | 
			
		||||
                $current['weekend'] = (int)$repetition['weekend'];
 | 
			
		||||
            }
 | 
			
		||||
            $return[] = $current;
 | 
			
		||||
        }
 | 
			
		||||
        if (0 === count($return)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the transaction data as it is found in the submitted data. It's a complex method according to code
 | 
			
		||||
     * standards but it just has a lot of ??-statements because of the fields that may or may not exist.
 | 
			
		||||
     */
 | 
			
		||||
    private function getTransactionData(): array
 | 
			
		||||
    {
 | 
			
		||||
        $return       = [];
 | 
			
		||||
 | 
			
		||||
        // transaction data:
 | 
			
		||||
        /** @var null|array $transactions */
 | 
			
		||||
        $transactions = $this->get('transactions');
 | 
			
		||||
        if (null === $transactions) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /** @var array $transaction */
 | 
			
		||||
        foreach ($transactions as $transaction) {
 | 
			
		||||
            $return[] = $this->getSingleTransactionData($transaction);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The rules that the incoming request must be matched against.
 | 
			
		||||
     */
 | 
			
		||||
@@ -142,72 +206,8 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
                $this->valUpdateAccountInfo($validator);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the repetition data as it is found in the submitted data.
 | 
			
		||||
     */
 | 
			
		||||
    private function getRepetitionData(): ?array
 | 
			
		||||
    {
 | 
			
		||||
        $return      = [];
 | 
			
		||||
 | 
			
		||||
        // repetition data:
 | 
			
		||||
        /** @var null|array $repetitions */
 | 
			
		||||
        $repetitions = $this->get('repetitions');
 | 
			
		||||
        if (null === $repetitions) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /** @var array $repetition */
 | 
			
		||||
        foreach ($repetitions as $repetition) {
 | 
			
		||||
            $current  = [];
 | 
			
		||||
            if (array_key_exists('type', $repetition)) {
 | 
			
		||||
                $current['type'] = $repetition['type'];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (array_key_exists('moment', $repetition)) {
 | 
			
		||||
                $current['moment'] = (string) $repetition['moment'];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (array_key_exists('skip', $repetition)) {
 | 
			
		||||
                $current['skip'] = (int) $repetition['skip'];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (array_key_exists('weekend', $repetition)) {
 | 
			
		||||
                $current['weekend'] = (int) $repetition['weekend'];
 | 
			
		||||
            }
 | 
			
		||||
            $return[] = $current;
 | 
			
		||||
        }
 | 
			
		||||
        if (0 === count($return)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the transaction data as it is found in the submitted data. It's a complex method according to code
 | 
			
		||||
     * standards but it just has a lot of ??-statements because of the fields that may or may not exist.
 | 
			
		||||
     */
 | 
			
		||||
    private function getTransactionData(): array
 | 
			
		||||
    {
 | 
			
		||||
        $return       = [];
 | 
			
		||||
 | 
			
		||||
        // transaction data:
 | 
			
		||||
        /** @var null|array $transactions */
 | 
			
		||||
        $transactions = $this->get('transactions');
 | 
			
		||||
        if (null === $transactions) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /** @var array $transaction */
 | 
			
		||||
        foreach ($transactions as $transaction) {
 | 
			
		||||
            $return[] = $this->getSingleTransactionData($transaction);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ declare(strict_types=1);
 | 
			
		||||
namespace FireflyIII\Api\V1\Requests\Models\Rule;
 | 
			
		||||
 | 
			
		||||
use FireflyIII\Rules\IsBoolean;
 | 
			
		||||
use FireflyIII\Rules\IsValidActionExpression;
 | 
			
		||||
use FireflyIII\Support\Request\ChecksLogin;
 | 
			
		||||
use FireflyIII\Support\Request\ConvertsDataTypes;
 | 
			
		||||
use FireflyIII\Support\Request\GetRuleConfiguration;
 | 
			
		||||
@@ -57,13 +58,49 @@ class StoreRequest extends FormRequest
 | 
			
		||||
            'active'           => ['active', 'boolean'],
 | 
			
		||||
        ];
 | 
			
		||||
        $data             = $this->getAllData($fields);
 | 
			
		||||
 | 
			
		||||
        $data['triggers'] = $this->getRuleTriggers();
 | 
			
		||||
        $data['actions']  = $this->getRuleActions();
 | 
			
		||||
 | 
			
		||||
        return $data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getRuleTriggers(): array
 | 
			
		||||
    {
 | 
			
		||||
        $triggers = $this->get('triggers');
 | 
			
		||||
        $return   = [];
 | 
			
		||||
        if (is_array($triggers)) {
 | 
			
		||||
            foreach ($triggers as $trigger) {
 | 
			
		||||
                $return[] = [
 | 
			
		||||
                    'type'            => $trigger['type'] ?? '',
 | 
			
		||||
                    'value'           => $trigger['value'] ?? null,
 | 
			
		||||
                    'prohibited'      => $this->convertBoolean((string)($trigger['prohibited'] ?? 'false')),
 | 
			
		||||
                    'active'          => $this->convertBoolean((string)($trigger['active'] ?? 'true')),
 | 
			
		||||
                    'stop_processing' => $this->convertBoolean((string)($trigger['stop_processing'] ?? 'false')),
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getRuleActions(): array
 | 
			
		||||
    {
 | 
			
		||||
        $actions = $this->get('actions');
 | 
			
		||||
        $return  = [];
 | 
			
		||||
        if (is_array($actions)) {
 | 
			
		||||
            foreach ($actions as $action) {
 | 
			
		||||
                $return[] = [
 | 
			
		||||
                    'type'            => $action['type'],
 | 
			
		||||
                    'value'           => $action['value'],
 | 
			
		||||
                    'active'          => $this->convertBoolean((string)($action['active'] ?? 'true')),
 | 
			
		||||
                    'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')),
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The rules that the incoming request must be matched against.
 | 
			
		||||
     */
 | 
			
		||||
@@ -87,7 +124,7 @@ class StoreRequest extends FormRequest
 | 
			
		||||
            'triggers.*.stop_processing' => [new IsBoolean()],
 | 
			
		||||
            'triggers.*.active'          => [new IsBoolean()],
 | 
			
		||||
            'actions.*.type'             => 'required|in:'.implode(',', $validActions),
 | 
			
		||||
            'actions.*.value'            => 'required_if:actions.*.type,'.$contextActions.'|ruleActionValue',
 | 
			
		||||
            'actions.*.value'            => [sprintf('required_if:actions.*.type,%s', $contextActions), new IsValidActionExpression(), 'ruleActionValue'],
 | 
			
		||||
            'actions.*.stop_processing'  => [new IsBoolean()],
 | 
			
		||||
            'actions.*.active'           => [new IsBoolean()],
 | 
			
		||||
            'strict'                     => [new IsBoolean()],
 | 
			
		||||
@@ -109,7 +146,7 @@ class StoreRequest extends FormRequest
 | 
			
		||||
                $this->atLeastOneActiveAction($validator);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -197,40 +234,4 @@ class StoreRequest extends FormRequest
 | 
			
		||||
            $validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string)trans('validation.at_least_one_active_action'));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getRuleTriggers(): array
 | 
			
		||||
    {
 | 
			
		||||
        $triggers = $this->get('triggers');
 | 
			
		||||
        $return   = [];
 | 
			
		||||
        if (is_array($triggers)) {
 | 
			
		||||
            foreach ($triggers as $trigger) {
 | 
			
		||||
                $return[] = [
 | 
			
		||||
                    'type'            => $trigger['type'],
 | 
			
		||||
                    'value'           => $trigger['value'],
 | 
			
		||||
                    'active'          => $this->convertBoolean((string)($trigger['active'] ?? 'true')),
 | 
			
		||||
                    'stop_processing' => $this->convertBoolean((string)($trigger['stop_processing'] ?? 'false')),
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getRuleActions(): array
 | 
			
		||||
    {
 | 
			
		||||
        $actions = $this->get('actions');
 | 
			
		||||
        $return  = [];
 | 
			
		||||
        if (is_array($actions)) {
 | 
			
		||||
            foreach ($actions as $action) {
 | 
			
		||||
                $return[] = [
 | 
			
		||||
                    'type'            => $action['type'],
 | 
			
		||||
                    'value'           => $action['value'],
 | 
			
		||||
                    'active'          => $this->convertBoolean((string)($action['active'] ?? 'true')),
 | 
			
		||||
                    'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')),
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,27 @@ class TestRequest extends FormRequest
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getPage(): int
 | 
			
		||||
    {
 | 
			
		||||
        return 0 === (int)$this->query('page') ? 1 : (int)$this->query('page');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getDate(string $field): ?Carbon
 | 
			
		||||
    {
 | 
			
		||||
        $value = $this->query($field);
 | 
			
		||||
        if (is_array($value)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $value = (string)$value;
 | 
			
		||||
 | 
			
		||||
        return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getAccounts(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->get('accounts');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function rules(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
@@ -56,29 +77,4 @@ class TestRequest extends FormRequest
 | 
			
		||||
            'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getPage(): int
 | 
			
		||||
    {
 | 
			
		||||
        return 0 === (int)$this->query('page') ? 1 : (int)$this->query('page');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getDate(string $field): ?Carbon
 | 
			
		||||
    {
 | 
			
		||||
        $value  = $this->query($field);
 | 
			
		||||
        if (is_array($value)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $value  = (string)$value;
 | 
			
		||||
        $result = null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
 | 
			
		||||
        if (false === $result) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getAccounts(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->get('accounts');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,22 @@ class TriggerRequest extends FormRequest
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getDate(string $field): ?Carbon
 | 
			
		||||
    {
 | 
			
		||||
        $value = $this->query($field);
 | 
			
		||||
        if (is_array($value)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $value = (string)$value;
 | 
			
		||||
 | 
			
		||||
        return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getAccounts(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->get('accounts') ?? [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function rules(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
@@ -55,24 +71,4 @@ class TriggerRequest extends FormRequest
 | 
			
		||||
            'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getDate(string $field): ?Carbon
 | 
			
		||||
    {
 | 
			
		||||
        $value  = $this->query($field);
 | 
			
		||||
        if (is_array($value)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $value  = (string)$value;
 | 
			
		||||
        $result = null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
 | 
			
		||||
        if (false === $result) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getAccounts(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->get('accounts') ?? [];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ namespace FireflyIII\Api\V1\Requests\Models\Rule;
 | 
			
		||||
 | 
			
		||||
use FireflyIII\Models\Rule;
 | 
			
		||||
use FireflyIII\Rules\IsBoolean;
 | 
			
		||||
use FireflyIII\Rules\IsValidActionExpression;
 | 
			
		||||
use FireflyIII\Support\Request\ChecksLogin;
 | 
			
		||||
use FireflyIII\Support\Request\ConvertsDataTypes;
 | 
			
		||||
use FireflyIII\Support\Request\GetRuleConfiguration;
 | 
			
		||||
@@ -70,6 +71,52 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getRuleTriggers(): ?array
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->has('triggers')) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $triggers = $this->get('triggers');
 | 
			
		||||
        $return   = [];
 | 
			
		||||
        if (is_array($triggers)) {
 | 
			
		||||
            foreach ($triggers as $trigger) {
 | 
			
		||||
                $active         = array_key_exists('active', $trigger) ? $trigger['active'] : true;
 | 
			
		||||
                $prohibited     = array_key_exists('prohibited', $trigger) ? $trigger['prohibited'] : false;
 | 
			
		||||
                $stopProcessing = array_key_exists('stop_processing', $trigger) ? $trigger['stop_processing'] : false;
 | 
			
		||||
                $return[]       = [
 | 
			
		||||
                    'type'            => $trigger['type'],
 | 
			
		||||
                    'value'           => $trigger['value'],
 | 
			
		||||
                    'prohibited'      => $prohibited,
 | 
			
		||||
                    'active'          => $active,
 | 
			
		||||
                    'stop_processing' => $stopProcessing,
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getRuleActions(): ?array
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->has('actions')) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $actions = $this->get('actions');
 | 
			
		||||
        $return  = [];
 | 
			
		||||
        if (is_array($actions)) {
 | 
			
		||||
            foreach ($actions as $action) {
 | 
			
		||||
                $return[] = [
 | 
			
		||||
                    'type'            => $action['type'],
 | 
			
		||||
                    'value'           => $action['value'],
 | 
			
		||||
                    'active'          => $this->convertBoolean((string)($action['active'] ?? 'false')),
 | 
			
		||||
                    'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')),
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The rules that the incoming request must be matched against.
 | 
			
		||||
     */
 | 
			
		||||
@@ -96,7 +143,7 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
            'triggers.*.stop_processing' => [new IsBoolean()],
 | 
			
		||||
            'triggers.*.active'          => [new IsBoolean()],
 | 
			
		||||
            'actions.*.type'             => 'required|in:'.implode(',', $validActions),
 | 
			
		||||
            'actions.*.value'            => 'required_if:actions.*.type,'.$contextActions.'|ruleActionValue',
 | 
			
		||||
            'actions.*.value'            => [sprintf('required_if:actions.*.type,%s', $contextActions), new IsValidActionExpression(), 'ruleActionValue'],
 | 
			
		||||
            'actions.*.stop_processing'  => [new IsBoolean()],
 | 
			
		||||
            'actions.*.active'           => [new IsBoolean()],
 | 
			
		||||
            'strict'                     => [new IsBoolean()],
 | 
			
		||||
@@ -119,7 +166,7 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
                $this->atLeastOneValidAction($validator);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -204,48 +251,4 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
            $validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string)trans('validation.at_least_one_active_action'));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getRuleTriggers(): ?array
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->has('triggers')) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $triggers = $this->get('triggers');
 | 
			
		||||
        $return   = [];
 | 
			
		||||
        if (is_array($triggers)) {
 | 
			
		||||
            foreach ($triggers as $trigger) {
 | 
			
		||||
                $active         = array_key_exists('active', $trigger) ? $trigger['active'] : true;
 | 
			
		||||
                $stopProcessing = array_key_exists('stop_processing', $trigger) ? $trigger['stop_processing'] : false;
 | 
			
		||||
                $return[]       = [
 | 
			
		||||
                    'type'            => $trigger['type'],
 | 
			
		||||
                    'value'           => $trigger['value'],
 | 
			
		||||
                    'active'          => $active,
 | 
			
		||||
                    'stop_processing' => $stopProcessing,
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getRuleActions(): ?array
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->has('actions')) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $actions = $this->get('actions');
 | 
			
		||||
        $return  = [];
 | 
			
		||||
        if (is_array($actions)) {
 | 
			
		||||
            foreach ($actions as $action) {
 | 
			
		||||
                $return[] = [
 | 
			
		||||
                    'type'            => $action['type'],
 | 
			
		||||
                    'value'           => $action['value'],
 | 
			
		||||
                    'active'          => $this->convertBoolean((string)($action['active'] ?? 'false')),
 | 
			
		||||
                    'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')),
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * auth.php
 | 
			
		||||
 * Copyright (c) 2019 james@firefly-iii.org
 | 
			
		||||
 * ValidateExpressionRequest.php
 | 
			
		||||
 * Copyright (c) 2024 Michael Thomas
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of Firefly III (https://github.com/firefly-iii).
 | 
			
		||||
 *
 | 
			
		||||
@@ -20,20 +20,23 @@
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * PLEASE DO NOT EDIT THIS FILE DIRECTLY.
 | 
			
		||||
 * YOUR CHANGES WILL BE OVERWRITTEN!
 | 
			
		||||
 * YOUR PR WITH CHANGES TO THIS FILE WILL BE REJECTED!
 | 
			
		||||
 *
 | 
			
		||||
 * GO TO CROWDIN TO FIX OR CHANGE TRANSLATIONS!
 | 
			
		||||
 *
 | 
			
		||||
 * https://crowdin.com/project/firefly-iii
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
    'failed'   => 'These credentials do not match our records.',
 | 
			
		||||
    'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
 | 
			
		||||
];
 | 
			
		||||
namespace FireflyIII\Api\V1\Requests\Models\Rule;
 | 
			
		||||
 | 
			
		||||
use FireflyIII\Rules\IsValidActionExpression;
 | 
			
		||||
use FireflyIII\Support\Request\ChecksLogin;
 | 
			
		||||
use Illuminate\Foundation\Http\FormRequest;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class ValidateExpressionRequest
 | 
			
		||||
 */
 | 
			
		||||
class ValidateExpressionRequest extends FormRequest
 | 
			
		||||
{
 | 
			
		||||
    use ChecksLogin;
 | 
			
		||||
 | 
			
		||||
    public function rules(): array
 | 
			
		||||
    {
 | 
			
		||||
        return ['expression' => ['required', new IsValidActionExpression()]];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -46,6 +46,22 @@ class TestRequest extends FormRequest
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getDate(string $field): ?Carbon
 | 
			
		||||
    {
 | 
			
		||||
        $value = $this->query($field);
 | 
			
		||||
        if (is_array($value)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $value = (string)$value;
 | 
			
		||||
 | 
			
		||||
        return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getAccounts(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->get('accounts');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function rules(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
@@ -55,24 +71,4 @@ class TestRequest extends FormRequest
 | 
			
		||||
            'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getDate(string $field): ?Carbon
 | 
			
		||||
    {
 | 
			
		||||
        $value  = $this->query($field);
 | 
			
		||||
        if (is_array($value)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $value  = (string)$value;
 | 
			
		||||
        $result = null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
 | 
			
		||||
        if (false === $result) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getAccounts(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->get('accounts');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,26 @@ class TriggerRequest extends FormRequest
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getDate(string $field): ?Carbon
 | 
			
		||||
    {
 | 
			
		||||
        $value = $this->query($field);
 | 
			
		||||
        if (is_array($value)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $value = (string)$value;
 | 
			
		||||
 | 
			
		||||
        return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getAccounts(): array
 | 
			
		||||
    {
 | 
			
		||||
        if (null === $this->get('accounts')) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->get('accounts');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function rules(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
@@ -53,28 +73,4 @@ class TriggerRequest extends FormRequest
 | 
			
		||||
            'end'   => 'date|after_or_equal:start',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getDate(string $field): ?Carbon
 | 
			
		||||
    {
 | 
			
		||||
        $value  = $this->query($field);
 | 
			
		||||
        if (is_array($value)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $value  = (string)$value;
 | 
			
		||||
        $result = null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
 | 
			
		||||
        if (false === $result) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getAccounts(): array
 | 
			
		||||
    {
 | 
			
		||||
        if(null === $this->get('accounts')) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->get('accounts');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -69,6 +69,101 @@ class StoreRequest extends FormRequest
 | 
			
		||||
        // TODO include location and ability to process it.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get transaction data.
 | 
			
		||||
     */
 | 
			
		||||
    private function getTransactionData(): array
 | 
			
		||||
    {
 | 
			
		||||
        $return = [];
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @var array $transaction
 | 
			
		||||
         */
 | 
			
		||||
        foreach ($this->get('transactions') as $transaction) {
 | 
			
		||||
            $object   = new NullArrayObject($transaction);
 | 
			
		||||
            $return[] = [
 | 
			
		||||
                'type'                  => $this->clearString($object['type']),
 | 
			
		||||
                'date'                  => $this->dateFromValue($object['date']),
 | 
			
		||||
                'order'                 => $this->integerFromValue((string)$object['order']),
 | 
			
		||||
 | 
			
		||||
                'currency_id'           => $this->integerFromValue((string)$object['currency_id']),
 | 
			
		||||
                'currency_code'         => $this->clearString((string)$object['currency_code']),
 | 
			
		||||
 | 
			
		||||
                // foreign currency info:
 | 
			
		||||
                'foreign_currency_id'   => $this->integerFromValue((string)$object['foreign_currency_id']),
 | 
			
		||||
                'foreign_currency_code' => $this->clearString((string)$object['foreign_currency_code']),
 | 
			
		||||
 | 
			
		||||
                // amount and foreign amount. Cannot be 0.
 | 
			
		||||
                'amount'                => $this->clearString((string)$object['amount']),
 | 
			
		||||
                'foreign_amount'        => $this->clearString((string)$object['foreign_amount']),
 | 
			
		||||
 | 
			
		||||
                // description.
 | 
			
		||||
                'description'           => $this->clearString($object['description']),
 | 
			
		||||
 | 
			
		||||
                // source of transaction. If everything is null, assume cash account.
 | 
			
		||||
                'source_id'             => $this->integerFromValue((string)$object['source_id']),
 | 
			
		||||
                'source_name'           => $this->clearString((string)$object['source_name']),
 | 
			
		||||
                'source_iban'           => $this->clearIban((string)$object['source_iban']),
 | 
			
		||||
                'source_number'         => $this->clearString((string)$object['source_number']),
 | 
			
		||||
                'source_bic'            => $this->clearString((string)$object['source_bic']),
 | 
			
		||||
 | 
			
		||||
                // destination of transaction. If everything is null, assume cash account.
 | 
			
		||||
                'destination_id'        => $this->integerFromValue((string)$object['destination_id']),
 | 
			
		||||
                'destination_name'      => $this->clearString((string)$object['destination_name']),
 | 
			
		||||
                'destination_iban'      => $this->clearIban((string)$object['destination_iban']),
 | 
			
		||||
                'destination_number'    => $this->clearString((string)$object['destination_number']),
 | 
			
		||||
                'destination_bic'       => $this->clearString((string)$object['destination_bic']),
 | 
			
		||||
 | 
			
		||||
                // budget info
 | 
			
		||||
                'budget_id'             => $this->integerFromValue((string)$object['budget_id']),
 | 
			
		||||
                'budget_name'           => $this->clearString((string)$object['budget_name']),
 | 
			
		||||
 | 
			
		||||
                // category info
 | 
			
		||||
                'category_id'           => $this->integerFromValue((string)$object['category_id']),
 | 
			
		||||
                'category_name'         => $this->clearString((string)$object['category_name']),
 | 
			
		||||
 | 
			
		||||
                // journal bill reference. Optional. Will only work for withdrawals
 | 
			
		||||
                'bill_id'               => $this->integerFromValue((string)$object['bill_id']),
 | 
			
		||||
                'bill_name'             => $this->clearString((string)$object['bill_name']),
 | 
			
		||||
 | 
			
		||||
                // piggy bank reference. Optional. Will only work for transfers
 | 
			
		||||
                'piggy_bank_id'         => $this->integerFromValue((string)$object['piggy_bank_id']),
 | 
			
		||||
                'piggy_bank_name'       => $this->clearString((string)$object['piggy_bank_name']),
 | 
			
		||||
 | 
			
		||||
                // some other interesting properties
 | 
			
		||||
                'reconciled'            => $this->convertBoolean((string)$object['reconciled']),
 | 
			
		||||
                'notes'                 => $this->clearStringKeepNewlines((string)$object['notes']),
 | 
			
		||||
                'tags'                  => $this->arrayFromValue($object['tags']),
 | 
			
		||||
 | 
			
		||||
                // all custom fields:
 | 
			
		||||
                'internal_reference'    => $this->clearString((string)$object['internal_reference']),
 | 
			
		||||
                'external_id'           => $this->clearString((string)$object['external_id']),
 | 
			
		||||
                'original_source'       => sprintf('ff3-v%s', config('firefly.version')),
 | 
			
		||||
                'recurrence_id'         => $this->integerFromValue($object['recurrence_id']),
 | 
			
		||||
                'bunq_payment_id'       => $this->clearString((string)$object['bunq_payment_id']),
 | 
			
		||||
                'external_url'          => $this->clearString((string)$object['external_url']),
 | 
			
		||||
 | 
			
		||||
                'sepa_cc'               => $this->clearString((string)$object['sepa_cc']),
 | 
			
		||||
                'sepa_ct_op'            => $this->clearString((string)$object['sepa_ct_op']),
 | 
			
		||||
                'sepa_ct_id'            => $this->clearString((string)$object['sepa_ct_id']),
 | 
			
		||||
                'sepa_db'               => $this->clearString((string)$object['sepa_db']),
 | 
			
		||||
                'sepa_country'          => $this->clearString((string)$object['sepa_country']),
 | 
			
		||||
                'sepa_ep'               => $this->clearString((string)$object['sepa_ep']),
 | 
			
		||||
                'sepa_ci'               => $this->clearString((string)$object['sepa_ci']),
 | 
			
		||||
                'sepa_batch_id'         => $this->clearString((string)$object['sepa_batch_id']),
 | 
			
		||||
                // custom date fields. Must be Carbon objects. Presence is optional.
 | 
			
		||||
                'interest_date'         => $this->dateFromValue($object['interest_date']),
 | 
			
		||||
                'book_date'             => $this->dateFromValue($object['book_date']),
 | 
			
		||||
                'process_date'          => $this->dateFromValue($object['process_date']),
 | 
			
		||||
                'due_date'              => $this->dateFromValue($object['due_date']),
 | 
			
		||||
                'payment_date'          => $this->dateFromValue($object['payment_date']),
 | 
			
		||||
                'invoice_date'          => $this->dateFromValue($object['invoice_date']),
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The rules that the incoming request must be matched against.
 | 
			
		||||
     */
 | 
			
		||||
@@ -79,82 +174,82 @@ class StoreRequest extends FormRequest
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            // basic fields for group:
 | 
			
		||||
            'group_title'                            => 'min:1|max:1000|nullable',
 | 
			
		||||
            'error_if_duplicate_hash'                => [new IsBoolean()],
 | 
			
		||||
            'apply_rules'                            => [new IsBoolean()],
 | 
			
		||||
            'group_title'                          => 'min:1|max:1000|nullable',
 | 
			
		||||
            'error_if_duplicate_hash'              => [new IsBoolean()],
 | 
			
		||||
            'apply_rules'                          => [new IsBoolean()],
 | 
			
		||||
 | 
			
		||||
            // transaction rules (in array for splits):
 | 
			
		||||
            'transactions.*.type'                    => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',
 | 
			
		||||
            'transactions.*.date'                    => ['required', new IsDateOrTime()],
 | 
			
		||||
            'transactions.*.order'                   => 'numeric|min:0',
 | 
			
		||||
            'transactions.*.type'                  => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',
 | 
			
		||||
            'transactions.*.date'                  => ['required', new IsDateOrTime()],
 | 
			
		||||
            'transactions.*.order'                 => 'numeric|min:0',
 | 
			
		||||
 | 
			
		||||
            // currency info
 | 
			
		||||
            'transactions.*.currency_id'             => 'numeric|exists:transaction_currencies,id|nullable',
 | 
			
		||||
            'transactions.*.currency_code'           => 'min:3|max:51|exists:transaction_currencies,code|nullable',
 | 
			
		||||
            'transactions.*.foreign_currency_id'     => 'numeric|exists:transaction_currencies,id|nullable',
 | 
			
		||||
            'transactions.*.foreign_currency_code'   => 'min:3|max:51|exists:transaction_currencies,code|nullable',
 | 
			
		||||
            'transactions.*.currency_id'           => 'numeric|exists:transaction_currencies,id|nullable',
 | 
			
		||||
            'transactions.*.currency_code'         => 'min:3|max:51|exists:transaction_currencies,code|nullable',
 | 
			
		||||
            'transactions.*.foreign_currency_id'   => 'numeric|exists:transaction_currencies,id|nullable',
 | 
			
		||||
            'transactions.*.foreign_currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable',
 | 
			
		||||
 | 
			
		||||
            // amount
 | 
			
		||||
            'transactions.*.amount'                  => ['required', new IsValidPositiveAmount()],
 | 
			
		||||
            'transactions.*.foreign_amount'          => ['nullable', new IsValidZeroOrMoreAmount()],
 | 
			
		||||
            'transactions.*.amount'                => ['required', new IsValidPositiveAmount()],
 | 
			
		||||
            'transactions.*.foreign_amount'        => ['nullable', new IsValidZeroOrMoreAmount()],
 | 
			
		||||
 | 
			
		||||
            // description
 | 
			
		||||
            'transactions.*.description'             => 'nullable|min:1|max:1000',
 | 
			
		||||
            'transactions.*.description'           => 'nullable|min:1|max:1000',
 | 
			
		||||
 | 
			
		||||
            // source of transaction
 | 
			
		||||
            'transactions.*.source_id'               => ['numeric', 'nullable', new BelongsUser()],
 | 
			
		||||
            'transactions.*.source_name'             => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.source_iban'             => 'min:1|max:255|nullable|iban',
 | 
			
		||||
            'transactions.*.source_number'           => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.source_bic'              => 'min:1|max:255|nullable|bic',
 | 
			
		||||
            'transactions.*.source_id'             => ['numeric', 'nullable', new BelongsUser()],
 | 
			
		||||
            'transactions.*.source_name'           => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.source_iban'           => 'min:1|max:255|nullable|iban',
 | 
			
		||||
            'transactions.*.source_number'         => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.source_bic'            => 'min:1|max:255|nullable|bic',
 | 
			
		||||
 | 
			
		||||
            // destination of transaction
 | 
			
		||||
            'transactions.*.destination_id'          => ['numeric', 'nullable', new BelongsUser()],
 | 
			
		||||
            'transactions.*.destination_name'        => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.destination_iban'        => 'min:1|max:255|nullable|iban',
 | 
			
		||||
            'transactions.*.destination_number'      => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.destination_bic'         => 'min:1|max:255|nullable|bic',
 | 
			
		||||
            'transactions.*.destination_id'        => ['numeric', 'nullable', new BelongsUser()],
 | 
			
		||||
            'transactions.*.destination_name'      => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.destination_iban'      => 'min:1|max:255|nullable|iban',
 | 
			
		||||
            'transactions.*.destination_number'    => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.destination_bic'       => 'min:1|max:255|nullable|bic',
 | 
			
		||||
 | 
			
		||||
            // budget, category, bill and piggy
 | 
			
		||||
            'transactions.*.budget_id'               => ['mustExist:budgets,id', new BelongsUser()],
 | 
			
		||||
            'transactions.*.budget_name'             => ['min:1', 'max:255', 'nullable', new BelongsUser()],
 | 
			
		||||
            'transactions.*.category_id'             => ['mustExist:categories,id', new BelongsUser(), 'nullable'],
 | 
			
		||||
            'transactions.*.category_name'           => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.bill_id'                 => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()],
 | 
			
		||||
            'transactions.*.bill_name'               => ['min:1', 'max:255', 'nullable', new BelongsUser()],
 | 
			
		||||
            'transactions.*.piggy_bank_id'           => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUser()],
 | 
			
		||||
            'transactions.*.piggy_bank_name'         => ['min:1', 'max:255', 'nullable', new BelongsUser()],
 | 
			
		||||
            'transactions.*.budget_id'             => ['mustExist:budgets,id', new BelongsUser()],
 | 
			
		||||
            'transactions.*.budget_name'           => ['min:1', 'max:255', 'nullable', new BelongsUser()],
 | 
			
		||||
            'transactions.*.category_id'           => ['mustExist:categories,id', new BelongsUser(), 'nullable'],
 | 
			
		||||
            'transactions.*.category_name'         => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.bill_id'               => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()],
 | 
			
		||||
            'transactions.*.bill_name'             => ['min:1', 'max:255', 'nullable', new BelongsUser()],
 | 
			
		||||
            'transactions.*.piggy_bank_id'         => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUser()],
 | 
			
		||||
            'transactions.*.piggy_bank_name'       => ['min:1', 'max:255', 'nullable', new BelongsUser()],
 | 
			
		||||
 | 
			
		||||
            // other interesting fields
 | 
			
		||||
            'transactions.*.reconciled'              => [new IsBoolean()],
 | 
			
		||||
            'transactions.*.notes'                   => 'min:1|max:32768|nullable',
 | 
			
		||||
            'transactions.*.tags'                    => 'min:0|max:255',
 | 
			
		||||
            'transactions.*.tags.*'                  => 'min:0|max:255',
 | 
			
		||||
            'transactions.*.reconciled'            => [new IsBoolean()],
 | 
			
		||||
            'transactions.*.notes'                 => 'min:1|max:32768|nullable',
 | 
			
		||||
            'transactions.*.tags'                  => 'min:0|max:255',
 | 
			
		||||
            'transactions.*.tags.*'                => 'min:0|max:255',
 | 
			
		||||
 | 
			
		||||
            // meta info fields
 | 
			
		||||
            'transactions.*.internal_reference'      => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.external_id'             => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.recurrence_id'           => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.bunq_payment_id'         => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.external_url'            => sprintf('min:1|max:255|nullable|url:%s', $validProtocols),
 | 
			
		||||
            'transactions.*.internal_reference'    => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.external_id'           => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.recurrence_id'         => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.bunq_payment_id'       => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.external_url'          => sprintf('min:1|max:255|nullable|url:%s', $validProtocols),
 | 
			
		||||
 | 
			
		||||
            // SEPA fields:
 | 
			
		||||
            'transactions.*.sepa_cc'                 => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ct_op'              => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ct_id'              => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_db'                 => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_country'            => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ep'                 => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ci'                 => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_batch_id'           => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_cc'               => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ct_op'            => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ct_id'            => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_db'               => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_country'          => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ep'               => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ci'               => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_batch_id'         => 'min:1|max:255|nullable',
 | 
			
		||||
 | 
			
		||||
            // dates
 | 
			
		||||
            'transactions.*.interest_date'           => 'date|nullable',
 | 
			
		||||
            'transactions.*.book_date'               => 'date|nullable',
 | 
			
		||||
            'transactions.*.process_date'            => 'date|nullable',
 | 
			
		||||
            'transactions.*.due_date'                => 'date|nullable',
 | 
			
		||||
            'transactions.*.payment_date'            => 'date|nullable',
 | 
			
		||||
            'transactions.*.invoice_date'            => 'date|nullable',
 | 
			
		||||
            'transactions.*.interest_date'         => 'date|nullable',
 | 
			
		||||
            'transactions.*.book_date'             => 'date|nullable',
 | 
			
		||||
            'transactions.*.process_date'          => 'date|nullable',
 | 
			
		||||
            'transactions.*.due_date'              => 'date|nullable',
 | 
			
		||||
            'transactions.*.payment_date'          => 'date|nullable',
 | 
			
		||||
            'transactions.*.invoice_date'          => 'date|nullable',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -192,103 +287,8 @@ class StoreRequest extends FormRequest
 | 
			
		||||
                $this->validateGroupDescription($validator);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get transaction data.
 | 
			
		||||
     */
 | 
			
		||||
    private function getTransactionData(): array
 | 
			
		||||
    {
 | 
			
		||||
        $return = [];
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @var array $transaction
 | 
			
		||||
         */
 | 
			
		||||
        foreach ($this->get('transactions') as $transaction) {
 | 
			
		||||
            $object   = new NullArrayObject($transaction);
 | 
			
		||||
            $return[] = [
 | 
			
		||||
                'type'                  => $this->clearString($object['type']),
 | 
			
		||||
                'date'                  => $this->dateFromValue($object['date']),
 | 
			
		||||
                'order'                 => $this->integerFromValue((string)$object['order']),
 | 
			
		||||
 | 
			
		||||
                'currency_id'           => $this->integerFromValue((string)$object['currency_id']),
 | 
			
		||||
                'currency_code'         => $this->clearString((string)$object['currency_code']),
 | 
			
		||||
 | 
			
		||||
                // foreign currency info:
 | 
			
		||||
                'foreign_currency_id'   => $this->integerFromValue((string)$object['foreign_currency_id']),
 | 
			
		||||
                'foreign_currency_code' => $this->clearString((string)$object['foreign_currency_code']),
 | 
			
		||||
 | 
			
		||||
                // amount and foreign amount. Cannot be 0.
 | 
			
		||||
                'amount'                => $this->clearString((string)$object['amount']),
 | 
			
		||||
                'foreign_amount'        => $this->clearString((string)$object['foreign_amount']),
 | 
			
		||||
 | 
			
		||||
                // description.
 | 
			
		||||
                'description'           => $this->clearString($object['description']),
 | 
			
		||||
 | 
			
		||||
                // source of transaction. If everything is null, assume cash account.
 | 
			
		||||
                'source_id'             => $this->integerFromValue((string)$object['source_id']),
 | 
			
		||||
                'source_name'           => $this->clearString((string)$object['source_name']),
 | 
			
		||||
                'source_iban'           => $this->clearString((string)$object['source_iban']),
 | 
			
		||||
                'source_number'         => $this->clearString((string)$object['source_number']),
 | 
			
		||||
                'source_bic'            => $this->clearString((string)$object['source_bic']),
 | 
			
		||||
 | 
			
		||||
                // destination of transaction. If everything is null, assume cash account.
 | 
			
		||||
                'destination_id'        => $this->integerFromValue((string)$object['destination_id']),
 | 
			
		||||
                'destination_name'      => $this->clearString((string)$object['destination_name']),
 | 
			
		||||
                'destination_iban'      => $this->clearString((string)$object['destination_iban']),
 | 
			
		||||
                'destination_number'    => $this->clearString((string)$object['destination_number']),
 | 
			
		||||
                'destination_bic'       => $this->clearString((string)$object['destination_bic']),
 | 
			
		||||
 | 
			
		||||
                // budget info
 | 
			
		||||
                'budget_id'             => $this->integerFromValue((string)$object['budget_id']),
 | 
			
		||||
                'budget_name'           => $this->clearString((string)$object['budget_name']),
 | 
			
		||||
 | 
			
		||||
                // category info
 | 
			
		||||
                'category_id'           => $this->integerFromValue((string)$object['category_id']),
 | 
			
		||||
                'category_name'         => $this->clearString((string)$object['category_name']),
 | 
			
		||||
 | 
			
		||||
                // journal bill reference. Optional. Will only work for withdrawals
 | 
			
		||||
                'bill_id'               => $this->integerFromValue((string)$object['bill_id']),
 | 
			
		||||
                'bill_name'             => $this->clearString((string)$object['bill_name']),
 | 
			
		||||
 | 
			
		||||
                // piggy bank reference. Optional. Will only work for transfers
 | 
			
		||||
                'piggy_bank_id'         => $this->integerFromValue((string)$object['piggy_bank_id']),
 | 
			
		||||
                'piggy_bank_name'       => $this->clearString((string)$object['piggy_bank_name']),
 | 
			
		||||
 | 
			
		||||
                // some other interesting properties
 | 
			
		||||
                'reconciled'            => $this->convertBoolean((string)$object['reconciled']),
 | 
			
		||||
                'notes'                 => $this->clearStringKeepNewlines((string)$object['notes']),
 | 
			
		||||
                'tags'                  => $this->arrayFromValue($object['tags']),
 | 
			
		||||
 | 
			
		||||
                // all custom fields:
 | 
			
		||||
                'internal_reference'    => $this->clearString((string)$object['internal_reference']),
 | 
			
		||||
                'external_id'           => $this->clearString((string)$object['external_id']),
 | 
			
		||||
                'original_source'       => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')),
 | 
			
		||||
                'recurrence_id'         => $this->integerFromValue($object['recurrence_id']),
 | 
			
		||||
                'bunq_payment_id'       => $this->clearString((string)$object['bunq_payment_id']),
 | 
			
		||||
                'external_url'          => $this->clearString((string)$object['external_url']),
 | 
			
		||||
 | 
			
		||||
                'sepa_cc'               => $this->clearString((string)$object['sepa_cc']),
 | 
			
		||||
                'sepa_ct_op'            => $this->clearString((string)$object['sepa_ct_op']),
 | 
			
		||||
                'sepa_ct_id'            => $this->clearString((string)$object['sepa_ct_id']),
 | 
			
		||||
                'sepa_db'               => $this->clearString((string)$object['sepa_db']),
 | 
			
		||||
                'sepa_country'          => $this->clearString((string)$object['sepa_country']),
 | 
			
		||||
                'sepa_ep'               => $this->clearString((string)$object['sepa_ep']),
 | 
			
		||||
                'sepa_ci'               => $this->clearString((string)$object['sepa_ci']),
 | 
			
		||||
                'sepa_batch_id'         => $this->clearString((string)$object['sepa_batch_id']),
 | 
			
		||||
                // custom date fields. Must be Carbon objects. Presence is optional.
 | 
			
		||||
                'interest_date'         => $this->dateFromValue($object['interest_date']),
 | 
			
		||||
                'book_date'             => $this->dateFromValue($object['book_date']),
 | 
			
		||||
                'process_date'          => $this->dateFromValue($object['process_date']),
 | 
			
		||||
                'due_date'              => $this->dateFromValue($object['due_date']),
 | 
			
		||||
                'payment_date'          => $this->dateFromValue($object['payment_date']),
 | 
			
		||||
                'invoice_date'          => $this->dateFromValue($object['invoice_date']),
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -90,127 +90,6 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
        return $data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The rules that the incoming request must be matched against.
 | 
			
		||||
     */
 | 
			
		||||
    public function rules(): array
 | 
			
		||||
    {
 | 
			
		||||
        app('log')->debug(sprintf('Now in %s', __METHOD__));
 | 
			
		||||
        $validProtocols = config('firefly.valid_url_protocols');
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            // basic fields for group:
 | 
			
		||||
            'group_title'                            => 'min:1|max:1000|nullable',
 | 
			
		||||
            'apply_rules'                            => [new IsBoolean()],
 | 
			
		||||
 | 
			
		||||
            // transaction rules (in array for splits):
 | 
			
		||||
            'transactions.*.type'                    => 'in:withdrawal,deposit,transfer,opening-balance,reconciliation',
 | 
			
		||||
            'transactions.*.date'                    => [new IsDateOrTime()],
 | 
			
		||||
            'transactions.*.order'                   => 'numeric|min:0',
 | 
			
		||||
 | 
			
		||||
            // group id:
 | 
			
		||||
            'transactions.*.transaction_journal_id'  => ['nullable', 'numeric', new BelongsUser()],
 | 
			
		||||
 | 
			
		||||
            // currency info
 | 
			
		||||
            'transactions.*.currency_id'             => 'numeric|exists:transaction_currencies,id|nullable',
 | 
			
		||||
            'transactions.*.currency_code'           => 'min:3|max:51|exists:transaction_currencies,code|nullable',
 | 
			
		||||
            'transactions.*.foreign_currency_id'     => 'nullable|numeric|exists:transaction_currencies,id',
 | 
			
		||||
            'transactions.*.foreign_currency_code'   => 'nullable|min:3|max:51|exists:transaction_currencies,code',
 | 
			
		||||
 | 
			
		||||
            // amount
 | 
			
		||||
            'transactions.*.amount'                  => [new IsValidPositiveAmount()],
 | 
			
		||||
            'transactions.*.foreign_amount'          => ['nullable', new IsValidZeroOrMoreAmount()],
 | 
			
		||||
 | 
			
		||||
            // description
 | 
			
		||||
            'transactions.*.description'             => 'nullable|min:1|max:1000',
 | 
			
		||||
 | 
			
		||||
            // source of transaction
 | 
			
		||||
            'transactions.*.source_id'               => ['numeric', 'nullable', new BelongsUser()],
 | 
			
		||||
            'transactions.*.source_name'             => 'min:1|max:255|nullable',
 | 
			
		||||
 | 
			
		||||
            // destination of transaction
 | 
			
		||||
            'transactions.*.destination_id'          => ['numeric', 'nullable', new BelongsUser()],
 | 
			
		||||
            'transactions.*.destination_name'        => 'min:1|max:255|nullable',
 | 
			
		||||
 | 
			
		||||
            // budget, category, bill and piggy
 | 
			
		||||
            'transactions.*.budget_id'               => ['mustExist:budgets,id', new BelongsUser(), 'nullable'],
 | 
			
		||||
            'transactions.*.budget_name'             => ['min:1', 'max:255', 'nullable', new BelongsUser()],
 | 
			
		||||
            'transactions.*.category_id'             => ['mustExist:categories,id', new BelongsUser(), 'nullable'],
 | 
			
		||||
            'transactions.*.category_name'           => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.bill_id'                 => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()],
 | 
			
		||||
            'transactions.*.bill_name'               => ['min:1', 'max:255', 'nullable', new BelongsUser()],
 | 
			
		||||
 | 
			
		||||
            // other interesting fields
 | 
			
		||||
            'transactions.*.reconciled'              => [new IsBoolean()],
 | 
			
		||||
            'transactions.*.notes'                   => 'min:1|max:32768|nullable',
 | 
			
		||||
            'transactions.*.tags'                    => 'min:0|max:255|nullable',
 | 
			
		||||
            'transactions.*.tags.*'                  => 'min:0|max:255',
 | 
			
		||||
 | 
			
		||||
            // meta info fields
 | 
			
		||||
            'transactions.*.internal_reference'      => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.external_id'             => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.recurrence_id'           => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.bunq_payment_id'         => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.external_url'            => sprintf('min:1|max:255|nullable|url:%s', $validProtocols),
 | 
			
		||||
 | 
			
		||||
            // SEPA fields:
 | 
			
		||||
            'transactions.*.sepa_cc'                 => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ct_op'              => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ct_id'              => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_db'                 => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_country'            => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ep'                 => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ci'                 => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_batch_id'           => 'min:1|max:255|nullable',
 | 
			
		||||
 | 
			
		||||
            // dates
 | 
			
		||||
            'transactions.*.interest_date'           => 'date|nullable',
 | 
			
		||||
            'transactions.*.book_date'               => 'date|nullable',
 | 
			
		||||
            'transactions.*.process_date'            => 'date|nullable',
 | 
			
		||||
            'transactions.*.due_date'                => 'date|nullable',
 | 
			
		||||
            'transactions.*.payment_date'            => 'date|nullable',
 | 
			
		||||
            'transactions.*.invoice_date'            => 'date|nullable',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Configure the validator instance.
 | 
			
		||||
     */
 | 
			
		||||
    public function withValidator(Validator $validator): void
 | 
			
		||||
    {
 | 
			
		||||
        app('log')->debug('Now in withValidator');
 | 
			
		||||
 | 
			
		||||
        /** @var TransactionGroup $transactionGroup */
 | 
			
		||||
        $transactionGroup = $this->route()->parameter('transactionGroup');
 | 
			
		||||
        $validator->after(
 | 
			
		||||
            function (Validator $validator) use ($transactionGroup): void {
 | 
			
		||||
                // if more than one, verify that there are journal ID's present.
 | 
			
		||||
                $this->validateJournalIds($validator, $transactionGroup);
 | 
			
		||||
 | 
			
		||||
                // all transaction types must be equal:
 | 
			
		||||
                $this->validateTransactionTypesForUpdate($validator);
 | 
			
		||||
 | 
			
		||||
                // user wants to update a reconciled transaction.
 | 
			
		||||
                // source, destination, amount + foreign_amount cannot be changed
 | 
			
		||||
                // and must be omitted from the request.
 | 
			
		||||
                $this->preventUpdateReconciled($validator, $transactionGroup);
 | 
			
		||||
 | 
			
		||||
                // validate source/destination is equal, depending on the transaction journal type.
 | 
			
		||||
                $this->validateEqualAccountsForUpdate($validator, $transactionGroup);
 | 
			
		||||
 | 
			
		||||
                // see method:
 | 
			
		||||
                // $this->preventNoAccountInfo($validator, );
 | 
			
		||||
 | 
			
		||||
                // validate that the currency fits the source and/or destination account.
 | 
			
		||||
                // validate all account info
 | 
			
		||||
                $this->validateAccountInformationUpdate($validator, $transactionGroup);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get transaction data.
 | 
			
		||||
     *
 | 
			
		||||
@@ -258,7 +137,7 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
    {
 | 
			
		||||
        foreach ($this->integerFields as $fieldName) {
 | 
			
		||||
            if (array_key_exists($fieldName, $transaction)) {
 | 
			
		||||
                $current[$fieldName] = $this->integerFromValue((string) $transaction[$fieldName]);
 | 
			
		||||
                $current[$fieldName] = $this->integerFromValue((string)$transaction[$fieldName]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -273,7 +152,7 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
    {
 | 
			
		||||
        foreach ($this->stringFields as $fieldName) {
 | 
			
		||||
            if (array_key_exists($fieldName, $transaction)) {
 | 
			
		||||
                $current[$fieldName] = $this->clearString((string) $transaction[$fieldName]);
 | 
			
		||||
                $current[$fieldName] = $this->clearString((string)$transaction[$fieldName]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -288,7 +167,7 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
    {
 | 
			
		||||
        foreach ($this->textareaFields as $fieldName) {
 | 
			
		||||
            if (array_key_exists($fieldName, $transaction)) {
 | 
			
		||||
                $current[$fieldName] = $this->clearStringKeepNewlines((string) $transaction[$fieldName]); // keep newlines
 | 
			
		||||
                $current[$fieldName] = $this->clearStringKeepNewlines((string)$transaction[$fieldName]); // keep newlines
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -304,8 +183,8 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
        foreach ($this->dateFields as $fieldName) {
 | 
			
		||||
            app('log')->debug(sprintf('Now at date field %s', $fieldName));
 | 
			
		||||
            if (array_key_exists($fieldName, $transaction)) {
 | 
			
		||||
                app('log')->debug(sprintf('New value: "%s"', (string) $transaction[$fieldName]));
 | 
			
		||||
                $current[$fieldName] = $this->dateFromValue((string) $transaction[$fieldName]);
 | 
			
		||||
                app('log')->debug(sprintf('New value: "%s"', (string)$transaction[$fieldName]));
 | 
			
		||||
                $current[$fieldName] = $this->dateFromValue((string)$transaction[$fieldName]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -320,7 +199,7 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
    {
 | 
			
		||||
        foreach ($this->booleanFields as $fieldName) {
 | 
			
		||||
            if (array_key_exists($fieldName, $transaction)) {
 | 
			
		||||
                $current[$fieldName] = $this->convertBoolean((string) $transaction[$fieldName]);
 | 
			
		||||
                $current[$fieldName] = $this->convertBoolean((string)$transaction[$fieldName]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -355,11 +234,132 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
                    $current[$fieldName] = sprintf('%.12f', $value);
 | 
			
		||||
                }
 | 
			
		||||
                if (!is_float($value)) {
 | 
			
		||||
                    $current[$fieldName] = (string) $value;
 | 
			
		||||
                    $current[$fieldName] = (string)$value;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $current;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The rules that the incoming request must be matched against.
 | 
			
		||||
     */
 | 
			
		||||
    public function rules(): array
 | 
			
		||||
    {
 | 
			
		||||
        app('log')->debug(sprintf('Now in %s', __METHOD__));
 | 
			
		||||
        $validProtocols = config('firefly.valid_url_protocols');
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            // basic fields for group:
 | 
			
		||||
            'group_title'                           => 'min:1|max:1000|nullable',
 | 
			
		||||
            'apply_rules'                           => [new IsBoolean()],
 | 
			
		||||
 | 
			
		||||
            // transaction rules (in array for splits):
 | 
			
		||||
            'transactions.*.type'                   => 'in:withdrawal,deposit,transfer,opening-balance,reconciliation',
 | 
			
		||||
            'transactions.*.date'                   => [new IsDateOrTime()],
 | 
			
		||||
            'transactions.*.order'                  => 'numeric|min:0',
 | 
			
		||||
 | 
			
		||||
            // group id:
 | 
			
		||||
            'transactions.*.transaction_journal_id' => ['nullable', 'numeric', new BelongsUser()],
 | 
			
		||||
 | 
			
		||||
            // currency info
 | 
			
		||||
            'transactions.*.currency_id'            => 'numeric|exists:transaction_currencies,id|nullable',
 | 
			
		||||
            'transactions.*.currency_code'          => 'min:3|max:51|exists:transaction_currencies,code|nullable',
 | 
			
		||||
            'transactions.*.foreign_currency_id'    => 'nullable|numeric|exists:transaction_currencies,id',
 | 
			
		||||
            'transactions.*.foreign_currency_code'  => 'nullable|min:3|max:51|exists:transaction_currencies,code',
 | 
			
		||||
 | 
			
		||||
            // amount
 | 
			
		||||
            'transactions.*.amount'                 => [new IsValidPositiveAmount()],
 | 
			
		||||
            'transactions.*.foreign_amount'         => ['nullable', new IsValidZeroOrMoreAmount()],
 | 
			
		||||
 | 
			
		||||
            // description
 | 
			
		||||
            'transactions.*.description'            => 'nullable|min:1|max:1000',
 | 
			
		||||
 | 
			
		||||
            // source of transaction
 | 
			
		||||
            'transactions.*.source_id'              => ['numeric', 'nullable', new BelongsUser()],
 | 
			
		||||
            'transactions.*.source_name'            => 'min:1|max:255|nullable',
 | 
			
		||||
 | 
			
		||||
            // destination of transaction
 | 
			
		||||
            'transactions.*.destination_id'         => ['numeric', 'nullable', new BelongsUser()],
 | 
			
		||||
            'transactions.*.destination_name'       => 'min:1|max:255|nullable',
 | 
			
		||||
 | 
			
		||||
            // budget, category, bill and piggy
 | 
			
		||||
            'transactions.*.budget_id'              => ['mustExist:budgets,id', new BelongsUser(), 'nullable'],
 | 
			
		||||
            'transactions.*.budget_name'            => ['min:1', 'max:255', 'nullable', new BelongsUser()],
 | 
			
		||||
            'transactions.*.category_id'            => ['mustExist:categories,id', new BelongsUser(), 'nullable'],
 | 
			
		||||
            'transactions.*.category_name'          => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.bill_id'                => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()],
 | 
			
		||||
            'transactions.*.bill_name'              => ['min:1', 'max:255', 'nullable', new BelongsUser()],
 | 
			
		||||
 | 
			
		||||
            // other interesting fields
 | 
			
		||||
            'transactions.*.reconciled'             => [new IsBoolean()],
 | 
			
		||||
            'transactions.*.notes'                  => 'min:1|max:32768|nullable',
 | 
			
		||||
            'transactions.*.tags'                   => 'min:0|max:255|nullable',
 | 
			
		||||
            'transactions.*.tags.*'                 => 'min:0|max:255',
 | 
			
		||||
 | 
			
		||||
            // meta info fields
 | 
			
		||||
            'transactions.*.internal_reference'     => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.external_id'            => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.recurrence_id'          => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.bunq_payment_id'        => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.external_url'           => sprintf('min:1|max:255|nullable|url:%s', $validProtocols),
 | 
			
		||||
 | 
			
		||||
            // SEPA fields:
 | 
			
		||||
            'transactions.*.sepa_cc'                => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ct_op'             => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ct_id'             => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_db'                => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_country'           => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ep'                => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_ci'                => 'min:1|max:255|nullable',
 | 
			
		||||
            'transactions.*.sepa_batch_id'          => 'min:1|max:255|nullable',
 | 
			
		||||
 | 
			
		||||
            // dates
 | 
			
		||||
            'transactions.*.interest_date'          => 'date|nullable',
 | 
			
		||||
            'transactions.*.book_date'              => 'date|nullable',
 | 
			
		||||
            'transactions.*.process_date'           => 'date|nullable',
 | 
			
		||||
            'transactions.*.due_date'               => 'date|nullable',
 | 
			
		||||
            'transactions.*.payment_date'           => 'date|nullable',
 | 
			
		||||
            'transactions.*.invoice_date'           => 'date|nullable',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Configure the validator instance.
 | 
			
		||||
     */
 | 
			
		||||
    public function withValidator(Validator $validator): void
 | 
			
		||||
    {
 | 
			
		||||
        app('log')->debug('Now in withValidator');
 | 
			
		||||
 | 
			
		||||
        /** @var TransactionGroup $transactionGroup */
 | 
			
		||||
        $transactionGroup = $this->route()->parameter('transactionGroup');
 | 
			
		||||
        $validator->after(
 | 
			
		||||
            function (Validator $validator) use ($transactionGroup): void {
 | 
			
		||||
                // if more than one, verify that there are journal ID's present.
 | 
			
		||||
                $this->validateJournalIds($validator, $transactionGroup);
 | 
			
		||||
 | 
			
		||||
                // all transaction types must be equal:
 | 
			
		||||
                $this->validateTransactionTypesForUpdate($validator);
 | 
			
		||||
 | 
			
		||||
                // user wants to update a reconciled transaction.
 | 
			
		||||
                // source, destination, amount + foreign_amount cannot be changed
 | 
			
		||||
                // and must be omitted from the request.
 | 
			
		||||
                $this->preventUpdateReconciled($validator, $transactionGroup);
 | 
			
		||||
 | 
			
		||||
                // validate source/destination is equal, depending on the transaction journal type.
 | 
			
		||||
                $this->validateEqualAccountsForUpdate($validator, $transactionGroup);
 | 
			
		||||
 | 
			
		||||
                // see method:
 | 
			
		||||
                // $this->preventNoAccountInfo($validator, );
 | 
			
		||||
 | 
			
		||||
                // validate that the currency fits the source and/or destination account.
 | 
			
		||||
                // validate all account info
 | 
			
		||||
                $this->validateAccountInformationUpdate($validator, $transactionGroup);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@ class StoreRequest extends FormRequest
 | 
			
		||||
                $this->validateExistingLink($validator);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@ class UpdateRequest extends FormRequest
 | 
			
		||||
                $this->validateUpdate($validator);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -98,7 +98,7 @@ class UserUpdateRequest extends FormRequest
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if($validator->fails()) {
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -28,22 +28,21 @@ use FireflyIII\Api\V2\Controllers\Controller;
 | 
			
		||||
use FireflyIII\Api\V2\Request\Autocomplete\AutocompleteRequest;
 | 
			
		||||
use FireflyIII\Exceptions\FireflyException;
 | 
			
		||||
use FireflyIII\Models\Account;
 | 
			
		||||
use FireflyIII\Models\AccountType;
 | 
			
		||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
 | 
			
		||||
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
 | 
			
		||||
use FireflyIII\Support\Http\Api\AccountFilter;
 | 
			
		||||
use FireflyIII\Models\AccountBalance;
 | 
			
		||||
use FireflyIII\Models\TransactionCurrency;
 | 
			
		||||
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
 | 
			
		||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
 | 
			
		||||
use Illuminate\Http\JsonResponse;
 | 
			
		||||
use Illuminate\Support\Facades\Log;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class AccountController
 | 
			
		||||
 */
 | 
			
		||||
class AccountController extends Controller
 | 
			
		||||
{
 | 
			
		||||
    use AccountFilter;
 | 
			
		||||
 | 
			
		||||
    private AdminAccountRepositoryInterface $adminRepository;
 | 
			
		||||
    private array                           $balanceTypes;
 | 
			
		||||
    private AccountRepositoryInterface      $repository;
 | 
			
		||||
    private AccountRepositoryInterface $repository;
 | 
			
		||||
    private TransactionCurrency        $default;
 | 
			
		||||
    private ExchangeRateConverter      $converter;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * AccountController constructor.
 | 
			
		||||
@@ -53,83 +52,89 @@ class AccountController extends Controller
 | 
			
		||||
        parent::__construct();
 | 
			
		||||
        $this->middleware(
 | 
			
		||||
            function ($request, $next) {
 | 
			
		||||
                $this->repository      = app(AccountRepositoryInterface::class);
 | 
			
		||||
                $this->adminRepository = app(AdminAccountRepositoryInterface::class);
 | 
			
		||||
 | 
			
		||||
                $userGroup             = $this->validateUserGroup($request);
 | 
			
		||||
                if (null !== $userGroup) {
 | 
			
		||||
                    $this->adminRepository->setUserGroup($userGroup);
 | 
			
		||||
                }
 | 
			
		||||
                $userGroup        = $this->validateUserGroup($request);
 | 
			
		||||
                $this->repository = app(AccountRepositoryInterface::class);
 | 
			
		||||
                $this->repository->setUserGroup($userGroup);
 | 
			
		||||
                $this->default    = app('amount')->getDefaultCurrency();
 | 
			
		||||
                $this->converter  = app(ExchangeRateConverter::class);
 | 
			
		||||
 | 
			
		||||
                return $next($request);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        $this->balanceTypes = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Documentation for this endpoint:
 | 
			
		||||
     * TODO list of checks
 | 
			
		||||
     * 1. use dates from ParameterBag
 | 
			
		||||
     * 2. Request validates dates
 | 
			
		||||
     * 3. Request includes user_group_id
 | 
			
		||||
     * 4. Endpoint is documented.
 | 
			
		||||
     * 5. Collector uses user_group_id
 | 
			
		||||
     *
 | 
			
		||||
     * @throws FireflyException
 | 
			
		||||
     * @throws FireflyException
 | 
			
		||||
     * Documentation: https://api-docs.firefly-iii.org/?urls.primaryName=2.1.0%20(v2)#/autocomplete/getAccountsAC
 | 
			
		||||
     */
 | 
			
		||||
    public function accounts(AutocompleteRequest $request): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        $data            = $request->getData();
 | 
			
		||||
        $types           = $data['types'];
 | 
			
		||||
        $query           = $data['query'];
 | 
			
		||||
        $date            = $this->parameters->get('date') ?? today(config('app.timezone'));
 | 
			
		||||
        $result          = $this->adminRepository->searchAccount((string)$query, $types, $data['limit']);
 | 
			
		||||
        $defaultCurrency = app('amount')->getDefaultCurrency();
 | 
			
		||||
        $groupedResult   = [];
 | 
			
		||||
        $allItems        = [];
 | 
			
		||||
        $params = $request->getParameters();
 | 
			
		||||
        $result = $this->repository->searchAccount($params['query'], $params['account_types'], $params['page'], $params['size']);
 | 
			
		||||
        $return = [];
 | 
			
		||||
 | 
			
		||||
        /** @var Account $account */
 | 
			
		||||
        foreach ($result as $account) {
 | 
			
		||||
            $nameWithBalance = $account->name;
 | 
			
		||||
            $currency        = $this->repository->getAccountCurrency($account) ?? $defaultCurrency;
 | 
			
		||||
 | 
			
		||||
            if (in_array($account->accountType->type, $this->balanceTypes, true)) {
 | 
			
		||||
                $balance         = app('steam')->balance($account, $date);
 | 
			
		||||
                $nameWithBalance = sprintf('%s (%s)', $account->name, app('amount')->formatAnything($currency, $balance, false));
 | 
			
		||||
            }
 | 
			
		||||
            $type            = (string)trans(sprintf('firefly.%s', $account->accountType->type));
 | 
			
		||||
            $groupedResult[$type] ??= [
 | 
			
		||||
                'group ' => $type,
 | 
			
		||||
                'items'  => [],
 | 
			
		||||
            ];
 | 
			
		||||
            $allItems[]      = [
 | 
			
		||||
                'id'                      => (string)$account->id,
 | 
			
		||||
                'value'                   => (string)$account->id,
 | 
			
		||||
                'name'                    => $account->name,
 | 
			
		||||
                'name_with_balance'       => $nameWithBalance,
 | 
			
		||||
                'label'                   => $nameWithBalance,
 | 
			
		||||
                'type'                    => $account->accountType->type,
 | 
			
		||||
                'currency_id'             => (string)$currency->id,
 | 
			
		||||
                'currency_name'           => $currency->name,
 | 
			
		||||
                'currency_code'           => $currency->code,
 | 
			
		||||
                'currency_symbol'         => $currency->symbol,
 | 
			
		||||
                'currency_decimal_places' => $currency->decimal_places,
 | 
			
		||||
            ];
 | 
			
		||||
            $return[] = $this->parseAccount($account);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        usort(
 | 
			
		||||
            $allItems,
 | 
			
		||||
            static function (array $left, array $right): int {
 | 
			
		||||
                $order    = [AccountType::ASSET, AccountType::REVENUE, AccountType::EXPENSE];
 | 
			
		||||
                $posLeft  = (int)array_search($left['type'], $order, true);
 | 
			
		||||
                $posRight = (int)array_search($right['type'], $order, true);
 | 
			
		||||
        return response()->json($return);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
                return $posLeft - $posRight;
 | 
			
		||||
    private function parseAccount(Account $account): array
 | 
			
		||||
    {
 | 
			
		||||
        $currency = $this->repository->getAccountCurrency($account);
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            'id'    => (string) $account->id,
 | 
			
		||||
            'title' => $account->name,
 | 
			
		||||
            'meta'  => [
 | 
			
		||||
                'type'                    => $account->accountType->type,
 | 
			
		||||
                // TODO is multi currency property.
 | 
			
		||||
                'currency_id'             => null === $currency ? null : (string) $currency->id,
 | 
			
		||||
                'currency_code'           => $currency?->code,
 | 
			
		||||
                'currency_symbol'         => $currency?->symbol,
 | 
			
		||||
                'currency_decimal_places' => $currency?->decimal_places,
 | 
			
		||||
                'account_balances'        => $this->getAccountBalances($account),
 | 
			
		||||
            ],
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getAccountBalances(Account $account): array
 | 
			
		||||
    {
 | 
			
		||||
        $return   = [];
 | 
			
		||||
        $balances = $this->repository->getAccountBalances($account);
 | 
			
		||||
 | 
			
		||||
        /** @var AccountBalance $balance */
 | 
			
		||||
        foreach ($balances as $balance) {
 | 
			
		||||
            try {
 | 
			
		||||
                $return[] = $this->parseAccountBalance($balance);
 | 
			
		||||
            } catch (FireflyException $e) {
 | 
			
		||||
                Log::error(sprintf('Could not parse convert account balance: %s', $e->getMessage()));
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return response()->json($allItems);
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws FireflyException
 | 
			
		||||
     */
 | 
			
		||||
    private function parseAccountBalance(AccountBalance $balance): array
 | 
			
		||||
    {
 | 
			
		||||
        $currency = $balance->transactionCurrency;
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            'title'                          => $balance->title,
 | 
			
		||||
            'native_amount'                  => $this->converter->convert($currency, $this->default, today(), $balance->balance),
 | 
			
		||||
            'amount'                         => app('steam')->bcround($balance->balance, $currency->decimal_places),
 | 
			
		||||
            'currency_id'                    => (string) $currency->id,
 | 
			
		||||
            'currency_code'                  => $currency->code,
 | 
			
		||||
            'currency_symbol'                => $currency->symbol,
 | 
			
		||||
            'currency_decimal_places'        => $currency->decimal_places,
 | 
			
		||||
            'native_currency_id'             => (string) $this->default->id,
 | 
			
		||||
            'native_currency_code'           => $this->default->code,
 | 
			
		||||
            'native_currency_symbol'         => $this->default->symbol,
 | 
			
		||||
            'native_currency_decimal_places' => $this->default->decimal_places,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -45,11 +45,7 @@ class CategoryController extends Controller
 | 
			
		||||
        $this->middleware(
 | 
			
		||||
            function ($request, $next) {
 | 
			
		||||
                $this->repository = app(CategoryRepositoryInterface::class);
 | 
			
		||||
 | 
			
		||||
                $userGroup        = $this->validateUserGroup($request);
 | 
			
		||||
                if (null !== $userGroup) {
 | 
			
		||||
                    $this->repository->setUserGroup($userGroup);
 | 
			
		||||
                }
 | 
			
		||||
                $this->repository->setUserGroup($this->validateUserGroup($request));
 | 
			
		||||
 | 
			
		||||
                return $next($request);
 | 
			
		||||
            }
 | 
			
		||||
@@ -57,23 +53,18 @@ class CategoryController extends Controller
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *  Documentation for this endpoint:
 | 
			
		||||
     *  TODO list of checks
 | 
			
		||||
     *  1. use dates from ParameterBag
 | 
			
		||||
     *  2. Request validates dates
 | 
			
		||||
     *  3. Request includes user_group_id
 | 
			
		||||
     *  4. Endpoint is documented.
 | 
			
		||||
     *  5. Collector uses user_group_id
 | 
			
		||||
     * Documentation: https://api-docs.firefly-iii.org/?urls.primaryName=2.1.0%20(v2)#/autocomplete/getCategoriesAC
 | 
			
		||||
     */
 | 
			
		||||
    public function categories(AutocompleteRequest $request): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        $data     = $request->getData();
 | 
			
		||||
        $result   = $this->repository->searchCategory($data['query'], $this->parameters->get('limit'));
 | 
			
		||||
        $filtered = $result->map(
 | 
			
		||||
        $queryParameters = $request->getParameters();
 | 
			
		||||
        $result          = $this->repository->searchCategory($queryParameters['query'], $queryParameters['size']);
 | 
			
		||||
        $filtered        = $result->map(
 | 
			
		||||
            static function (Category $item) {
 | 
			
		||||
                return [
 | 
			
		||||
                    'id'   => (string) $item->id,
 | 
			
		||||
                    'name' => $item->name,
 | 
			
		||||
                    'id'    => (string)$item->id,
 | 
			
		||||
                    'title' => $item->name,
 | 
			
		||||
                    'meta'  => [],
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
 
 | 
			
		||||
@@ -45,11 +45,7 @@ class TagController extends Controller
 | 
			
		||||
        $this->middleware(
 | 
			
		||||
            function ($request, $next) {
 | 
			
		||||
                $this->repository = app(TagRepositoryInterface::class);
 | 
			
		||||
 | 
			
		||||
                $userGroup        = $this->validateUserGroup($request);
 | 
			
		||||
                if (null !== $userGroup) {
 | 
			
		||||
                    $this->repository->setUserGroup($userGroup);
 | 
			
		||||
                }
 | 
			
		||||
                $this->repository->setUserGroup($this->validateUserGroup($request));
 | 
			
		||||
 | 
			
		||||
                return $next($request);
 | 
			
		||||
            }
 | 
			
		||||
@@ -57,25 +53,20 @@ class TagController extends Controller
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *  Documentation for this endpoint:
 | 
			
		||||
     *  TODO list of checks
 | 
			
		||||
     *  1. use dates from ParameterBag
 | 
			
		||||
     *  2. Request validates dates
 | 
			
		||||
     *  3. Request includes user_group_id
 | 
			
		||||
     *  4. Endpoint is documented.
 | 
			
		||||
     *  5. Collector uses user_group_id
 | 
			
		||||
     * Documentation: https://api-docs.firefly-iii.org/?urls.primaryName=2.1.0%20(v2)#/autocomplete/getTagsAC
 | 
			
		||||
     */
 | 
			
		||||
    public function tags(AutocompleteRequest $request): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        $data     = $request->getData();
 | 
			
		||||
        $result   = $this->repository->searchTag($data['query'], $data['limit']);
 | 
			
		||||
        $filtered = $result->map(
 | 
			
		||||
        $queryParameters = $request->getParameters();
 | 
			
		||||
        $result          = $this->repository->searchTag($queryParameters['query'], $queryParameters['size']);
 | 
			
		||||
        $filtered        = $result->map(
 | 
			
		||||
            static function (Tag $item) {
 | 
			
		||||
                return [
 | 
			
		||||
                    'id'      => (string) $item->id,
 | 
			
		||||
                    'name'    => $item->tag,
 | 
			
		||||
                    'value'   => (string) $item->id,
 | 
			
		||||
                    'label'   => $item->tag,
 | 
			
		||||
                    'id'    => (string) $item->id,
 | 
			
		||||
                    'title' => $item->tag,
 | 
			
		||||
                    'value' => (string) $item->id,
 | 
			
		||||
                    'label' => $item->tag,
 | 
			
		||||
                    'meta'  => [],
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
 
 | 
			
		||||
@@ -45,11 +45,7 @@ class TransactionController extends Controller
 | 
			
		||||
        $this->middleware(
 | 
			
		||||
            function ($request, $next) {
 | 
			
		||||
                $this->repository = app(JournalRepositoryInterface::class);
 | 
			
		||||
 | 
			
		||||
                $userGroup        = $this->validateUserGroup($request);
 | 
			
		||||
                if (null !== $userGroup) {
 | 
			
		||||
                    $this->repository->setUserGroup($userGroup);
 | 
			
		||||
                }
 | 
			
		||||
                $this->repository->setUserGroup($this->validateUserGroup($request));
 | 
			
		||||
 | 
			
		||||
                return $next($request);
 | 
			
		||||
            }
 | 
			
		||||
@@ -57,30 +53,25 @@ class TransactionController extends Controller
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *  Documentation for this endpoint:
 | 
			
		||||
     *  TODO list of checks
 | 
			
		||||
     *  1. use dates from ParameterBag
 | 
			
		||||
     *  2. Request validates dates
 | 
			
		||||
     *  3. Request includes user_group_id
 | 
			
		||||
     *  4. Endpoint is documented.
 | 
			
		||||
     *  5. Collector uses user_group_id
 | 
			
		||||
     * Documentation: https://api-docs.firefly-iii.org/?urls.primaryName=2.1.0%20(v2)#/autocomplete/getTransactionsAC
 | 
			
		||||
     */
 | 
			
		||||
    public function transactionDescriptions(AutocompleteRequest $request): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        $data     = $request->getData();
 | 
			
		||||
        $result   = $this->repository->searchJournalDescriptions($data['query'], $data['limit']);
 | 
			
		||||
        $queryParameters = $request->getParameters();
 | 
			
		||||
        $result          = $this->repository->searchJournalDescriptions($queryParameters['query'], $queryParameters['size']);
 | 
			
		||||
 | 
			
		||||
        // limit and unique
 | 
			
		||||
        $filtered = $result->unique('description');
 | 
			
		||||
        $array    = [];
 | 
			
		||||
        $filtered        = $result->unique('description');
 | 
			
		||||
        $array           = [];
 | 
			
		||||
 | 
			
		||||
        /** @var TransactionJournal $journal */
 | 
			
		||||
        foreach ($filtered as $journal) {
 | 
			
		||||
            $array[] = [
 | 
			
		||||
                'id'                   => (string)$journal->id,
 | 
			
		||||
                'transaction_group_id' => (string)$journal->transaction_group_id,
 | 
			
		||||
                'name'                 => $journal->description,
 | 
			
		||||
                'description'          => $journal->description,
 | 
			
		||||
                'id'    => (string) $journal->id,
 | 
			
		||||
                'title' => $journal->description,
 | 
			
		||||
                'meta'  => [
 | 
			
		||||
                    'transaction_group_id' => (string) $journal->transaction_group_id,
 | 
			
		||||
                ],
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,18 +24,17 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace FireflyIII\Api\V2\Controllers\Chart;
 | 
			
		||||
 | 
			
		||||
use Carbon\Carbon;
 | 
			
		||||
use FireflyIII\Api\V2\Controllers\Controller;
 | 
			
		||||
use FireflyIII\Api\V2\Request\Chart\DashboardChartRequest;
 | 
			
		||||
use FireflyIII\Api\V2\Request\Chart\ChartRequest;
 | 
			
		||||
use FireflyIII\Exceptions\FireflyException;
 | 
			
		||||
use FireflyIII\Models\Account;
 | 
			
		||||
use FireflyIII\Models\AccountType;
 | 
			
		||||
use FireflyIII\Models\TransactionCurrency;
 | 
			
		||||
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
 | 
			
		||||
use FireflyIII\Support\Chart\ChartData;
 | 
			
		||||
use FireflyIII\Support\Http\Api\CleansChartData;
 | 
			
		||||
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
 | 
			
		||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
 | 
			
		||||
use Illuminate\Http\JsonResponse;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class AccountController
 | 
			
		||||
@@ -43,9 +42,12 @@ use Illuminate\Support\Collection;
 | 
			
		||||
class AccountController extends Controller
 | 
			
		||||
{
 | 
			
		||||
    use CleansChartData;
 | 
			
		||||
    use CollectsAccountsFromFilter;
 | 
			
		||||
    use ValidatesUserGroupTrait;
 | 
			
		||||
 | 
			
		||||
    private AccountRepositoryInterface $repository;
 | 
			
		||||
    private ChartData                  $chartData;
 | 
			
		||||
    private TransactionCurrency        $default;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
@@ -53,10 +55,9 @@ class AccountController extends Controller
 | 
			
		||||
        $this->middleware(
 | 
			
		||||
            function ($request, $next) {
 | 
			
		||||
                $this->repository = app(AccountRepositoryInterface::class);
 | 
			
		||||
                $userGroup        = $this->validateUserGroup($request);
 | 
			
		||||
                if (null !== $userGroup) {
 | 
			
		||||
                    $this->repository->setUserGroup($userGroup);
 | 
			
		||||
                }
 | 
			
		||||
                $this->repository->setUserGroup($this->validateUserGroup($request));
 | 
			
		||||
                $this->chartData  = new ChartData();
 | 
			
		||||
                $this->default    = app('amount')->getDefaultCurrency();
 | 
			
		||||
 | 
			
		||||
                return $next($request);
 | 
			
		||||
            }
 | 
			
		||||
@@ -64,107 +65,76 @@ class AccountController extends Controller
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This endpoint is documented at
 | 
			
		||||
     * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v2)#/charts/getChartAccountOverview
 | 
			
		||||
     *
 | 
			
		||||
     * The native currency is the preferred currency on the page /currencies.
 | 
			
		||||
     *
 | 
			
		||||
     * If a transaction has foreign currency = native currency, the foreign amount will be used, no conversion
 | 
			
		||||
     * will take place.
 | 
			
		||||
     *
 | 
			
		||||
     * TODO validate and set user_group_id from request
 | 
			
		||||
     * TODO fix documentation
 | 
			
		||||
     *
 | 
			
		||||
     * @throws FireflyException
 | 
			
		||||
     *
 | 
			
		||||
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
 | 
			
		||||
     */
 | 
			
		||||
    public function dashboard(DashboardChartRequest $request): JsonResponse
 | 
			
		||||
    public function dashboard(ChartRequest $request): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        /** @var Carbon $start */
 | 
			
		||||
        $start     = $this->parameters->get('start');
 | 
			
		||||
        $queryParameters = $request->getParameters();
 | 
			
		||||
        $accounts        = $this->getAccountList($queryParameters);
 | 
			
		||||
 | 
			
		||||
        /** @var Carbon $end */
 | 
			
		||||
        $end       = $this->parameters->get('end');
 | 
			
		||||
        $end->endOfDay();
 | 
			
		||||
 | 
			
		||||
        /** @var TransactionCurrency $default */
 | 
			
		||||
        $default   = app('amount')->getDefaultCurrency();
 | 
			
		||||
        $params    = $request->getAll();
 | 
			
		||||
 | 
			
		||||
        /** @var Collection $accounts */
 | 
			
		||||
        $accounts  = $params['accounts'];
 | 
			
		||||
        $chartData = [];
 | 
			
		||||
 | 
			
		||||
        // user's preferences
 | 
			
		||||
        if (0 === $accounts->count()) {
 | 
			
		||||
            $defaultSet = $this->repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT])->pluck('id')->toArray();
 | 
			
		||||
            $frontPage  = app('preferences')->get('frontPageAccounts', $defaultSet);
 | 
			
		||||
 | 
			
		||||
            if (!(is_array($frontPage->data) && count($frontPage->data) > 0)) {
 | 
			
		||||
                $frontPage->data = $defaultSet;
 | 
			
		||||
                $frontPage->save();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $accounts   = $this->repository->getAccountsById($frontPage->data);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // both options are overruled by "preselected"
 | 
			
		||||
        if ('all' === $params['preselected']) {
 | 
			
		||||
            $accounts = $this->repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]);
 | 
			
		||||
        }
 | 
			
		||||
        if ('assets' === $params['preselected']) {
 | 
			
		||||
            $accounts = $this->repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
 | 
			
		||||
        }
 | 
			
		||||
        if ('liabilities' === $params['preselected']) {
 | 
			
		||||
            $accounts = $this->repository->getAccountsByType([AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]);
 | 
			
		||||
        }
 | 
			
		||||
        // move date to end of day
 | 
			
		||||
        $queryParameters['start']->startOfDay();
 | 
			
		||||
        $queryParameters['end']->endOfDay();
 | 
			
		||||
 | 
			
		||||
        // loop each account, and collect info:
 | 
			
		||||
        /** @var Account $account */
 | 
			
		||||
        foreach ($accounts as $account) {
 | 
			
		||||
            $currency          = $this->repository->getAccountCurrency($account);
 | 
			
		||||
            if (null === $currency) {
 | 
			
		||||
                $currency = $default;
 | 
			
		||||
            }
 | 
			
		||||
            $currentSet        = [
 | 
			
		||||
                'label'                            => $account->name,
 | 
			
		||||
                // the currency that belongs to the account.
 | 
			
		||||
                'currency_id'                      => (string)$currency->id,
 | 
			
		||||
                'currency_code'                    => $currency->code,
 | 
			
		||||
                'currency_symbol'                  => $currency->symbol,
 | 
			
		||||
                'currency_decimal_places'          => $currency->decimal_places,
 | 
			
		||||
 | 
			
		||||
                // the default currency of the user (could be the same!)
 | 
			
		||||
                'native_currency_id'               => (string)$default->id,
 | 
			
		||||
                'native_currency_code'             => $default->code,
 | 
			
		||||
                'native_currency_symbol'           => $default->symbol,
 | 
			
		||||
                'native_currency_decimal_places'   => $default->decimal_places,
 | 
			
		||||
                'start'                            => $start->toAtomString(),
 | 
			
		||||
                'end'                              => $end->toAtomString(),
 | 
			
		||||
                'period'                           => '1D',
 | 
			
		||||
                'entries'                          => [],
 | 
			
		||||
                'native_entries'                   => [],
 | 
			
		||||
            ];
 | 
			
		||||
            $currentStart      = clone $start;
 | 
			
		||||
            $range             = app('steam')->balanceInRange($account, $start, clone $end, $currency);
 | 
			
		||||
            $rangeConverted    = app('steam')->balanceInRangeConverted($account, $start, clone $end, $default);
 | 
			
		||||
 | 
			
		||||
            $previous          = array_values($range)[0];
 | 
			
		||||
            $previousConverted = array_values($rangeConverted)[0];
 | 
			
		||||
            while ($currentStart <= $end) {
 | 
			
		||||
                $format                               = $currentStart->format('Y-m-d');
 | 
			
		||||
                $label                                = $currentStart->toAtomString();
 | 
			
		||||
                $balance                              = array_key_exists($format, $range) ? $range[$format] : $previous;
 | 
			
		||||
                $balanceConverted                     = array_key_exists($format, $rangeConverted) ? $rangeConverted[$format] : $previousConverted;
 | 
			
		||||
                $previous                             = $balance;
 | 
			
		||||
                $previousConverted                    = $balanceConverted;
 | 
			
		||||
 | 
			
		||||
                $currentStart->addDay();
 | 
			
		||||
                $currentSet['entries'][$label]        = $balance;
 | 
			
		||||
                $currentSet['native_entries'][$label] = $balanceConverted;
 | 
			
		||||
            }
 | 
			
		||||
            $chartData[]       = $currentSet;
 | 
			
		||||
            $this->renderAccountData($queryParameters, $account);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return response()->json($this->clean($chartData));
 | 
			
		||||
        return response()->json($this->chartData->render());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws FireflyException
 | 
			
		||||
     */
 | 
			
		||||
    private function renderAccountData(array $params, Account $account): void
 | 
			
		||||
    {
 | 
			
		||||
        $currency          = $this->repository->getAccountCurrency($account);
 | 
			
		||||
        if (null === $currency) {
 | 
			
		||||
            $currency = $this->default;
 | 
			
		||||
        }
 | 
			
		||||
        $currentSet        = [
 | 
			
		||||
            'label'                          => $account->name,
 | 
			
		||||
 | 
			
		||||
            // the currency that belongs to the account.
 | 
			
		||||
            'currency_id'                    => (string) $currency->id,
 | 
			
		||||
            'currency_code'                  => $currency->code,
 | 
			
		||||
            'currency_symbol'                => $currency->symbol,
 | 
			
		||||
            'currency_decimal_places'        => $currency->decimal_places,
 | 
			
		||||
 | 
			
		||||
            // the default currency of the user (could be the same!)
 | 
			
		||||
            'native_currency_id'             => (string) $this->default->id,
 | 
			
		||||
            'native_currency_code'           => $this->default->code,
 | 
			
		||||
            'native_currency_symbol'         => $this->default->symbol,
 | 
			
		||||
            'native_currency_decimal_places' => $this->default->decimal_places,
 | 
			
		||||
            'date'                           => $params['start']->toAtomString(),
 | 
			
		||||
            'start'                          => $params['start']->toAtomString(),
 | 
			
		||||
            'end'                            => $params['end']->toAtomString(),
 | 
			
		||||
            'period'                         => '1D',
 | 
			
		||||
            'entries'                        => [],
 | 
			
		||||
            'native_entries'                 => [],
 | 
			
		||||
        ];
 | 
			
		||||
        $currentStart      = clone $params['start'];
 | 
			
		||||
        $range             = app('steam')->balanceInRange($account, $params['start'], clone $params['end'], $currency);
 | 
			
		||||
        $rangeConverted    = app('steam')->balanceInRangeConverted($account, $params['start'], clone $params['end'], $this->default);
 | 
			
		||||
 | 
			
		||||
        $previous          = array_values($range)[0];
 | 
			
		||||
        $previousConverted = array_values($rangeConverted)[0];
 | 
			
		||||
        while ($currentStart <= $params['end']) {
 | 
			
		||||
            $format                               = $currentStart->format('Y-m-d');
 | 
			
		||||
            $label                                = $currentStart->toAtomString();
 | 
			
		||||
            $balance                              = array_key_exists($format, $range) ? $range[$format] : $previous;
 | 
			
		||||
            $balanceConverted                     = array_key_exists($format, $rangeConverted) ? $rangeConverted[$format] : $previousConverted;
 | 
			
		||||
            $previous                             = $balance;
 | 
			
		||||
            $previousConverted                    = $balanceConverted;
 | 
			
		||||
 | 
			
		||||
            $currentStart->addDay();
 | 
			
		||||
            $currentSet['entries'][$label]        = $balance;
 | 
			
		||||
            $currentSet['native_entries'][$label] = $balanceConverted;
 | 
			
		||||
        }
 | 
			
		||||
        $this->chartData->add($currentSet);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,17 +24,18 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace FireflyIII\Api\V2\Controllers\Chart;
 | 
			
		||||
 | 
			
		||||
use Carbon\Carbon;
 | 
			
		||||
use FireflyIII\Api\V2\Controllers\Controller;
 | 
			
		||||
use FireflyIII\Api\V2\Request\Chart\BalanceChartRequest;
 | 
			
		||||
use FireflyIII\Api\V2\Request\Chart\ChartRequest;
 | 
			
		||||
use FireflyIII\Exceptions\FireflyException;
 | 
			
		||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
 | 
			
		||||
use FireflyIII\Models\TransactionCurrency;
 | 
			
		||||
use FireflyIII\Models\TransactionType;
 | 
			
		||||
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
 | 
			
		||||
use FireflyIII\Support\Chart\ChartData;
 | 
			
		||||
use FireflyIII\Support\Http\Api\AccountBalanceGrouped;
 | 
			
		||||
use FireflyIII\Support\Http\Api\CleansChartData;
 | 
			
		||||
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
 | 
			
		||||
use Illuminate\Http\JsonResponse;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class BalanceController
 | 
			
		||||
@@ -42,6 +43,30 @@ use Illuminate\Support\Collection;
 | 
			
		||||
class BalanceController extends Controller
 | 
			
		||||
{
 | 
			
		||||
    use CleansChartData;
 | 
			
		||||
    use CollectsAccountsFromFilter;
 | 
			
		||||
 | 
			
		||||
    private AccountRepositoryInterface $repository;
 | 
			
		||||
    private GroupCollectorInterface    $collector;
 | 
			
		||||
    private ChartData                  $chartData;
 | 
			
		||||
    // private TransactionCurrency        $default;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct();
 | 
			
		||||
        $this->middleware(
 | 
			
		||||
            function ($request, $next) {
 | 
			
		||||
                $this->repository = app(AccountRepositoryInterface::class);
 | 
			
		||||
                $this->collector  = app(GroupCollectorInterface::class);
 | 
			
		||||
                $userGroup        = $this->validateUserGroup($request);
 | 
			
		||||
                $this->repository->setUserGroup($userGroup);
 | 
			
		||||
                $this->collector->setUserGroup($userGroup);
 | 
			
		||||
                $this->chartData  = new ChartData();
 | 
			
		||||
                // $this->default    = app('amount')->getDefaultCurrency();
 | 
			
		||||
 | 
			
		||||
                return $next($request);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The code is practically a duplicate of ReportController::operations.
 | 
			
		||||
@@ -52,50 +77,39 @@ class BalanceController extends Controller
 | 
			
		||||
     * If the transaction being processed is already in native currency OR if the
 | 
			
		||||
     * foreign amount is in the native currency, the amount will not be converted.
 | 
			
		||||
     *
 | 
			
		||||
     * TODO validate and set user_group_id
 | 
			
		||||
     * TODO collector set group, not user
 | 
			
		||||
     *
 | 
			
		||||
     * @throws FireflyException
 | 
			
		||||
     */
 | 
			
		||||
    public function balance(BalanceChartRequest $request): JsonResponse
 | 
			
		||||
    public function balance(ChartRequest $request): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        $params         = $request->getAll();
 | 
			
		||||
 | 
			
		||||
        /** @var Carbon $start */
 | 
			
		||||
        $start          = $this->parameters->get('start');
 | 
			
		||||
 | 
			
		||||
        /** @var Carbon $end */
 | 
			
		||||
        $end            = $this->parameters->get('end');
 | 
			
		||||
        $end->endOfDay();
 | 
			
		||||
 | 
			
		||||
        /** @var Collection $accounts */
 | 
			
		||||
        $accounts       = $params['accounts'];
 | 
			
		||||
 | 
			
		||||
        /** @var string $preferredRange */
 | 
			
		||||
        $preferredRange = $params['period'];
 | 
			
		||||
        $queryParameters = $request->getParameters();
 | 
			
		||||
        $accounts        = $this->getAccountList($queryParameters);
 | 
			
		||||
 | 
			
		||||
        // prepare for currency conversion and data collection:
 | 
			
		||||
        /** @var TransactionCurrency $default */
 | 
			
		||||
        $default        = app('amount')->getDefaultCurrency();
 | 
			
		||||
        $default         = app('amount')->getDefaultCurrency();
 | 
			
		||||
 | 
			
		||||
        // get journals for entire period:
 | 
			
		||||
        /** @var GroupCollectorInterface $collector */
 | 
			
		||||
        $collector      = app(GroupCollectorInterface::class);
 | 
			
		||||
        $collector->setRange($start, $end)->withAccountInformation();
 | 
			
		||||
        $collector->setXorAccounts($accounts);
 | 
			
		||||
        $collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::RECONCILIATION, TransactionType::TRANSFER]);
 | 
			
		||||
        $journals       = $collector->getExtractedJournals();
 | 
			
		||||
 | 
			
		||||
        $object         = new AccountBalanceGrouped();
 | 
			
		||||
        $object->setPreferredRange($preferredRange);
 | 
			
		||||
        $this->collector->setRange($queryParameters['start'], $queryParameters['end'])
 | 
			
		||||
            ->withAccountInformation()
 | 
			
		||||
            ->setXorAccounts($accounts)
 | 
			
		||||
            ->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::RECONCILIATION, TransactionType::TRANSFER])
 | 
			
		||||
        ;
 | 
			
		||||
        $journals        = $this->collector->getExtractedJournals();
 | 
			
		||||
 | 
			
		||||
        $object          = new AccountBalanceGrouped();
 | 
			
		||||
        $object->setPreferredRange($queryParameters['period']);
 | 
			
		||||
        $object->setDefault($default);
 | 
			
		||||
        $object->setAccounts($accounts);
 | 
			
		||||
        $object->setJournals($journals);
 | 
			
		||||
        $object->setStart($start);
 | 
			
		||||
        $object->setEnd($end);
 | 
			
		||||
        $object->setStart($queryParameters['start']);
 | 
			
		||||
        $object->setEnd($queryParameters['end']);
 | 
			
		||||
        $object->groupByCurrencyAndPeriod();
 | 
			
		||||
        $chartData      = $object->convertToChartData();
 | 
			
		||||
        $data            = $object->convertToChartData();
 | 
			
		||||
        foreach ($data as $entry) {
 | 
			
		||||
            $this->chartData->add($entry);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return response()->json($this->clean($chartData));
 | 
			
		||||
        return response()->json($this->chartData->render());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -64,12 +64,9 @@ class BudgetController extends Controller
 | 
			
		||||
                $this->blRepository  = app(BudgetLimitRepositoryInterface::class);
 | 
			
		||||
                $this->opsRepository = app(OperationsRepositoryInterface::class);
 | 
			
		||||
                $this->currency      = app('amount')->getDefaultCurrency();
 | 
			
		||||
 | 
			
		||||
                $userGroup           = $this->validateUserGroup($request);
 | 
			
		||||
                if (null !== $userGroup) {
 | 
			
		||||
                    $this->repository->setUserGroup($userGroup);
 | 
			
		||||
                    $this->opsRepository->setUserGroup($userGroup);
 | 
			
		||||
                }
 | 
			
		||||
                $this->repository->setUserGroup($userGroup);
 | 
			
		||||
                $this->opsRepository->setUserGroup($userGroup);
 | 
			
		||||
 | 
			
		||||
                return $next($request);
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -57,10 +57,7 @@ class CategoryController extends Controller
 | 
			
		||||
            function ($request, $next) {
 | 
			
		||||
                $this->accountRepos  = app(AccountRepositoryInterface::class);
 | 
			
		||||
                $this->currencyRepos = app(CurrencyRepositoryInterface::class);
 | 
			
		||||
                $userGroup           = $this->validateUserGroup($request);
 | 
			
		||||
                if (null !== $userGroup) {
 | 
			
		||||
                    $this->accountRepos->setUserGroup($userGroup);
 | 
			
		||||
                }
 | 
			
		||||
                $this->accountRepos->setUserGroup($this->validateUserGroup($request));
 | 
			
		||||
 | 
			
		||||
                return $next($request);
 | 
			
		||||
            }
 | 
			
		||||
@@ -100,34 +97,34 @@ class CategoryController extends Controller
 | 
			
		||||
 | 
			
		||||
        /** @var array $journal */
 | 
			
		||||
        foreach ($journals as $journal) {
 | 
			
		||||
            $currencyId                    = (int)$journal['currency_id'];
 | 
			
		||||
            $currencyId                    = (int) $journal['currency_id'];
 | 
			
		||||
            $currency                      = $currencies[$currencyId] ?? $this->currencyRepos->find($currencyId);
 | 
			
		||||
            $currencies[$currencyId]       = $currency;
 | 
			
		||||
            $categoryName                  = null === $journal['category_name'] ? (string)trans('firefly.no_category') : $journal['category_name'];
 | 
			
		||||
            $categoryName                  = null === $journal['category_name'] ? (string) trans('firefly.no_category') : $journal['category_name'];
 | 
			
		||||
            $amount                        = app('steam')->positive($journal['amount']);
 | 
			
		||||
            $nativeAmount                  = $converter->convert($default, $currency, $journal['date'], $amount);
 | 
			
		||||
            $key                           = sprintf('%s-%s', $categoryName, $currency->code);
 | 
			
		||||
            if ((int)$journal['foreign_currency_id'] === $default->id) {
 | 
			
		||||
            if ((int) $journal['foreign_currency_id'] === $default->id) {
 | 
			
		||||
                $nativeAmount = app('steam')->positive($journal['foreign_amount']);
 | 
			
		||||
            }
 | 
			
		||||
            // create arrays
 | 
			
		||||
            $return[$key] ??= [
 | 
			
		||||
                'label'                            => $categoryName,
 | 
			
		||||
                'currency_id'                      => (string)$currency->id,
 | 
			
		||||
                'currency_code'                    => $currency->code,
 | 
			
		||||
                'currency_name'                    => $currency->name,
 | 
			
		||||
                'currency_symbol'                  => $currency->symbol,
 | 
			
		||||
                'currency_decimal_places'          => $currency->decimal_places,
 | 
			
		||||
                'native_currency_id'               => (string)$default->id,
 | 
			
		||||
                'native_currency_code'             => $default->code,
 | 
			
		||||
                'native_currency_name'             => $default->name,
 | 
			
		||||
                'native_currency_symbol'           => $default->symbol,
 | 
			
		||||
                'native_currency_decimal_places'   => $default->decimal_places,
 | 
			
		||||
                'period'                           => null,
 | 
			
		||||
                'start'                            => $start->toAtomString(),
 | 
			
		||||
                'end'                              => $end->toAtomString(),
 | 
			
		||||
                'amount'                           => '0',
 | 
			
		||||
                'native_amount'                    => '0',
 | 
			
		||||
                'label'                          => $categoryName,
 | 
			
		||||
                'currency_id'                    => (string) $currency->id,
 | 
			
		||||
                'currency_code'                  => $currency->code,
 | 
			
		||||
                'currency_name'                  => $currency->name,
 | 
			
		||||
                'currency_symbol'                => $currency->symbol,
 | 
			
		||||
                'currency_decimal_places'        => $currency->decimal_places,
 | 
			
		||||
                'native_currency_id'             => (string) $default->id,
 | 
			
		||||
                'native_currency_code'           => $default->code,
 | 
			
		||||
                'native_currency_name'           => $default->name,
 | 
			
		||||
                'native_currency_symbol'         => $default->symbol,
 | 
			
		||||
                'native_currency_decimal_places' => $default->decimal_places,
 | 
			
		||||
                'period'                         => null,
 | 
			
		||||
                'start'                          => $start->toAtomString(),
 | 
			
		||||
                'end'                            => $end->toAtomString(),
 | 
			
		||||
                'amount'                         => '0',
 | 
			
		||||
                'native_amount'                  => '0',
 | 
			
		||||
            ];
 | 
			
		||||
 | 
			
		||||
            // add monies
 | 
			
		||||
@@ -138,7 +135,7 @@ class CategoryController extends Controller
 | 
			
		||||
 | 
			
		||||
        // order by native amount
 | 
			
		||||
        usort($return, static function (array $a, array $b) {
 | 
			
		||||
            return (float)$a['native_amount'] < (float)$b['native_amount'] ? 1 : -1;
 | 
			
		||||
            return (float) $a['native_amount'] < (float) $b['native_amount'] ? 1 : -1;
 | 
			
		||||
        });
 | 
			
		||||
        $converter->summarize();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ namespace FireflyIII\Api\V2\Controllers;
 | 
			
		||||
use Carbon\Carbon;
 | 
			
		||||
use Carbon\Exceptions\InvalidDateException;
 | 
			
		||||
use Carbon\Exceptions\InvalidFormatException;
 | 
			
		||||
use FireflyIII\Enums\UserRoleEnum;
 | 
			
		||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
 | 
			
		||||
use FireflyIII\Transformers\V2\AbstractTransformer;
 | 
			
		||||
use Illuminate\Database\Eloquent\Model;
 | 
			
		||||
@@ -55,6 +56,7 @@ class Controller extends BaseController
 | 
			
		||||
 | 
			
		||||
    protected const string CONTENT_TYPE = 'application/vnd.api+json';
 | 
			
		||||
    protected ParameterBag $parameters;
 | 
			
		||||
    protected array $acceptedRoles      = [UserRoleEnum::READ_ONLY];
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
@@ -67,43 +69,6 @@ class Controller extends BaseController
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final protected function jsonApiList(string $key, LengthAwarePaginator $paginator, AbstractTransformer $transformer): array
 | 
			
		||||
    {
 | 
			
		||||
        $manager  = new Manager();
 | 
			
		||||
        $baseUrl  = request()->getSchemeAndHttpHost().'/api/v2';
 | 
			
		||||
        $manager->setSerializer(new JsonApiSerializer($baseUrl));
 | 
			
		||||
 | 
			
		||||
        $objects  = $paginator->getCollection();
 | 
			
		||||
 | 
			
		||||
        // the transformer, at this point, needs to collect information that ALL items in the collection
 | 
			
		||||
        // require, like meta-data and stuff like that, and save it for later.
 | 
			
		||||
        $transformer->collectMetaData($objects);
 | 
			
		||||
 | 
			
		||||
        $resource = new FractalCollection($objects, $transformer, $key);
 | 
			
		||||
        $resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
 | 
			
		||||
 | 
			
		||||
        return $manager->createData($resource)->toArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a JSON API object and returns it.
 | 
			
		||||
     *
 | 
			
		||||
     * @param array<int, mixed>|Model $object
 | 
			
		||||
     */
 | 
			
		||||
    final protected function jsonApiObject(string $key, array|Model $object, AbstractTransformer $transformer): array
 | 
			
		||||
    {
 | 
			
		||||
        // create some objects:
 | 
			
		||||
        $manager  = new Manager();
 | 
			
		||||
        $baseUrl  = request()->getSchemeAndHttpHost().'/api/v2';
 | 
			
		||||
        $manager->setSerializer(new JsonApiSerializer($baseUrl));
 | 
			
		||||
 | 
			
		||||
        $transformer->collectMetaData(new Collection([$object]));
 | 
			
		||||
 | 
			
		||||
        $resource = new Item($object, $transformer, $key);
 | 
			
		||||
 | 
			
		||||
        return $manager->createData($resource)->toArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * TODO duplicate from V1 controller
 | 
			
		||||
     * Method to grab all parameters from the URL.
 | 
			
		||||
@@ -157,6 +122,9 @@ class Controller extends BaseController
 | 
			
		||||
                    $obj = null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (null !== $date && 'end' === $field) {
 | 
			
		||||
                $obj->endOfDay();
 | 
			
		||||
            }
 | 
			
		||||
            $bag->set($field, $obj);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -184,4 +152,45 @@ class Controller extends BaseController
 | 
			
		||||
 | 
			
		||||
        return $bag;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final protected function jsonApiList(string $key, LengthAwarePaginator $paginator, AbstractTransformer $transformer): array
 | 
			
		||||
    {
 | 
			
		||||
        $manager  = new Manager();
 | 
			
		||||
        $baseUrl  = request()->getSchemeAndHttpHost().'/api/v2';
 | 
			
		||||
 | 
			
		||||
        // TODO add stuff to path?
 | 
			
		||||
 | 
			
		||||
        $manager->setSerializer(new JsonApiSerializer($baseUrl));
 | 
			
		||||
 | 
			
		||||
        $objects  = $paginator->getCollection();
 | 
			
		||||
 | 
			
		||||
        // the transformer, at this point, needs to collect information that ALL items in the collection
 | 
			
		||||
        // require, like meta-data and stuff like that, and save it for later.
 | 
			
		||||
        $objects  = $transformer->collectMetaData($objects);
 | 
			
		||||
        $paginator->setCollection($objects);
 | 
			
		||||
 | 
			
		||||
        $resource = new FractalCollection($objects, $transformer, $key);
 | 
			
		||||
        $resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
 | 
			
		||||
 | 
			
		||||
        return $manager->createData($resource)->toArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a JSON API object and returns it.
 | 
			
		||||
     *
 | 
			
		||||
     * @param array<int, mixed>|Model $object
 | 
			
		||||
     */
 | 
			
		||||
    final protected function jsonApiObject(string $key, array|Model $object, AbstractTransformer $transformer): array
 | 
			
		||||
    {
 | 
			
		||||
        // create some objects:
 | 
			
		||||
        $manager  = new Manager();
 | 
			
		||||
        $baseUrl  = request()->getSchemeAndHttpHost().'/api/v2';
 | 
			
		||||
        $manager->setSerializer(new JsonApiSerializer($baseUrl));
 | 
			
		||||
 | 
			
		||||
        $transformer->collectMetaData(new Collection([$object]));
 | 
			
		||||
 | 
			
		||||
        $resource = new Item($object, $transformer, $key);
 | 
			
		||||
 | 
			
		||||
        return $manager->createData($resource)->toArray();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										111
									
								
								app/Api/V2/Controllers/JsonApi/AccountController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								app/Api/V2/Controllers/JsonApi/AccountController.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
			
		||||
<?php
 | 
			
		||||
/*
 | 
			
		||||
 * AccountController.php
 | 
			
		||||
 * Copyright (c) 2024 james@firefly-iii.org.
 | 
			
		||||
 *
 | 
			
		||||
 * This file is part of Firefly III (https://github.com/firefly-iii).
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as
 | 
			
		||||
 * published by the Free Software Foundation, either version 3 of the
 | 
			
		||||
 * License, or (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see https://www.gnu.org/licenses/.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace FireflyIII\Api\V2\Controllers\JsonApi;
 | 
			
		||||
 | 
			
		||||
use FireflyIII\Http\Controllers\Controller;
 | 
			
		||||
use FireflyIII\JsonApi\V2\Accounts\AccountCollectionQuery;
 | 
			
		||||
use FireflyIII\JsonApi\V2\Accounts\AccountSchema;
 | 
			
		||||
use FireflyIII\JsonApi\V2\Accounts\AccountSingleQuery;
 | 
			
		||||
use FireflyIII\Models\Account;
 | 
			
		||||
use Illuminate\Contracts\Support\Responsable;
 | 
			
		||||
use Illuminate\Http\Response;
 | 
			
		||||
use Illuminate\Support\Facades\Log;
 | 
			
		||||
use LaravelJsonApi\Core\Responses\DataResponse;
 | 
			
		||||
use LaravelJsonApi\Laravel\Http\Controllers\Actions;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class AccountController
 | 
			
		||||
 *
 | 
			
		||||
 * This class handles api/v2 requests for accounts.
 | 
			
		||||
 * Most stuff is default stuff.
 | 
			
		||||
 */
 | 
			
		||||
class AccountController extends Controller
 | 
			
		||||
{
 | 
			
		||||
    use Actions\AttachRelationship;
 | 
			
		||||
    use Actions\Destroy;
 | 
			
		||||
    use Actions\DetachRelationship;
 | 
			
		||||
 | 
			
		||||
    use Actions\FetchMany;
 | 
			
		||||
    // use Actions\FetchOne;
 | 
			
		||||
    use Actions\FetchRelated;
 | 
			
		||||
    use Actions\FetchRelationship;
 | 
			
		||||
    use Actions\Store;
 | 
			
		||||
    use Actions\Update;
 | 
			
		||||
    use Actions\UpdateRelationship;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetch zero to many JSON API resources.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Responsable|Response
 | 
			
		||||
     */
 | 
			
		||||
    public function index(AccountSchema $schema, AccountCollectionQuery $request)
 | 
			
		||||
    {
 | 
			
		||||
        Log::debug(__METHOD__);
 | 
			
		||||
        $models = $schema
 | 
			
		||||
            ->repository()
 | 
			
		||||
            ->queryAll()
 | 
			
		||||
            ->withRequest($request)
 | 
			
		||||
            ->get()
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
        // do something custom...
 | 
			
		||||
 | 
			
		||||
        return new DataResponse($models);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetch zero to one JSON API resource by id.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Responsable|Response
 | 
			
		||||
     */
 | 
			
		||||
    public function show(AccountSchema $schema, AccountSingleQuery $request, Account $account)
 | 
			
		||||
    {
 | 
			
		||||
        Log::debug(__METHOD__);
 | 
			
		||||
        $model = $schema->repository()
 | 
			
		||||
            ->queryOne($account)
 | 
			
		||||
            ->withRequest($request)
 | 
			
		||||
            ->first()
 | 
			
		||||
        ;
 | 
			
		||||
        Log::debug(sprintf('%s again!', __METHOD__));
 | 
			
		||||
 | 
			
		||||
        // do something custom...
 | 
			
		||||
 | 
			
		||||
        return new DataResponse($model);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //    public function readAccountBalances(AnonymousQuery $query, AccountBalanceSchema $schema, Account $account): Responsable
 | 
			
		||||
    //    {
 | 
			
		||||
    //        $schema = JsonApi::server()->schemas()->schemaFor('account-balances');
 | 
			
		||||
    //
 | 
			
		||||
    //        $models = $schema
 | 
			
		||||
    //            ->repository()
 | 
			
		||||
    //            ->queryAll()
 | 
			
		||||
    //            ->withRequest($query)
 | 
			
		||||
    //            ->withAccount($account)
 | 
			
		||||
    //            ->get()
 | 
			
		||||
    //        ;
 | 
			
		||||
    //
 | 
			
		||||
    //        return DataResponse::make($models);
 | 
			
		||||
    //    }
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user